├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENCE ├── README.md ├── _config.yml ├── codecov.yml ├── common ├── config │ └── rush │ │ ├── .npmrc │ │ ├── command-line.json │ │ ├── common-versions.json │ │ ├── npm-shrinkwrap.json │ │ └── version-policies.json └── scripts │ ├── install-run-rush.js │ └── install-run.js ├── packages ├── hayspec-cli │ ├── CHANGELOG.json │ ├── CHANGELOG.md │ ├── README.md │ ├── bin │ │ └── hayspec │ ├── nodemon.json │ ├── package.json │ ├── src │ │ ├── commands │ │ │ ├── init.ts │ │ │ └── test.ts │ │ ├── index.ts │ │ ├── lib │ │ │ └── env.ts │ │ └── tests │ │ │ ├── assets │ │ │ ├── invalid.hay.ts │ │ │ └── valid.hay.ts │ │ │ ├── index.test.ts │ │ │ └── lib │ │ │ └── env.test.ts │ └── tsconfig.json ├── hayspec-init │ ├── CHANGELOG.json │ ├── CHANGELOG.md │ ├── README.md │ ├── nodemon.json │ ├── package.json │ ├── src │ │ ├── core │ │ │ ├── generator.ts │ │ │ └── structure.ts │ │ ├── index.ts │ │ └── tests │ │ │ └── core │ │ │ ├── generator.test.ts │ │ │ └── index.test.ts │ └── tsconfig.json ├── hayspec-reporter │ ├── CHANGELOG.json │ ├── CHANGELOG.md │ ├── README.md │ ├── nodemon.json │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ └── printer.ts │ │ ├── reporters │ │ │ └── default.ts │ │ └── tests │ │ │ ├── index.test.ts │ │ │ └── reporters │ │ │ └── default.test.ts │ └── tsconfig.json ├── hayspec-runner │ ├── CHANGELOG.json │ ├── CHANGELOG.md │ ├── README.md │ ├── nodemon.json │ ├── package.json │ ├── src │ │ ├── core │ │ │ └── runner.ts │ │ ├── index.ts │ │ └── tests │ │ │ ├── assets │ │ │ ├── all │ │ │ │ ├── ok.all.ts │ │ │ │ └── sub │ │ │ │ │ ├── err.all.ts │ │ │ │ │ └── ok.all.ts │ │ │ └── only │ │ │ │ ├── err.only.ts │ │ │ │ └── ok.only.ts │ │ │ └── runner.test.ts │ └── tsconfig.json └── hayspec-spec │ ├── CHANGELOG.json │ ├── CHANGELOG.md │ ├── README.md │ ├── nodemon.json │ ├── package.json │ ├── src │ ├── asserts │ │ ├── deep-equal.ts │ │ ├── is.ts │ │ ├── throws.ts │ │ └── truthy.ts │ ├── core │ │ ├── context.ts │ │ ├── reporter.ts │ │ ├── spec.ts │ │ ├── stage.ts │ │ └── types.ts │ ├── index.ts │ ├── methods │ │ ├── exec.ts │ │ ├── request.ts │ │ └── sleep.ts │ └── tests │ │ ├── asserts │ │ ├── deep-equal.test.ts │ │ ├── is.test.ts │ │ ├── throws.test.ts │ │ └── truthy.test.ts │ │ ├── core │ │ ├── context.test.ts │ │ ├── reporter.test.ts │ │ ├── spec.test.ts │ │ └── stage.test.ts │ │ ├── index.test.ts │ │ └── methods │ │ ├── exec.test.ts │ │ ├── request.test.ts │ │ └── sleep.test.ts │ └── tsconfig.json └── rush.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Don't allow people to merge changes to these generated files, because the result 2 | # may be invalid. You need to run "rush update" again. 3 | shrinkwrap.yaml merge=binary 4 | npm-shrinkwrap.json merge=binary 5 | yarn.lock merge=binary 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [xpepermint] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # Bower dependency directory (https://bower.io/) 25 | bower_components 26 | 27 | # node-waf configuration 28 | .lock-wscript 29 | 30 | # Compiled binary addons (https://nodejs.org/api/addons.html) 31 | build/Release 32 | 33 | # Dependency directories 34 | node_modules/ 35 | jspm_packages/ 36 | 37 | # Optional npm cache directory 38 | .npm 39 | 40 | # Optional eslint cache 41 | .eslintcache 42 | 43 | # Optional REPL history 44 | .node_repl_history 45 | 46 | # Output of 'npm pack' 47 | *.tgz 48 | 49 | # Yarn Integrity file 50 | .yarn-integrity 51 | 52 | # dotenv environment variables file 53 | .env 54 | 55 | # next.js build output 56 | .next 57 | 58 | # Idea configuration. 59 | .idea 60 | 61 | # Common toolchain intermediate files 62 | temp 63 | 64 | # Rush files 65 | common/temp/** 66 | package-deps.json 67 | 68 | # OSx environment 69 | .DS_Store 70 | 71 | # Compiled data 72 | dist 73 | build 74 | _site 75 | GemFile 76 | GemFile.lock 77 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | .nyc_output 4 | node_modules 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - 14 5 | script: 6 | # use latest npm to work on node9 7 | - npm install -g npm 8 | # install required global npm packages 9 | - npm install -g codecov 10 | - npm install -g @microsoft/rush 11 | # testing 12 | - rush update --full 13 | - rush rebuild 14 | - rush test 15 | after_success: 16 | # code coverage & codecov upload 17 | - rm -Rf .nyc_output && mkdir -p .nyc_output 18 | - find packages/**/.nyc_output -type f -name '*.json' -not -path 'packages/**/.nyc_output/processinfo/*' -exec cp '{}' .nyc_output \; 19 | - npx nyc report --extension=.ts --reporter=text-lcov > .nyc_output/coverage.lcov 20 | - codecov 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Development 4 | 5 | We use [RushJS](https://rushjs.io) to manage this repository. Some quick notes on how to manage the repository are documented [here](https://gist.github.com/xpepermint/eecfc6ad6cd7c9f5dcda381aa255738d). 6 | 7 | **Install dependencies** -- You only need to run this once. 8 | 9 | ```sh 10 | npm install -g @microsoft/rush 11 | ``` 12 | 13 | **Update packages** -- Run this if you add/remove packages from this repository. 14 | 15 | ```sh 16 | rush update --full 17 | ``` 18 | 19 | **Rebuild and test** -- Do this each time you make changes to the code 20 | 21 | ```sh 22 | rush rebuild --verbose 23 | rush test --verbose 24 | ``` 25 | 26 | The above notes will help you decide which commands to run during development on your own machine. But for any commits and pull requests in this repository, the entire test suite will be run using continuous integration. 27 | 28 | ## Issues 29 | 30 | We use GitHub issues to track bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. 31 | 32 | ## Pull requests 33 | 34 | Always fork the repo and create your branch from master. If you've added code that should be tested, add tests. Alsp ensure the test suite passes before submitting the PR. 35 | 36 | ## Coding style 37 | 38 | Please follow the [TypeScript coding guidelines](https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines). 39 | 40 | ## Release process 41 | 42 | The release manager will publish packages to NPM using these commands. 43 | 44 | ``` 45 | $ rush version --bump --override-bump minor 46 | $ rm -Rf common/temp && rush update --full 47 | $ rush rebuild 48 | $ rush test -v 49 | $ rush publish --publish --include-all 50 | ``` 51 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018 Xpepermint. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hayspec Framework 2 | 3 | [![Build Status](https://travis-ci.org/hayspec/framework.svg?branch=master)](https://travis-ci.org/hayspec/framework) [![codecov](https://codecov.io/gh/hayspec/framework/branch/master/graph/badge.svg)](https://codecov.io/gh/hayspec/framework) 4 | 5 | Hayspec is a lightweight, open source, magic-free framework for testing JavaScript and NodeJS applications. It's written in [TypeScript](https://www.typescriptlang.org/) and it's actively maintained. The source code is available on [GitHub](https://github.com/hayspec/framework) where you can also find our [issue tracker](https://github.com/hayspec/framework/issues). 6 | 7 | ## Installation of Hayspec 8 | 9 | Start by installing the hayspec command-line tool. 10 | 11 | ```bash 12 | $ npm install -g @hayspec/cli 13 | ``` 14 | 15 | This package uses promises thus you need to use [Promise polyfill](https://github.com/taylorhakes/promise-polyfill) when promises are not supported. 16 | 17 | ## Getting started 18 | 19 | Hayspec automates the testing process of your JavaScript or TypeScript code. It doesn't require you to install certain applications in order to get started. 20 | 21 | The Hayspec interface is designed to fully support the power of [TypeScript](https://www.typescriptlang.org/). It is magic-free which means you have a complete control and visibility of what the code does and how tests are executed. The code should look familiar to any JavaScript or TypeScript developer. 22 | 23 | ### Project initialization 24 | 25 | Start by creating a new project folder. 26 | 27 | ```bash 28 | $ mkdir myProject 29 | $ cd myProject 30 | ``` 31 | 32 | Initialize the project and install the dependencies. 33 | 34 | ```bash 35 | $ hayspec init 36 | $ npm install 37 | ``` 38 | 39 | Run tests to verify everything works as expected. 40 | 41 | ```bash 42 | $ npm test 43 | ``` 44 | 45 | ### Writting tests 46 | 47 | The core test functionality is provided by the `@hayspec/spec` module which is automatically attached to your project at initialization. 48 | 49 | #### Initializing specs 50 | 51 | The framework provides a `Spec` class which holds basically the whole testing power. You start your test by creating an instance of that class. 52 | 53 | ```ts 54 | import { Spec } from '@hayspec/spec'; 55 | 56 | const spec = new Spec(); 57 | ``` 58 | 59 | #### Testing features 60 | 61 | The Spec instance provide methods that you can use when writting tests. Most of the time you will use the `test` method which performs the test you write. 62 | 63 | ```ts 64 | spec.test('is true', async (ctx) => { // promise | function 65 | ctx.true(true); 66 | }); 67 | ``` 68 | 69 | There is also the `skip` method which prevents a test te be performed, and the `only` method which includes itself into the test process but excludes all other tests. 70 | 71 | #### Nested specs 72 | 73 | Tests can be nested using the `spec` method. 74 | 75 | ```ts 76 | const colors = new Spec(); 77 | ... 78 | spec.spec('colors', colors); 79 | ``` 80 | 81 | #### Using callbacks 82 | 83 | The framework provides `before` and `after` methods which are execute at the beginning and at the end of the spec case. 84 | 85 | ```ts 86 | spec.before((stage) => { 87 | // execute before all tests 88 | }); 89 | ... 90 | spec.after((stage) => { 91 | // execute after all tests 92 | }); 93 | ``` 94 | 95 | These methods have access to the `stage` of the spec instance. The stage is global to the whole spec stack which means that all settings are always preserved. 96 | 97 | There are also the `beforeEach` and `afterEach` methods which are triggered before and after each test. These methods have access to the `context` and `stage` of the spec. The context represents a copy of a stage and is preserved between `beforeEach`, `test` and `afterEach` methods. This allows for testing atomic tests where the context is always reset for each test. 98 | 99 | ```ts 100 | spec.beforeEach((context, stage) => { 101 | // execute before all tests 102 | }); 103 | ... 104 | spec.afterEach((context, stage) => { 105 | // execute after all tests 106 | }); 107 | ``` 108 | Callback functions can be called multiple times and the execution will happen in a defined sequence. 109 | 110 | #### Shared data 111 | 112 | The `context` and the `stage` both provide a way to `set` and `get` values with proper TypeScript types. 113 | 114 | ```ts 115 | interface Data { 116 | id: number; 117 | name: string; 118 | } 119 | 120 | const spec = new Spec(); 121 | 122 | spec.beforeEach((ctx) => { 123 | ctx.set('id', 100); 124 | ctx.set('name', 'John'); 125 | }) 126 | 127 | spec.test('is John with id=100', (ctx) => { 128 | const id = ctx.get('id'); 129 | const name = ctx.get('name'); 130 | ctx.is(id, 100); 131 | ctx.is(name, 'John'); 132 | }) 133 | ``` 134 | 135 | Values set inside the `before` and `after` blocks are available to all `spec` methods. Values set in the `beforeEach` and `afterEach` blocks are available only on the context stack of each test. 136 | 137 | ### Using CLI 138 | 139 | The `@hayspec/cli` module is automatically installed when you initialize the project. You can interact with the utility using the `npx hayspec` command in your terminal. 140 | 141 | To get a list of available features use the `--help` flag. 142 | 143 | ``` 144 | $ npx hayspec --help 145 | ``` 146 | 147 | #### Running tests 148 | 149 | Every test file must export the `spec` instance for the CLI to be able to detect the test. 150 | 151 | ```ts 152 | export default spec; 153 | ``` 154 | 155 | Run the `hayspec test` command to run tests. Customize the files search by using the `--match` flag. 156 | 157 | ``` 158 | $ npx hayspec test --match ./**/*.test.* 159 | ``` 160 | 161 | #### TypeScript support 162 | 163 | Install the [ts-node](https://www.npmjs.com/package/ts-node) NPM package then use the `--require` flag to enable [TypeScript](https://www.typescriptlang.org/) support. 164 | 165 | ``` 166 | hayspec --require ts-node/register 167 | ``` 168 | 169 | #### Project configuration 170 | 171 | Hayspec configuration options can be saved inside the `package.json` file under the the `hayspec` key. 172 | 173 | ``` 174 | { 175 | "hayspec": { 176 | "require": [ 177 | "ts-node/register" 178 | ], 179 | "match": [ 180 | "./src/**/*.test.*" 181 | ] 182 | } 183 | } 184 | ``` 185 | 186 | Note that these options can be overriden by providing CLI arguments. 187 | 188 | ## Hayspec packages 189 | 190 | | Package | Description | Version 191 | |-|-|- 192 | | [@hayspec/cli](https://github.com/hayspec/framework/tree/master/packages/hayspec-cli) | Command-line interface. | [![NPM Version](https://badge.fury.io/js/@hayspec%2Fcli.svg)](https://badge.fury.io/js/%40hayspec%2Fcli) 193 | | [@hayspec/init](https://github.com/hayspec/framework/tree/master/packages/hayspec-init) | Project initializer. | [![NPM Version](https://badge.fury.io/js/@hayspec%2Finit.svg)](https://badge.fury.io/js/%40hayspec%2Finit) 194 | | [@hayspec/reporter](https://github.com/hayspec/framework/tree/master/packages/hayspec-reporter) | Default command-line reporter. | [![NPM Version](https://badge.fury.io/js/@hayspec%2Freporter.svg)](https://badge.fury.io/js/%40hayspec%2Freporter) 195 | | [@hayspec/runner](https://github.com/hayspec/framework/tree/master/packages/hayspec-runner) | Helper for loading and performing test files. | [![NPM Version](https://badge.fury.io/js/@hayspec%2Frunner.svg)](https://badge.fury.io/js/%40hayspec%2Frunner) 196 | | [@hayspec/spec](https://github.com/hayspec/framework/tree/master/packages/hayspec-spec) | Main framework features for writing tests. | [![NPM Version](https://badge.fury.io/js/@hayspec%2Fspec.svg)](https://badge.fury.io/js/%40hayspec%2Fspec) 197 | 198 | ## Contributing 199 | 200 | See [CONTRIBUTING.md](https://github.com/hayspec/framework/blob/master/CONTRIBUTING.md) for how to help out. 201 | 202 | ## Licence 203 | 204 | See [LICENSE](https://github.com/hayspec/framework/blob/master/LICENCE) for details. 205 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: hayspec 2 | heading: a magic-free framework for testing JavaScript and NodeJS applications. 3 | logo: /assets/images/hayspec.svg 4 | email: info@hayspec.org 5 | google_analytics: 6 | 7 | menu: 8 | - url: '#hayspec-framework' 9 | title: What's Hayspec? 10 | - url: '#getting-started' 11 | title: Get Started 12 | - url: '#writting-tests' 13 | title: Writing tests 14 | 15 | remote_theme: hayspec/gp-theme 16 | 17 | plugins: 18 | - jekyll-remote-theme 19 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | fixes: 2 | - "hayspec/framework::/" # travis path to match github repo paths 3 | comment: 4 | layout: "reach, diff, flags, files" 5 | behavior: default 6 | require_changes: false # if true: only post the comment if coverage changes 7 | require_base: no # [yes :: must have a base report to post] 8 | require_head: yes # [yes :: must have a head report to post] 9 | branches: null 10 | coverage: 11 | status: 12 | project: 13 | default: 14 | # basic 15 | target: auto 16 | threshold: null 17 | base: auto 18 | # advanced 19 | branches: null 20 | if_no_uploads: error 21 | if_not_found: success 22 | if_ci_failed: error 23 | only_pulls: false 24 | flags: null 25 | paths: null 26 | -------------------------------------------------------------------------------- /common/config/rush/.npmrc: -------------------------------------------------------------------------------- 1 | # Rush uses this file to configure the package registry, regardless of whether the 2 | # package manager is PNPM, NPM, or Yarn. Prior to invoking the package manager, 3 | # Rush will always copy this file to the folder where installation is performed. 4 | # When NPM is the package manager, Rush works around NPM's processing of 5 | # undefined environment variables by deleting any lines that reference undefined 6 | # environment variables. 7 | # 8 | # DO NOT SPECIFY AUTHENTICATION CREDENTIALS IN THIS FILE. It should only be used 9 | # to configure registry sources. 10 | 11 | registry=https://registry.npmjs.org/ 12 | always-auth=false 13 | -------------------------------------------------------------------------------- /common/config/rush/command-line.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json", 3 | "commands": [ 4 | { 5 | "commandKind": "bulk", 6 | "name": "test", 7 | "summary": "Test packages.", 8 | "description": "Executes automated tests.", 9 | "enableParallelism": true 10 | } 11 | ], 12 | "parameters": [] 13 | } 14 | -------------------------------------------------------------------------------- /common/config/rush/common-versions.json: -------------------------------------------------------------------------------- 1 | /** 2 | * This configuration file specifies NPM dependency version selections that affect all projects 3 | * in a Rush repo. For full documentation, please see https://rushjs.io 4 | */ 5 | { 6 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json", 7 | 8 | /** 9 | * A table that specifies a "preferred version" for a dependency package. The "preferred version" 10 | * is typically used to hold an indirect dependency back to a specific version, however generally 11 | * it can be any SemVer range specifier (e.g. "~1.2.3"), and it will narrow any (compatible) 12 | * SemVer range specifier. See the Rush documentation for details about this feature. 13 | */ 14 | "preferredVersions": { 15 | 16 | /** 17 | * When someone asks for "^1.0.0" make sure they get "1.2.3" when working in this repo, 18 | * instead of the latest version. 19 | */ 20 | // "some-library": "1.2.3" 21 | }, 22 | 23 | /** 24 | * The "rush check" command can be used to enforce that every project in the repo must specify 25 | * the same SemVer range for a given dependency. However, sometimes exceptions are needed. 26 | * The allowedAlternativeVersions table allows you to list other SemVer ranges that will be 27 | * accepted by "rush check" for a given dependency. 28 | * 29 | * IMPORTANT: THIS TABLE IS FOR *ADDITIONAL* VERSION RANGES THAT ARE ALTERNATIVES TO THE 30 | * USUAL VERSION (WHICH IS INFERRED BY LOOKING AT ALL PROJECTS IN THE REPO). 31 | * This design avoids unnecessary churn in this file. 32 | */ 33 | "allowedAlternativeVersions": { 34 | 35 | /** 36 | * For example, allow some projects to use an older TypeScript compiler 37 | * (in addition to whatever "usual" version is being used by other projects in the repo): 38 | */ 39 | // "typescript": [ 40 | // "~2.4.0" 41 | // ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common/config/rush/version-policies.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "policyName": "patchAll", 4 | "definitionName": "lockStepVersion", 5 | "version": "0.10.2", 6 | "nextBump": "patch" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /common/scripts/install-run-rush.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. 3 | // See the @microsoft/rush package's LICENSE file for license information. 4 | Object.defineProperty(exports, "__esModule", { value: true }); 5 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED. 6 | // 7 | // This script is intended for usage in an automated build environment where the Rush command may not have 8 | // been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush 9 | // specified in the rush.json configuration file (if not already installed), and then pass a command-line to it. 10 | // An example usage would be: 11 | // 12 | // node common/scripts/install-run-rush.js install 13 | // 14 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/ 15 | const path = require("path"); 16 | const fs = require("fs"); 17 | const install_run_1 = require("./install-run"); 18 | const PACKAGE_NAME = '@microsoft/rush'; 19 | function getRushVersion() { 20 | const rushJsonFolder = install_run_1.findRushJsonFolder(); 21 | const rushJsonPath = path.join(rushJsonFolder, install_run_1.RUSH_JSON_FILENAME); 22 | try { 23 | const rushJsonContents = fs.readFileSync(rushJsonPath, 'utf-8'); 24 | // Use a regular expression to parse out the rushVersion value because rush.json supports comments, 25 | // but JSON.parse does not and we don't want to pull in more dependencies than we need to in this script. 26 | const rushJsonMatches = rushJsonContents.match(/\"rushVersion\"\s*\:\s*\"([0-9a-zA-Z.+\-]+)\"/); 27 | return rushJsonMatches[1]; 28 | } 29 | catch (e) { 30 | throw new Error(`Unable to determine the required version of Rush from rush.json (${rushJsonFolder}). ` + 31 | 'The \'rushVersion\' field is either not assigned in rush.json or was specified ' + 32 | 'using an unexpected syntax.'); 33 | } 34 | } 35 | function run() { 36 | const [nodePath, /* Ex: /bin/node */ scriptPath, /* /repo/common/scripts/install-run-rush.js */ ...packageBinArgs /* [build, --to, myproject] */] = process.argv; 37 | if (!nodePath || !scriptPath) { 38 | throw new Error('Unexpected exception: could not detect node path or script path'); 39 | } 40 | if (process.argv.length < 3) { 41 | console.log('Usage: install-run-rush.js [args...]'); 42 | console.log('Example: install-run-rush.js build --to myproject'); 43 | process.exit(1); 44 | } 45 | install_run_1.runWithErrorAndStatusCode(() => { 46 | const version = getRushVersion(); 47 | console.log(`The rush.json configuration requests Rush version ${version}`); 48 | return install_run_1.installAndRun(PACKAGE_NAME, version, 'rush', packageBinArgs); 49 | }); 50 | } 51 | run(); 52 | //# sourceMappingURL=install-run-rush.js.map -------------------------------------------------------------------------------- /common/scripts/install-run.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. 3 | // See the @microsoft/rush package's LICENSE file for license information. 4 | Object.defineProperty(exports, "__esModule", { value: true }); 5 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED. 6 | // 7 | // This script is intended for usage in an automated build environment where a Node tool may not have 8 | // been preinstalled, or may have an unpredictable version. This script will automatically install the specified 9 | // version of the specified tool (if not already installed), and then pass a command-line to it. 10 | // An example usage would be: 11 | // 12 | // node common/scripts/install-run.js qrcode@1.2.2 qrcode https://rushjs.io 13 | // 14 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/ 15 | const childProcess = require("child_process"); 16 | const fs = require("fs"); 17 | const os = require("os"); 18 | const path = require("path"); 19 | exports.RUSH_JSON_FILENAME = 'rush.json'; 20 | const INSTALLED_FLAG_FILENAME = 'installed.flag'; 21 | const NODE_MODULES_FOLDER_NAME = 'node_modules'; 22 | const PACKAGE_JSON_FILENAME = 'package.json'; 23 | /** 24 | * Parse a package specifier (in the form of name\@version) into name and version parts. 25 | */ 26 | function parsePackageSpecifier(rawPackageSpecifier) { 27 | rawPackageSpecifier = (rawPackageSpecifier || '').trim(); 28 | const separatorIndex = rawPackageSpecifier.lastIndexOf('@'); 29 | let name; 30 | let version = undefined; 31 | if (separatorIndex === 0) { 32 | // The specifier starts with a scope and doesn't have a version specified 33 | name = rawPackageSpecifier; 34 | } 35 | else if (separatorIndex === -1) { 36 | // The specifier doesn't have a version 37 | name = rawPackageSpecifier; 38 | } 39 | else { 40 | name = rawPackageSpecifier.substring(0, separatorIndex); 41 | version = rawPackageSpecifier.substring(separatorIndex + 1); 42 | } 43 | if (!name) { 44 | throw new Error(`Invalid package specifier: ${rawPackageSpecifier}`); 45 | } 46 | return { name, version }; 47 | } 48 | /** 49 | * Resolve a package specifier to a static version 50 | */ 51 | function resolvePackageVersion(rushCommonFolder, { name, version }) { 52 | if (!version) { 53 | version = '*'; // If no version is specified, use the latest version 54 | } 55 | if (version.match(/^[a-zA-Z0-9\-\+\.]+$/)) { 56 | // If the version contains only characters that we recognize to be used in static version specifiers, 57 | // pass the version through 58 | return version; 59 | } 60 | else { 61 | // version resolves to 62 | try { 63 | const rushTempFolder = ensureAndJoinPath(rushCommonFolder, 'temp'); 64 | const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush'); 65 | syncNpmrc(sourceNpmrcFolder, rushTempFolder); 66 | const npmPath = getNpmPath(); 67 | // This returns something that looks like: 68 | // @microsoft/rush@3.0.0 '3.0.0' 69 | // @microsoft/rush@3.0.1 '3.0.1' 70 | // ... 71 | // @microsoft/rush@3.0.20 '3.0.20' 72 | // 73 | const npmVersionSpawnResult = childProcess.spawnSync(npmPath, ['view', `${name}@${version}`, 'version', '--no-update-notifier'], { 74 | cwd: rushTempFolder, 75 | stdio: [] 76 | }); 77 | if (npmVersionSpawnResult.status !== 0) { 78 | throw new Error(`"npm view" returned error code ${npmVersionSpawnResult.status}`); 79 | } 80 | const npmViewVersionOutput = npmVersionSpawnResult.stdout.toString(); 81 | const versionLines = npmViewVersionOutput.split('\n').filter((line) => !!line); 82 | const latestVersion = versionLines[versionLines.length - 1]; 83 | if (!latestVersion) { 84 | throw new Error('No versions found for the specified version range.'); 85 | } 86 | const versionMatches = latestVersion.match(/^.+\s\'(.+)\'$/); 87 | if (!versionMatches) { 88 | throw new Error(`Invalid npm output ${latestVersion}`); 89 | } 90 | return versionMatches[1]; 91 | } 92 | catch (e) { 93 | throw new Error(`Unable to resolve version ${version} of package ${name}: ${e}`); 94 | } 95 | } 96 | } 97 | let _npmPath = undefined; 98 | /** 99 | * Get the absolute path to the npm executable 100 | */ 101 | function getNpmPath() { 102 | if (!_npmPath) { 103 | try { 104 | if (os.platform() === 'win32') { 105 | // We're on Windows 106 | const whereOutput = childProcess.execSync('where npm', { stdio: [] }).toString(); 107 | const lines = whereOutput.split(os.EOL).filter((line) => !!line); 108 | // take the last result, we are looking for a .cmd command 109 | // see https://github.com/Microsoft/web-build-tools/issues/759 110 | _npmPath = lines[lines.length - 1]; 111 | } 112 | else { 113 | // We aren't on Windows - assume we're on *NIX or Darwin 114 | _npmPath = childProcess.execSync('which npm', { stdio: [] }).toString(); 115 | } 116 | } 117 | catch (e) { 118 | throw new Error(`Unable to determine the path to the NPM tool: ${e}`); 119 | } 120 | _npmPath = _npmPath.trim(); 121 | if (!fs.existsSync(_npmPath)) { 122 | throw new Error('The NPM executable does not exist'); 123 | } 124 | } 125 | return _npmPath; 126 | } 127 | exports.getNpmPath = getNpmPath; 128 | let _rushJsonFolder; 129 | /** 130 | * Find the absolute path to the folder containing rush.json 131 | */ 132 | function findRushJsonFolder() { 133 | if (!_rushJsonFolder) { 134 | let basePath = __dirname; 135 | let tempPath = __dirname; 136 | do { 137 | const testRushJsonPath = path.join(basePath, exports.RUSH_JSON_FILENAME); 138 | if (fs.existsSync(testRushJsonPath)) { 139 | _rushJsonFolder = basePath; 140 | break; 141 | } 142 | else { 143 | basePath = tempPath; 144 | } 145 | } while (basePath !== (tempPath = path.dirname(basePath))); // Exit the loop when we hit the disk root 146 | if (!_rushJsonFolder) { 147 | throw new Error('Unable to find rush.json.'); 148 | } 149 | } 150 | return _rushJsonFolder; 151 | } 152 | exports.findRushJsonFolder = findRushJsonFolder; 153 | /** 154 | * Create missing directories under the specified base directory, and return the resolved directory. 155 | * 156 | * Does not support "." or ".." path segments. 157 | * Assumes the baseFolder exists. 158 | */ 159 | function ensureAndJoinPath(baseFolder, ...pathSegments) { 160 | let joinedPath = baseFolder; 161 | try { 162 | for (let pathSegment of pathSegments) { 163 | pathSegment = pathSegment.replace(/[\\\/]/g, '+'); 164 | joinedPath = path.join(joinedPath, pathSegment); 165 | if (!fs.existsSync(joinedPath)) { 166 | fs.mkdirSync(joinedPath); 167 | } 168 | } 169 | } 170 | catch (e) { 171 | throw new Error(`Error building local installation folder (${path.join(baseFolder, ...pathSegments)}): ${e}`); 172 | } 173 | return joinedPath; 174 | } 175 | /** 176 | * As a workaround, _syncNpmrc() copies the .npmrc file to the target folder, and also trims 177 | * unusable lines from the .npmrc file. If the source .npmrc file not exist, then _syncNpmrc() 178 | * will delete an .npmrc that is found in the target folder. 179 | * 180 | * Why are we trimming the .npmrc lines? NPM allows environment variables to be specified in 181 | * the .npmrc file to provide different authentication tokens for different registry. 182 | * However, if the environment variable is undefined, it expands to an empty string, which 183 | * produces a valid-looking mapping with an invalid URL that causes an error. Instead, 184 | * we'd prefer to skip that line and continue looking in other places such as the user's 185 | * home directory. 186 | * 187 | * IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities._syncNpmrc() 188 | */ 189 | function syncNpmrc(sourceNpmrcFolder, targetNpmrcFolder) { 190 | const sourceNpmrcPath = path.join(sourceNpmrcFolder, '.npmrc'); 191 | const targetNpmrcPath = path.join(targetNpmrcFolder, '.npmrc'); 192 | try { 193 | if (fs.existsSync(sourceNpmrcPath)) { 194 | let npmrcFileLines = fs.readFileSync(sourceNpmrcPath).toString().split('\n'); 195 | npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim()); 196 | const resultLines = []; 197 | // Trim out lines that reference environment variables that aren't defined 198 | for (const line of npmrcFileLines) { 199 | // This finds environment variable tokens that look like "${VAR_NAME}" 200 | const regex = /\$\{([^\}]+)\}/g; 201 | const environmentVariables = line.match(regex); 202 | let lineShouldBeTrimmed = false; 203 | if (environmentVariables) { 204 | for (const token of environmentVariables) { 205 | // Remove the leading "${" and the trailing "}" from the token 206 | const environmentVariableName = token.substring(2, token.length - 1); 207 | if (!process.env[environmentVariableName]) { 208 | lineShouldBeTrimmed = true; 209 | break; 210 | } 211 | } 212 | } 213 | if (lineShouldBeTrimmed) { 214 | // Example output: 215 | // "; MISSING ENVIRONMENT VARIABLE: //my-registry.com/npm/:_authToken=${MY_AUTH_TOKEN}" 216 | resultLines.push('; MISSING ENVIRONMENT VARIABLE: ' + line); 217 | } 218 | else { 219 | resultLines.push(line); 220 | } 221 | } 222 | fs.writeFileSync(targetNpmrcPath, resultLines.join(os.EOL)); 223 | } 224 | else if (fs.existsSync(targetNpmrcPath)) { 225 | // If the source .npmrc doesn't exist and there is one in the target, delete the one in the target 226 | fs.unlinkSync(targetNpmrcPath); 227 | } 228 | } 229 | catch (e) { 230 | throw new Error(`Error syncing .npmrc file: ${e}`); 231 | } 232 | } 233 | /** 234 | * Detects if the package in the specified directory is installed 235 | */ 236 | function isPackageAlreadyInstalled(packageInstallFolder) { 237 | try { 238 | const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME); 239 | if (!fs.existsSync(flagFilePath)) { 240 | return false; 241 | } 242 | const fileContents = fs.readFileSync(flagFilePath).toString(); 243 | return fileContents.trim() === process.version; 244 | } 245 | catch (e) { 246 | return false; 247 | } 248 | } 249 | /** 250 | * Removes the following files and directories under the specified folder path: 251 | * - installed.flag 252 | * - 253 | * - node_modules 254 | */ 255 | function cleanInstallFolder(rushCommonFolder, packageInstallFolder) { 256 | try { 257 | const flagFile = path.resolve(packageInstallFolder, INSTALLED_FLAG_FILENAME); 258 | if (fs.existsSync(flagFile)) { 259 | fs.unlinkSync(flagFile); 260 | } 261 | const packageLockFile = path.resolve(packageInstallFolder, 'package-lock.json'); 262 | if (fs.existsSync(packageLockFile)) { 263 | fs.unlinkSync(packageLockFile); 264 | } 265 | const nodeModulesFolder = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME); 266 | if (fs.existsSync(nodeModulesFolder)) { 267 | const rushRecyclerFolder = ensureAndJoinPath(rushCommonFolder, 'temp', 'rush-recycler', `install-run-${Date.now().toString()}`); 268 | fs.renameSync(nodeModulesFolder, rushRecyclerFolder); 269 | } 270 | } 271 | catch (e) { 272 | throw new Error(`Error cleaning the package install folder (${packageInstallFolder}): ${e}`); 273 | } 274 | } 275 | function createPackageJson(packageInstallFolder, name, version) { 276 | try { 277 | const packageJsonContents = { 278 | 'name': 'ci-rush', 279 | 'version': '0.0.0', 280 | 'dependencies': { 281 | [name]: version 282 | }, 283 | 'description': 'DON\'T WARN', 284 | 'repository': 'DON\'T WARN', 285 | 'license': 'MIT' 286 | }; 287 | const packageJsonPath = path.join(packageInstallFolder, PACKAGE_JSON_FILENAME); 288 | fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContents, undefined, 2)); 289 | } 290 | catch (e) { 291 | throw new Error(`Unable to create package.json: ${e}`); 292 | } 293 | } 294 | /** 295 | * Run "npm install" in the package install folder. 296 | */ 297 | function installPackage(packageInstallFolder, name, version) { 298 | try { 299 | console.log(`Installing ${name}...`); 300 | const npmPath = getNpmPath(); 301 | const result = childProcess.spawnSync(npmPath, ['install'], { 302 | stdio: 'inherit', 303 | cwd: packageInstallFolder, 304 | env: process.env 305 | }); 306 | if (result.status !== 0) { 307 | throw new Error('"npm install" encountered an error'); 308 | } 309 | console.log(`Successfully installed ${name}@${version}`); 310 | } 311 | catch (e) { 312 | throw new Error(`Unable to install package: ${e}`); 313 | } 314 | } 315 | /** 316 | * Get the ".bin" path for the package. 317 | */ 318 | function getBinPath(packageInstallFolder, binName) { 319 | const binFolderPath = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin'); 320 | const resolvedBinName = (os.platform() === 'win32') ? `${binName}.cmd` : binName; 321 | return path.resolve(binFolderPath, resolvedBinName); 322 | } 323 | /** 324 | * Write a flag file to the package's install directory, signifying that the install was successful. 325 | */ 326 | function writeFlagFile(packageInstallFolder) { 327 | try { 328 | const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME); 329 | fs.writeFileSync(flagFilePath, process.version); 330 | } 331 | catch (e) { 332 | throw new Error(`Unable to create installed.flag file in ${packageInstallFolder}`); 333 | } 334 | } 335 | function installAndRun(packageName, packageVersion, packageBinName, packageBinArgs) { 336 | const rushJsonFolder = findRushJsonFolder(); 337 | const rushCommonFolder = path.join(rushJsonFolder, 'common'); 338 | const packageInstallFolder = ensureAndJoinPath(rushCommonFolder, 'temp', 'install-run', `${packageName}@${packageVersion}`); 339 | if (!isPackageAlreadyInstalled(packageInstallFolder)) { 340 | // The package isn't already installed 341 | cleanInstallFolder(rushCommonFolder, packageInstallFolder); 342 | const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush'); 343 | syncNpmrc(sourceNpmrcFolder, packageInstallFolder); 344 | createPackageJson(packageInstallFolder, packageName, packageVersion); 345 | installPackage(packageInstallFolder, packageName, packageVersion); 346 | writeFlagFile(packageInstallFolder); 347 | } 348 | const statusMessage = `Invoking "${packageBinName} ${packageBinArgs.join(' ')}"`; 349 | const statusMessageLine = new Array(statusMessage.length + 1).join('-'); 350 | console.log(os.EOL + statusMessage + os.EOL + statusMessageLine + os.EOL); 351 | const binPath = getBinPath(packageInstallFolder, packageBinName); 352 | const result = childProcess.spawnSync(binPath, packageBinArgs, { 353 | stdio: 'inherit', 354 | cwd: process.cwd(), 355 | env: process.env 356 | }); 357 | return result.status; 358 | } 359 | exports.installAndRun = installAndRun; 360 | function runWithErrorAndStatusCode(fn) { 361 | process.exitCode = 1; 362 | try { 363 | const exitCode = fn(); 364 | process.exitCode = exitCode; 365 | } 366 | catch (e) { 367 | console.error(os.EOL + os.EOL + e.toString() + os.EOL + os.EOL); 368 | } 369 | } 370 | exports.runWithErrorAndStatusCode = runWithErrorAndStatusCode; 371 | function run() { 372 | const [nodePath, /* Ex: /bin/node */ scriptPath, /* /repo/common/scripts/install-run-rush.js */ rawPackageSpecifier, /* qrcode@^1.2.0 */ packageBinName, /* qrcode */ ...packageBinArgs /* [-f, myproject/lib] */] = process.argv; 373 | if (!nodePath) { 374 | throw new Error('Unexpected exception: could not detect node path'); 375 | } 376 | if (path.basename(scriptPath).toLowerCase() !== 'install-run.js') { 377 | // If install-run.js wasn't directly invoked, don't execute the rest of this function. Return control 378 | // to the script that (presumably) imported this file 379 | return; 380 | } 381 | if (process.argv.length < 4) { 382 | console.log('Usage: install-run.js @ [args...]'); 383 | console.log('Example: install-run.js qrcode@1.2.2 qrcode https://rushjs.io'); 384 | process.exit(1); 385 | } 386 | runWithErrorAndStatusCode(() => { 387 | const rushJsonFolder = findRushJsonFolder(); 388 | const rushCommonFolder = ensureAndJoinPath(rushJsonFolder, 'common'); 389 | const packageSpecifier = parsePackageSpecifier(rawPackageSpecifier); 390 | const name = packageSpecifier.name; 391 | const version = resolvePackageVersion(rushCommonFolder, packageSpecifier); 392 | if (packageSpecifier.version !== version) { 393 | console.log(`Resolved to ${name}@${version}`); 394 | } 395 | return installAndRun(name, version, packageBinName, packageBinArgs); 396 | }); 397 | } 398 | run(); 399 | //# sourceMappingURL=install-run.js.map -------------------------------------------------------------------------------- /packages/hayspec-cli/CHANGELOG.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hayspec/cli", 3 | "entries": [ 4 | { 5 | "version": "0.10.2", 6 | "tag": "@hayspec/cli_v0.10.2", 7 | "date": "Wed, 07 Apr 2021 09:51:28 GMT", 8 | "comments": {} 9 | }, 10 | { 11 | "version": "0.10.1", 12 | "tag": "@hayspec/cli_v0.10.1", 13 | "date": "Wed, 07 Apr 2021 09:51:23 GMT", 14 | "comments": {} 15 | }, 16 | { 17 | "version": "0.10.0", 18 | "tag": "@hayspec/cli_v0.10.0", 19 | "date": "Mon, 04 May 2020 09:30:44 GMT", 20 | "comments": {} 21 | }, 22 | { 23 | "version": "0.9.0", 24 | "tag": "@hayspec/cli_v0.9.0", 25 | "date": "Thu, 26 Sep 2019 17:35:19 GMT", 26 | "comments": {} 27 | }, 28 | { 29 | "version": "0.8.4", 30 | "tag": "@hayspec/cli_v0.8.4", 31 | "date": "Sat, 13 Jul 2019 01:23:01 GMT", 32 | "comments": {} 33 | }, 34 | { 35 | "version": "0.8.3", 36 | "tag": "@hayspec/cli_v0.8.3", 37 | "date": "Mon, 17 Dec 2018 18:41:04 GMT", 38 | "comments": {} 39 | }, 40 | { 41 | "version": "0.8.2", 42 | "tag": "@hayspec/cli_v0.8.2", 43 | "date": "Mon, 10 Dec 2018 21:06:44 GMT", 44 | "comments": {} 45 | }, 46 | { 47 | "version": "0.8.1", 48 | "tag": "@hayspec/cli_v0.8.1", 49 | "date": "Mon, 10 Dec 2018 20:16:37 GMT", 50 | "comments": {} 51 | }, 52 | { 53 | "version": "0.8.0", 54 | "tag": "@hayspec/cli_v0.8.0", 55 | "date": "Tue, 04 Dec 2018 18:16:48 GMT", 56 | "comments": {} 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /packages/hayspec-cli/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log - @hayspec/cli 2 | 3 | This log was last generated on Wed, 07 Apr 2021 09:51:28 GMT and should not be manually modified. 4 | 5 | ## 0.10.2 6 | Wed, 07 Apr 2021 09:51:28 GMT 7 | 8 | *Version update only* 9 | 10 | ## 0.10.1 11 | Wed, 07 Apr 2021 09:51:23 GMT 12 | 13 | *Version update only* 14 | 15 | ## 0.10.0 16 | Mon, 04 May 2020 09:30:44 GMT 17 | 18 | *Version update only* 19 | 20 | ## 0.9.0 21 | Thu, 26 Sep 2019 17:35:19 GMT 22 | 23 | *Version update only* 24 | 25 | ## 0.8.4 26 | Sat, 13 Jul 2019 01:23:01 GMT 27 | 28 | *Version update only* 29 | 30 | ## 0.8.3 31 | Mon, 17 Dec 2018 18:41:04 GMT 32 | 33 | *Version update only* 34 | 35 | ## 0.8.2 36 | Mon, 10 Dec 2018 21:06:44 GMT 37 | 38 | *Version update only* 39 | 40 | ## 0.8.1 41 | Mon, 10 Dec 2018 20:16:37 GMT 42 | 43 | *Version update only* 44 | 45 | ## 0.8.0 46 | Tue, 04 Dec 2018 18:16:48 GMT 47 | 48 | *Initial release* 49 | 50 | -------------------------------------------------------------------------------- /packages/hayspec-cli/README.md: -------------------------------------------------------------------------------- 1 | ![Build Status](https://travis-ci.org/hayspec/framework.svg?branch=master) [![NPM Version](https://badge.fury.io/js/@hayspec%2Fcli.svg)](https://badge.fury.io/js/%40hayspec%2Fcli) 2 | 3 | This package provides a command-line interface for running automated tests. 4 | -------------------------------------------------------------------------------- /packages/hayspec-cli/bin/hayspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../dist'); 4 | -------------------------------------------------------------------------------- /packages/hayspec-cli/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": ["dist/*"], 3 | "ext": "js,ts" 4 | } 5 | -------------------------------------------------------------------------------- /packages/hayspec-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hayspec/cli", 3 | "version": "0.10.2", 4 | "description": "CLI for Hayspec framework.", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "bin": { 8 | "hayspec": "./bin/hayspec" 9 | }, 10 | "scripts": { 11 | "clean": "rm -Rf ./dist", 12 | "build": "npm run clean; npx tsc", 13 | "test": "npm run build; npx nyc npx ava --verbose" 14 | }, 15 | "hayspec": { 16 | "require": [ 17 | "ts-node/register" 18 | ], 19 | "match": [ 20 | "./src/**/*.hay.*" 21 | ] 22 | }, 23 | "ava": { 24 | "extensions": [ 25 | "ts" 26 | ], 27 | "require": [ 28 | "ts-node/register" 29 | ], 30 | "files": [ 31 | "src/tests/*.test.ts", 32 | "src/tests/**/*.test.ts" 33 | ] 34 | }, 35 | "nyc": { 36 | "exclude": [ 37 | "src/tests" 38 | ] 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/hayspec/framework.git" 43 | }, 44 | "bugs": { 45 | "url": "https://github.com/hayspec/framework/issues" 46 | }, 47 | "homepage": "https://github.com/hayspec/framework#readme", 48 | "keywords": [ 49 | "test", 50 | "testing", 51 | "spec", 52 | "specification", 53 | "hay", 54 | "javascript", 55 | "typescript", 56 | "nodejs", 57 | "tdd", 58 | "unit", 59 | "cli" 60 | ], 61 | "author": "Kristijan Sedlak (Xpepermint)", 62 | "license": "MIT", 63 | "devDependencies": { 64 | "@types/inquirer": "^7.3.1", 65 | "@types/node": "^14.14.37", 66 | "@types/yargs": "^16.0.1", 67 | "ava": "3.15.0", 68 | "nyc": "^15.1.0", 69 | "ts-node": "^9.1.1", 70 | "typescript": "^4.2.3" 71 | }, 72 | "dependencies": { 73 | "@hayspec/init": "^0.10.2", 74 | "@hayspec/reporter": "^0.10.2", 75 | "@hayspec/runner": "^0.10.2", 76 | "@hayspec/spec": "^0.10.2", 77 | "inquirer": "^8.0.0", 78 | "yargs": "^16.2.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/hayspec-cli/src/commands/init.ts: -------------------------------------------------------------------------------- 1 | import * as inquirer from 'inquirer'; 2 | import { Generator } from '@hayspec/init'; 3 | import { Printer } from '@hayspec/reporter'; 4 | import { getConfig } from '../lib/env'; 5 | 6 | /** 7 | * Initializes project directory. 8 | */ 9 | export default async function (argv) { 10 | const { name, description } = getConfig(argv); 11 | const root = process.cwd(); 12 | const printer = new Printer(); 13 | 14 | let answers = {}; 15 | if (!(name && description)) { 16 | answers = await inquirer.prompt([ 17 | { 18 | type: 'input', 19 | name: 'name', 20 | message: "Project name:", 21 | default: name || 'myproject', 22 | }, 23 | { 24 | type: 'input', 25 | name: 'description', 26 | message: "Project description:", 27 | default: description || '.', 28 | }, 29 | ]); 30 | } else { 31 | answers = { name, description }; 32 | } 33 | 34 | const generator = new Generator({ 35 | root, 36 | name: (answers['name'] || process.cwd().split(/\\|\//).reverse()[0]).toLowerCase(), 37 | description: answers['description'], 38 | }); 39 | try { 40 | printer.end(); 41 | await generator.build(); 42 | 43 | printer.end( 44 | printer.indent(1, ''), 45 | `Continue by running the commands below:` 46 | ); 47 | printer.end( 48 | printer.indent(2, ''), 49 | printer.colorize('gray', `$ npm install`) 50 | ); 51 | printer.end( 52 | printer.indent(2, ''), 53 | printer.colorize('gray', `$ npm test`) 54 | ); 55 | printer.end(); 56 | process.exit(0); 57 | 58 | } catch (e) { 59 | console.error(e); 60 | process.exit(2); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/hayspec-cli/src/commands/test.ts: -------------------------------------------------------------------------------- 1 | import { Runner } from '@hayspec/runner'; 2 | import { Spec, Stage } from '@hayspec/spec'; 3 | import { DefaultReporter } from '@hayspec/reporter'; 4 | import { getConfig } from '../lib/env'; 5 | 6 | /** 7 | * Runs tests. 8 | */ 9 | export default async function (argv) { 10 | const { match } = getConfig(argv); 11 | const reporter = new DefaultReporter(); 12 | const stage = new Stage(reporter); 13 | const test = new Spec(stage); 14 | 15 | const runner = new Runner(); 16 | await runner.require(...match); 17 | runner.results.forEach((result) => { 18 | const message = result.file.substr(process.cwd().length + 1); 19 | test.spec(message, result.spec); 20 | }); 21 | 22 | try { 23 | await test.perform(); 24 | process.exit(reporter.failedCount ? 1 : 0); 25 | } catch (e) { 26 | console.log(e); 27 | process.exit(2); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/hayspec-cli/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as yargs from 'yargs'; 2 | import initHandler from './commands/init'; 3 | import testHandler from './commands/test'; 4 | import { getConfig } from './lib/env'; 5 | 6 | /** 7 | * Interface definition. 8 | */ 9 | const { argv } = yargs 10 | .usage('Usage: $0 --help') 11 | .command('init', 'Initializes project directory.', (yargs) => yargs 12 | .option('name', { 13 | string: true, 14 | description: 'Project name', 15 | }) 16 | .option('description', { 17 | string: true, 18 | description: 'Project description', 19 | }), 20 | initHandler) 21 | .command('test', 'Runs tests', (yargs) => yargs 22 | .option('match', { 23 | array: true, 24 | description: 'Match pattern', 25 | }) 26 | .option('require', { 27 | array: true, 28 | description: 'Require dependencies', 29 | }), 30 | testHandler) 31 | .epilog('Copyright © Xpepermint 2018.') 32 | .help() 33 | .version(); 34 | 35 | /** 36 | * Upgrading environment. 37 | */ 38 | getConfig(argv).require.forEach((v) => require(v)); 39 | -------------------------------------------------------------------------------- /packages/hayspec-cli/src/lib/env.ts: -------------------------------------------------------------------------------- 1 | import * as pt from 'path'; 2 | 3 | /** 4 | * Returns package.json data. 5 | */ 6 | export function getPackage() { 7 | try { 8 | return require(pt.join(process.cwd(), 'package.json')) || {}; 9 | } catch (e) { 10 | return {}; 11 | } 12 | } 13 | 14 | /** 15 | * Returns Hayspec options. 16 | */ 17 | export function getConfig(argv?: any) { 18 | const defaults = getPackage()['hayspec'] || {}; 19 | const custom = argv || {}; 20 | return { 21 | name: custom['name'] || defaults['name'] || '', 22 | description: custom['description'] || defaults['description'] || '', 23 | require: custom['require'] || defaults['require'] || [], 24 | match: custom['match'] || defaults['match'] || [], 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/hayspec-cli/src/tests/assets/invalid.hay.ts: -------------------------------------------------------------------------------- 1 | import { Spec } from '@hayspec/spec'; 2 | 3 | const spec = new Spec(); 4 | 5 | spec.test('foo', async (context) => { 6 | context.true(true); 7 | }); 8 | 9 | spec.test('bar', async (context) => { 10 | context.true(false); 11 | context.false(true); 12 | }); 13 | 14 | export default spec; 15 | -------------------------------------------------------------------------------- /packages/hayspec-cli/src/tests/assets/valid.hay.ts: -------------------------------------------------------------------------------- 1 | import { Spec } from '@hayspec/spec'; 2 | 3 | const spec = new Spec(); 4 | 5 | spec.test('foo', async (context) => { 6 | context.true(true); 7 | }); 8 | 9 | spec.test('bar', async (context) => { 10 | context.true(true); 11 | context.true(true); 12 | }); 13 | 14 | export default spec; 15 | -------------------------------------------------------------------------------- /packages/hayspec-cli/src/tests/index.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import * as util from 'util'; 3 | import * as cproc from 'child_process'; 4 | 5 | const exec = util.promisify(cproc.exec); 6 | 7 | test('initializes current folder', async (t) => { 8 | const command = `mkdir -p ./node_modules/.tmp/test; cd ./node_modules/.tmp/test; ../../../bin/hayspec init --name foo --description bar; echo code: $?`; 9 | const { stdout, stderr } = await exec(command); 10 | t.true(stdout.indexOf('Continue by running the commands below:') !== -1); 11 | t.true(stdout.indexOf('code: 0') !== -1); 12 | t.true(stderr === ''); 13 | }); 14 | 15 | test('runs valid tests', async (t) => { 16 | const command = './bin/hayspec test --require ts-node/register --match ./src/tests/assets/**/valid.hay.*; echo code: $?'; 17 | const { stdout, stderr } = await exec(command); 18 | t.true(stdout.indexOf('src/tests/assets/valid.hay.ts') !== -1); 19 | t.true(stdout.indexOf('src/tests/assets/invalid.hay.ts') === -1); 20 | t.true(stdout.indexOf('code: 0') !== -1); 21 | t.true(stderr === ''); 22 | }); 23 | 24 | test('runs invalid tests', async (t) => { 25 | const command = './bin/hayspec test --require ts-node/register --match ./src/tests/assets/**/invalid.hay.*; echo code: $?'; 26 | const { stdout, stderr } = await exec(command); 27 | t.true(stdout.indexOf('src/tests/assets/valid.hay.ts') === -1); 28 | t.true(stdout.indexOf('src/tests/assets/invalid.hay.ts') !== -1); 29 | t.true(stdout.indexOf('code: 1') !== -1); 30 | t.true(stderr === ''); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/hayspec-cli/src/tests/lib/env.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { getConfig } from '../../lib/env'; 3 | 4 | test('method `getConfig` returns package.json hayspec configuration', async (t) => { 5 | t.deepEqual(getConfig(), { 6 | name: '', 7 | description: '', 8 | require: ['ts-node/register'], 9 | match: ['./src/**/*.hay.*'], 10 | }); 11 | }); 12 | 13 | test('method `getConfig` merges reveived configuration', async (t) => { 14 | t.deepEqual(getConfig({ 15 | extension: [], 16 | require: ['bar'], 17 | match: ['foo'], 18 | }), { 19 | name: '', 20 | description: '', 21 | require: ['bar'], 22 | match: ['foo'], 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/hayspec-cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "sourceMap": true, 8 | "outDir": "dist", 9 | "declaration": true 10 | } 11 | } -------------------------------------------------------------------------------- /packages/hayspec-init/CHANGELOG.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hayspec/init", 3 | "entries": [ 4 | { 5 | "version": "0.10.2", 6 | "tag": "@hayspec/init_v0.10.2", 7 | "date": "Wed, 07 Apr 2021 09:51:28 GMT", 8 | "comments": {} 9 | }, 10 | { 11 | "version": "0.10.1", 12 | "tag": "@hayspec/init_v0.10.1", 13 | "date": "Wed, 07 Apr 2021 09:51:23 GMT", 14 | "comments": {} 15 | }, 16 | { 17 | "version": "0.10.0", 18 | "tag": "@hayspec/init_v0.10.0", 19 | "date": "Mon, 04 May 2020 09:30:44 GMT", 20 | "comments": {} 21 | }, 22 | { 23 | "version": "0.9.0", 24 | "tag": "@hayspec/init_v0.9.0", 25 | "date": "Thu, 26 Sep 2019 17:35:19 GMT", 26 | "comments": {} 27 | }, 28 | { 29 | "version": "0.8.4", 30 | "tag": "@hayspec/init_v0.8.4", 31 | "date": "Sat, 13 Jul 2019 01:23:01 GMT", 32 | "comments": {} 33 | }, 34 | { 35 | "version": "0.8.3", 36 | "tag": "@hayspec/init_v0.8.3", 37 | "date": "Mon, 17 Dec 2018 18:41:04 GMT", 38 | "comments": {} 39 | }, 40 | { 41 | "version": "0.8.2", 42 | "tag": "@hayspec/init_v0.8.2", 43 | "date": "Mon, 10 Dec 2018 21:06:44 GMT", 44 | "comments": {} 45 | }, 46 | { 47 | "version": "0.8.1", 48 | "tag": "@hayspec/init_v0.8.1", 49 | "date": "Mon, 10 Dec 2018 20:16:37 GMT", 50 | "comments": {} 51 | }, 52 | { 53 | "version": "0.8.0", 54 | "tag": "@hayspec/init_v0.8.0", 55 | "date": "Tue, 04 Dec 2018 18:16:48 GMT", 56 | "comments": {} 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /packages/hayspec-init/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log - @hayspec/init 2 | 3 | This log was last generated on Wed, 07 Apr 2021 09:51:28 GMT and should not be manually modified. 4 | 5 | ## 0.10.2 6 | Wed, 07 Apr 2021 09:51:28 GMT 7 | 8 | *Version update only* 9 | 10 | ## 0.10.1 11 | Wed, 07 Apr 2021 09:51:23 GMT 12 | 13 | *Version update only* 14 | 15 | ## 0.10.0 16 | Mon, 04 May 2020 09:30:44 GMT 17 | 18 | *Version update only* 19 | 20 | ## 0.9.0 21 | Thu, 26 Sep 2019 17:35:19 GMT 22 | 23 | *Version update only* 24 | 25 | ## 0.8.4 26 | Sat, 13 Jul 2019 01:23:01 GMT 27 | 28 | *Version update only* 29 | 30 | ## 0.8.3 31 | Mon, 17 Dec 2018 18:41:04 GMT 32 | 33 | *Version update only* 34 | 35 | ## 0.8.2 36 | Mon, 10 Dec 2018 21:06:44 GMT 37 | 38 | *Version update only* 39 | 40 | ## 0.8.1 41 | Mon, 10 Dec 2018 20:16:37 GMT 42 | 43 | *Version update only* 44 | 45 | ## 0.8.0 46 | Tue, 04 Dec 2018 18:16:48 GMT 47 | 48 | *Initial release* 49 | 50 | -------------------------------------------------------------------------------- /packages/hayspec-init/README.md: -------------------------------------------------------------------------------- 1 | ![Build Status](https://travis-ci.org/hayspec/framework.svg?branch=master) [![NPM Version](https://badge.fury.io/js/@hayspec%2Finit.svg)](https://badge.fury.io/js/%40hayspec%2Finit) 2 | 3 | This package provides the logic for initializing the project directory. It's included in the Hayspec CLI, but you can use it to do the initialization directly from your NodeJS application. 4 | -------------------------------------------------------------------------------- /packages/hayspec-init/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": ["dist/*"], 3 | "ext": "js,ts" 4 | } 5 | -------------------------------------------------------------------------------- /packages/hayspec-init/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hayspec/init", 3 | "version": "0.10.2", 4 | "description": "Project generator for Hayspec framework.", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "scripts": { 8 | "clean": "rm -Rf ./dist", 9 | "build": "npm run clean; npx tsc", 10 | "test": "npx nyc npx ava --verbose" 11 | }, 12 | "ava": { 13 | "extensions": [ 14 | "ts" 15 | ], 16 | "require": [ 17 | "ts-node/register" 18 | ], 19 | "files": [ 20 | "src/tests/*.test.ts", 21 | "src/tests/**/*.test.ts" 22 | ] 23 | }, 24 | "nyc": { 25 | "extension": [ 26 | ".ts" 27 | ], 28 | "require": [ 29 | "ts-node/register" 30 | ], 31 | "exclude": [ 32 | "src/tests" 33 | ] 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/hayspec/framework.git" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/hayspec/framework/issues" 41 | }, 42 | "homepage": "https://github.com/hayspec/framework#readme", 43 | "keywords": [ 44 | "test", 45 | "testing", 46 | "spec", 47 | "specification", 48 | "hay", 49 | "javascript", 50 | "typescript", 51 | "nodejs", 52 | "init", 53 | "initialize" 54 | ], 55 | "author": "Kristijan Sedlak (Xpepermint)", 56 | "license": "MIT", 57 | "devDependencies": { 58 | "@types/fs-extra": "^9.0.10", 59 | "@types/node": "^14.14.37", 60 | "ava": "3.15.0", 61 | "nyc": "^15.1.0", 62 | "ts-node": "^9.1.1", 63 | "typescript": "^4.2.3" 64 | }, 65 | "dependencies": { 66 | "fs-extra": "^9.1.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/hayspec-init/src/core/generator.ts: -------------------------------------------------------------------------------- 1 | import * as fsx from 'fs-extra'; 2 | import * as path from 'path'; 3 | import * as structure from './structure'; 4 | 5 | /** 6 | * Initializer config recipe. 7 | */ 8 | export interface GeneratorRecipe { 9 | root: string; 10 | name: string; 11 | description: string; 12 | } 13 | 14 | /** 15 | * Project structure initializer. 16 | */ 17 | export class Generator { 18 | protected recipe: GeneratorRecipe; 19 | 20 | /** 21 | * Class constructor. 22 | * @param recipe Initializer config recipe. 23 | */ 24 | public constructor (recipe: GeneratorRecipe) { 25 | this.recipe = recipe; 26 | } 27 | 28 | /** 29 | * Creates project files. 30 | */ 31 | public async build () { 32 | for (const file of structure.files) { 33 | const dest = path.resolve(this.recipe.root, ...file.path); 34 | 35 | const dir = path.dirname(dest); 36 | await fsx.ensureDir(dir); 37 | 38 | const src = file.content 39 | .join('\n') 40 | .replace('{{ name }}', this.recipe.name) 41 | .replace('{{ description }}', this.recipe.description); 42 | await fsx.writeFile(dest, src); 43 | } 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /packages/hayspec-init/src/core/structure.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File recipe interface. 3 | */ 4 | export interface FileRecipe { 5 | path: string[]; 6 | content: string[]; 7 | } 8 | 9 | /** 10 | * Project files. 11 | */ 12 | export const files = [ 13 | { 14 | path: ['.gitignore'], 15 | content: [ 16 | `.DS_Store`, 17 | `.vscode`, 18 | `node_modules`, 19 | `dist`, 20 | ], 21 | }, 22 | { 23 | path: ['.npmignore'], 24 | content: [ 25 | `.DS_Store`, 26 | `.vscode`, 27 | `node_modules`, 28 | ], 29 | }, 30 | { 31 | path: ['package.json'], 32 | content: [ 33 | `{`, 34 | ` "name": "{{ name }}",`, 35 | ` "version": "0.0.0",`, 36 | ` "description": "{{ description }}",`, 37 | ` "scripts": {`, 38 | ` "build": "tsc",`, 39 | ` "prepare": "npm run build",`, 40 | ` "test": "hayspec test"`, 41 | ` },`, 42 | ` "hayspec": {`, 43 | ` "require": [`, 44 | ` "ts-node/register"`, 45 | ` ],`, 46 | ` "match": [`, 47 | ` "./src/tests/**/*.test.ts"`, 48 | ` ]`, 49 | ` },`, 50 | ` "license": "MIT",`, 51 | ` "devDependencies": {`, 52 | ` "@hayspec/cli": "latest",`, 53 | ` "@hayspec/spec": "latest",`, 54 | ` "ts-node": "latest",`, 55 | ` "typescript": "latest"`, 56 | ` }`, 57 | `}`, 58 | ], 59 | }, 60 | { 61 | path: ['src', 'index.ts'], 62 | content: [ 63 | `/**`, 64 | ` * Example function simply returning true.`, 65 | ` */`, 66 | `export function isHay() {`, 67 | ` return true;`, 68 | `}`, 69 | ], 70 | }, 71 | { 72 | path: ['src', 'tests', 'index.test.ts'], 73 | content: [ 74 | `import { Spec } from '@hayspec/spec';`, 75 | `import * as index from '..';`, 76 | ``, 77 | `/**`, 78 | ` * Testing module interface.`, 79 | ` */`, 80 | ``, 81 | `const spec = new Spec();`, 82 | ``, 83 | `spec.test('isHay() returns true', (ctx) => {`, 84 | ` ctx.true(index.isHay());`, 85 | `});`, 86 | ``, 87 | `export default spec;`, 88 | ], 89 | }, 90 | { 91 | path: ['tsconfig.json'], 92 | content: [ 93 | `{`, 94 | ` "compilerOptions": {`, 95 | ` "module": "commonjs",`, 96 | ` "outDir": "dist",`, 97 | ` "target": "es6"`, 98 | ` }`, 99 | `}`, 100 | ], 101 | }, 102 | ] as FileRecipe[]; 103 | -------------------------------------------------------------------------------- /packages/hayspec-init/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core/generator'; 2 | -------------------------------------------------------------------------------- /packages/hayspec-init/src/tests/core/generator.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import * as fsx from 'fs-extra'; 3 | import * as path from 'path'; 4 | import { Generator } from '../..'; 5 | 6 | test('builds project structure', async (t) => { 7 | const root = path.join('node_modules', `.test${Date.now()}`); 8 | const generator = new Generator({ 9 | root: root, 10 | name: '18sb3h301', 11 | description: '8f3nh19831', 12 | }); 13 | await generator.build(); 14 | const pkg = path.join(root, 'package.json'); 15 | const src = await fsx.readFile(pkg); 16 | t.is(src.indexOf('"18sb3h301"') !== 0, true); // replaced variable 17 | t.is(src.indexOf('"8f3nh19831"') !== 0, true); // replaced variable 18 | }); 19 | -------------------------------------------------------------------------------- /packages/hayspec-init/src/tests/core/index.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import * as init from '../..'; 3 | 4 | test('exposes Generator class', async (t) => { 5 | t.true(!!init.Generator); 6 | }); 7 | -------------------------------------------------------------------------------- /packages/hayspec-init/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "sourceMap": true, 8 | "outDir": "dist", 9 | "declaration": true 10 | } 11 | } -------------------------------------------------------------------------------- /packages/hayspec-reporter/CHANGELOG.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hayspec/reporter", 3 | "entries": [ 4 | { 5 | "version": "0.10.2", 6 | "tag": "@hayspec/reporter_v0.10.2", 7 | "date": "Wed, 07 Apr 2021 09:51:28 GMT", 8 | "comments": {} 9 | }, 10 | { 11 | "version": "0.10.1", 12 | "tag": "@hayspec/reporter_v0.10.1", 13 | "date": "Wed, 07 Apr 2021 09:51:23 GMT", 14 | "comments": {} 15 | }, 16 | { 17 | "version": "0.10.0", 18 | "tag": "@hayspec/reporter_v0.10.0", 19 | "date": "Mon, 04 May 2020 09:30:44 GMT", 20 | "comments": {} 21 | }, 22 | { 23 | "version": "0.9.0", 24 | "tag": "@hayspec/reporter_v0.9.0", 25 | "date": "Thu, 26 Sep 2019 17:35:19 GMT", 26 | "comments": {} 27 | }, 28 | { 29 | "version": "0.8.4", 30 | "tag": "@hayspec/reporter_v0.8.4", 31 | "date": "Sat, 13 Jul 2019 01:23:01 GMT", 32 | "comments": {} 33 | }, 34 | { 35 | "version": "0.8.3", 36 | "tag": "@hayspec/reporter_v0.8.3", 37 | "date": "Mon, 17 Dec 2018 18:41:04 GMT", 38 | "comments": {} 39 | }, 40 | { 41 | "version": "0.8.2", 42 | "tag": "@hayspec/reporter_v0.8.2", 43 | "date": "Mon, 10 Dec 2018 21:06:44 GMT", 44 | "comments": {} 45 | }, 46 | { 47 | "version": "0.8.1", 48 | "tag": "@hayspec/reporter_v0.8.1", 49 | "date": "Mon, 10 Dec 2018 20:16:37 GMT", 50 | "comments": {} 51 | }, 52 | { 53 | "version": "0.8.0", 54 | "tag": "@hayspec/reporter_v0.8.0", 55 | "date": "Tue, 04 Dec 2018 18:16:48 GMT", 56 | "comments": {} 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /packages/hayspec-reporter/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log - @hayspec/reporter 2 | 3 | This log was last generated on Wed, 07 Apr 2021 09:51:28 GMT and should not be manually modified. 4 | 5 | ## 0.10.2 6 | Wed, 07 Apr 2021 09:51:28 GMT 7 | 8 | *Version update only* 9 | 10 | ## 0.10.1 11 | Wed, 07 Apr 2021 09:51:23 GMT 12 | 13 | *Version update only* 14 | 15 | ## 0.10.0 16 | Mon, 04 May 2020 09:30:44 GMT 17 | 18 | *Version update only* 19 | 20 | ## 0.9.0 21 | Thu, 26 Sep 2019 17:35:19 GMT 22 | 23 | *Version update only* 24 | 25 | ## 0.8.4 26 | Sat, 13 Jul 2019 01:23:01 GMT 27 | 28 | *Version update only* 29 | 30 | ## 0.8.3 31 | Mon, 17 Dec 2018 18:41:04 GMT 32 | 33 | *Version update only* 34 | 35 | ## 0.8.2 36 | Mon, 10 Dec 2018 21:06:44 GMT 37 | 38 | *Version update only* 39 | 40 | ## 0.8.1 41 | Mon, 10 Dec 2018 20:16:37 GMT 42 | 43 | *Version update only* 44 | 45 | ## 0.8.0 46 | Tue, 04 Dec 2018 18:16:48 GMT 47 | 48 | *Initial release* 49 | 50 | -------------------------------------------------------------------------------- /packages/hayspec-reporter/README.md: -------------------------------------------------------------------------------- 1 | ![Build Status](https://travis-ci.org/hayspec/framework.svg?branch=master) [![NPM Version](https://badge.fury.io/js/@hayspec%2Freporter.svg)](https://badge.fury.io/js/%40hayspec%2Freporter) 2 | 3 | This package provides the default command-line reporter. It's included in the Hayspec CLI. 4 | -------------------------------------------------------------------------------- /packages/hayspec-reporter/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": ["dist/*"], 3 | "ext": "js,ts" 4 | } 5 | -------------------------------------------------------------------------------- /packages/hayspec-reporter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hayspec/reporter", 3 | "version": "0.10.2", 4 | "description": "Reporter for Hayspec framework.", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "scripts": { 8 | "clean": "rm -Rf ./dist", 9 | "build": "npm run clean; npx tsc", 10 | "test": "npx nyc npx ava --verbose" 11 | }, 12 | "ava": { 13 | "extensions": [ 14 | "ts" 15 | ], 16 | "require": [ 17 | "ts-node/register" 18 | ], 19 | "files": [ 20 | "src/tests/*.test.ts", 21 | "src/tests/**/*.test.ts" 22 | ] 23 | }, 24 | "nyc": { 25 | "extension": [ 26 | ".ts" 27 | ], 28 | "require": [ 29 | "ts-node/register" 30 | ], 31 | "exclude": [ 32 | "src/tests" 33 | ] 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/hayspec/framework.git" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/hayspec/framework/issues" 41 | }, 42 | "homepage": "https://github.com/hayspec/framework#readme", 43 | "keywords": [ 44 | "test", 45 | "testing", 46 | "spec", 47 | "specification", 48 | "hay", 49 | "javascript", 50 | "typescript", 51 | "nodejs", 52 | "tdd", 53 | "unit", 54 | "cli" 55 | ], 56 | "author": "Kristijan Sedlak (Xpepermint)", 57 | "license": "MIT", 58 | "devDependencies": { 59 | "@types/node": "^14.14.37", 60 | "ava": "3.15.0", 61 | "nyc": "^15.1.0", 62 | "ts-node": "^9.1.1", 63 | "typescript": "^4.2.3" 64 | }, 65 | "dependencies": { 66 | "@hayspec/spec": "^0.10.2", 67 | "chalk": "^4.1.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/hayspec-reporter/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/printer'; 2 | export * from './reporters/default'; 3 | -------------------------------------------------------------------------------- /packages/hayspec-reporter/src/lib/printer.ts: -------------------------------------------------------------------------------- 1 | import * as chalk from 'chalk'; 2 | 3 | /** 4 | * 5 | */ 6 | export class Printer { 7 | protected muted: boolean; 8 | 9 | /** 10 | * 11 | */ 12 | public constructor({ mute = false } = {}) { 13 | this.muted = mute; 14 | } 15 | 16 | /** 17 | * 18 | */ 19 | public mute() { 20 | this.muted = true; 21 | } 22 | 23 | /** 24 | * 25 | */ 26 | public unmute() { 27 | this.muted = false; 28 | } 29 | 30 | /** 31 | * 32 | */ 33 | public write(...input: any[]) { 34 | if (!this.muted) { 35 | process.stdout.write(input.join('')); 36 | } 37 | } 38 | 39 | /** 40 | * 41 | */ 42 | public end(...input: any[]) { 43 | if (!this.muted) { 44 | this.write(...input, '\n\r'); 45 | } 46 | } 47 | 48 | /** 49 | * 50 | */ 51 | public colorize(color: string, text: string | number) { 52 | return chalk[color](`${text}`); 53 | } 54 | 55 | /** 56 | * 57 | */ 58 | public indent(times: number = 1, text: string | number) { 59 | return `${Array(times * 3).join(' ')}${text}`; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /packages/hayspec-reporter/src/reporters/default.ts: -------------------------------------------------------------------------------- 1 | import { Reporter, SpecStartNote, SpecEndNote, TestStartNote, TestEndNote, AssertionNote } from '@hayspec/spec'; 2 | import { Printer } from '../lib/printer'; 3 | 4 | /** 5 | * 6 | */ 7 | export class DefaultReporter extends Reporter { 8 | protected printer: Printer; 9 | protected assertionResults: boolean[] = []; 10 | public passedCount: number = 0; 11 | public skippedCount: number = 0; 12 | public failedCount: number = 0; 13 | 14 | /** 15 | * 16 | */ 17 | public constructor({ mute = false } = {}) { 18 | super(); 19 | this.printer = new Printer({ mute }); 20 | } 21 | 22 | /** 23 | * 24 | */ 25 | public reset() { 26 | super.reset(); 27 | this.passedCount = 0; 28 | this.skippedCount = 0; 29 | this.failedCount = 0; 30 | } 31 | 32 | /** 33 | * 34 | */ 35 | protected onBegin() { 36 | this.printer.end(); 37 | } 38 | 39 | /** 40 | * 41 | */ 42 | protected onEnd() { 43 | this.printer.end(); 44 | 45 | const messages = []; 46 | if (this.passedCount) { 47 | messages.push( 48 | this.printer.indent(1, ''), 49 | this.printer.colorize('greenBright', this.passedCount), 50 | ' passing', 51 | ); 52 | } 53 | if (this.skippedCount) { 54 | messages.push( 55 | this.printer.indent(1, ''), 56 | this.printer.colorize('yellowBright', this.skippedCount), 57 | ' skipped', 58 | ); 59 | } 60 | if (this.failedCount) { 61 | messages.push( 62 | this.printer.indent(1, ''), 63 | this.printer.colorize('redBright', this.failedCount), 64 | ' failed', 65 | ); 66 | } 67 | if (messages.length) { 68 | this.printer.end(...messages); 69 | } 70 | 71 | this.printer.end(); 72 | } 73 | 74 | /** 75 | * 76 | */ 77 | protected onSpecStartNote(note: SpecStartNote) { 78 | this.printer.write( 79 | this.printer.indent(this.level, ''), 80 | note.message, 81 | ); 82 | this.printer.end(); 83 | } 84 | 85 | /** 86 | * 87 | */ 88 | protected onTestStartNote(note: TestStartNote) { 89 | const skipped = !note.perform; 90 | if (skipped) { 91 | this.skippedCount++; 92 | } 93 | 94 | this.printer.write( 95 | this.printer.indent(this.level, ''), 96 | this.printer.colorize('gray', note.message), 97 | ' ', 98 | skipped ? this.printer.colorize('yellowBright', '⚑ ') : '', 99 | ); 100 | } 101 | 102 | /** 103 | * 104 | */ 105 | protected onTestEndNote(note: TestEndNote) { 106 | const passing = this.assertionResults.indexOf(false) === -1; 107 | if (passing) { 108 | this.passedCount++; 109 | } else { 110 | this.failedCount++; 111 | } 112 | this.assertionResults = []; 113 | 114 | const color = this.getDurationColor(note.duration); 115 | if (color) { 116 | this.printer.write( 117 | this.printer.colorize(color, `${note.duration}ms`) 118 | ); 119 | } 120 | 121 | this.printer.end(); 122 | } 123 | 124 | /** 125 | * 126 | */ 127 | protected onAssertionNote(note: AssertionNote) { 128 | this.assertionResults.push(note.success); 129 | 130 | const passing = note.success; 131 | if (passing) { 132 | this.printer.write( 133 | this.printer.colorize('greenBright', '⚑ ') 134 | ); 135 | } else { 136 | this.printer.write( 137 | this.printer.colorize('redBright', '⚑ ') 138 | ); 139 | } 140 | } 141 | 142 | /** 143 | * 144 | */ 145 | protected getDurationColor(duration: number) { 146 | if (duration > 1000) { 147 | return 'redBright'; 148 | } else if (duration > 500) { 149 | return 'yellowBright'; 150 | } else { 151 | return null; 152 | } 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /packages/hayspec-reporter/src/tests/index.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | test.skip('foo', async (t) => { 4 | t.pass(); 5 | }); 6 | -------------------------------------------------------------------------------- /packages/hayspec-reporter/src/tests/reporters/default.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | test.skip('foo', async (t) => { 4 | t.pass(); 5 | }); 6 | 7 | 8 | // import { Spec } from '@hayspec/spec'; 9 | // import { DefaultReporter } from '../../reporters/default'; 10 | 11 | // const reporter = new DefaultReporter(); 12 | 13 | // const colors = new Spec(); 14 | // colors.test('correctly checks all the supported interfaces', async (context, stage) => { 15 | // context.is(true, true); 16 | // }); 17 | // colors.test('returns correct balanceOf after mint', async (context, stage) => { 18 | // context.is(true, true); 19 | // context.is(true, true); 20 | // context.is(true, false); 21 | // }); 22 | 23 | // export const weights = new Spec(); 24 | // weights.test('throws when trying to mint 2 NFTs with the same claim', async (context, stage) => { 25 | // context.is(true, true); 26 | // }); 27 | // weights.spec('Contract: NFTokenMock', colors); 28 | // weights.test('throws when trying to mint NFT to 0x0 address', async (context, stage) => { 29 | // context.is(true, true); 30 | // context.is(true, true); 31 | // context.is(true, true); 32 | // }); 33 | 34 | // export const base = new Spec(); 35 | // base.test('throws when trying to get approval of non-existing NFT id', async (context, stage) => { 36 | // context.is(true, true); 37 | // }); 38 | // base.skip('throws when trying to approve NFT ID which it does not own', async (context, stage) => { 39 | // context.is(true, true); 40 | // }); 41 | // base.spec('Contract: NFTokenSpec', weights); 42 | // base.test('throws when trying to approve NFT ID which it already owns', async (context, stage) => { 43 | // context.is(true, true); 44 | // }); 45 | 46 | // base.stage.reporter = reporter; 47 | // base.perform(); 48 | -------------------------------------------------------------------------------- /packages/hayspec-reporter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "sourceMap": true, 8 | "outDir": "dist", 9 | "declaration": true, 10 | "lib": ["es2015.promise", "es2015.iterable", "es5"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/hayspec-runner/CHANGELOG.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hayspec/runner", 3 | "entries": [ 4 | { 5 | "version": "0.10.2", 6 | "tag": "@hayspec/runner_v0.10.2", 7 | "date": "Wed, 07 Apr 2021 09:51:28 GMT", 8 | "comments": {} 9 | }, 10 | { 11 | "version": "0.10.1", 12 | "tag": "@hayspec/runner_v0.10.1", 13 | "date": "Wed, 07 Apr 2021 09:51:23 GMT", 14 | "comments": {} 15 | }, 16 | { 17 | "version": "0.10.0", 18 | "tag": "@hayspec/runner_v0.10.0", 19 | "date": "Mon, 04 May 2020 09:30:44 GMT", 20 | "comments": {} 21 | }, 22 | { 23 | "version": "0.9.0", 24 | "tag": "@hayspec/runner_v0.9.0", 25 | "date": "Thu, 26 Sep 2019 17:35:19 GMT", 26 | "comments": {} 27 | }, 28 | { 29 | "version": "0.8.4", 30 | "tag": "@hayspec/runner_v0.8.4", 31 | "date": "Sat, 13 Jul 2019 01:23:01 GMT", 32 | "comments": {} 33 | }, 34 | { 35 | "version": "0.8.3", 36 | "tag": "@hayspec/runner_v0.8.3", 37 | "date": "Mon, 17 Dec 2018 18:41:04 GMT", 38 | "comments": {} 39 | }, 40 | { 41 | "version": "0.8.2", 42 | "tag": "@hayspec/runner_v0.8.2", 43 | "date": "Mon, 10 Dec 2018 21:06:44 GMT", 44 | "comments": {} 45 | }, 46 | { 47 | "version": "0.8.1", 48 | "tag": "@hayspec/runner_v0.8.1", 49 | "date": "Mon, 10 Dec 2018 20:16:37 GMT", 50 | "comments": {} 51 | }, 52 | { 53 | "version": "0.8.0", 54 | "tag": "@hayspec/runner_v0.8.0", 55 | "date": "Tue, 04 Dec 2018 18:16:48 GMT", 56 | "comments": {} 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /packages/hayspec-runner/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log - @hayspec/runner 2 | 3 | This log was last generated on Wed, 07 Apr 2021 09:51:28 GMT and should not be manually modified. 4 | 5 | ## 0.10.2 6 | Wed, 07 Apr 2021 09:51:28 GMT 7 | 8 | *Version update only* 9 | 10 | ## 0.10.1 11 | Wed, 07 Apr 2021 09:51:23 GMT 12 | 13 | *Version update only* 14 | 15 | ## 0.10.0 16 | Mon, 04 May 2020 09:30:44 GMT 17 | 18 | *Version update only* 19 | 20 | ## 0.9.0 21 | Thu, 26 Sep 2019 17:35:19 GMT 22 | 23 | *Version update only* 24 | 25 | ## 0.8.4 26 | Sat, 13 Jul 2019 01:23:01 GMT 27 | 28 | *Version update only* 29 | 30 | ## 0.8.3 31 | Mon, 17 Dec 2018 18:41:04 GMT 32 | 33 | *Version update only* 34 | 35 | ## 0.8.2 36 | Mon, 10 Dec 2018 21:06:44 GMT 37 | 38 | *Version update only* 39 | 40 | ## 0.8.1 41 | Mon, 10 Dec 2018 20:16:37 GMT 42 | 43 | *Version update only* 44 | 45 | ## 0.8.0 46 | Tue, 04 Dec 2018 18:16:48 GMT 47 | 48 | *Initial release* 49 | 50 | -------------------------------------------------------------------------------- /packages/hayspec-runner/README.md: -------------------------------------------------------------------------------- 1 | ![Build Status](https://travis-ci.org/hayspec/framework.svg?branch=master) [![NPM Version](https://badge.fury.io/js/@hayspec%2Frunner.svg)](https://badge.fury.io/js/%40hayspec%2Frunner) 2 | 3 | This package provides the logic for discovering and performing test files. It's included in the Hayspec CLI, but you can use it to run tests directly from your NodeJS application. 4 | 5 | ```ts 6 | import { Spec } from '@hayspec/spec'; 7 | import { Runner } from '@hayspec/runner'; 8 | 9 | const runner = new Runner(); 10 | runner.require('./foo/**/*.test.js', '!./foo/**/foo.test.js'); 11 | runner.require('./bar/*.hay.js'); 12 | 13 | const spec = new Spec(); 14 | runner.specs.forEach((folder, spec) => { 15 | spec.spec(filder, spec); 16 | }); 17 | spec.perform(); 18 | ``` 19 | -------------------------------------------------------------------------------- /packages/hayspec-runner/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": ["dist/*"], 3 | "ext": "js,ts" 4 | } 5 | -------------------------------------------------------------------------------- /packages/hayspec-runner/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hayspec/runner", 3 | "version": "0.10.2", 4 | "description": "Tests runner for Hayspec framework.", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "scripts": { 8 | "clean": "rm -Rf ./dist", 9 | "build": "npm run clean; npx tsc", 10 | "test": "npx nyc npx ava --verbose" 11 | }, 12 | "ava": { 13 | "extensions": [ 14 | "ts" 15 | ], 16 | "require": [ 17 | "ts-node/register" 18 | ], 19 | "files": [ 20 | "src/tests/*.test.ts", 21 | "src/tests/**/*.test.ts" 22 | ] 23 | }, 24 | "nyc": { 25 | "extension": [ 26 | ".ts" 27 | ], 28 | "require": [ 29 | "ts-node/register" 30 | ], 31 | "exclude": [ 32 | "src/tests" 33 | ] 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/hayspec/framework.git" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/hayspec/framework/issues" 41 | }, 42 | "homepage": "https://github.com/hayspec/framework#readme", 43 | "keywords": [ 44 | "test", 45 | "testing", 46 | "spec", 47 | "specification", 48 | "hay", 49 | "javascript", 50 | "typescript", 51 | "nodejs", 52 | "tdd", 53 | "unit", 54 | "cli" 55 | ], 56 | "author": "Kristijan Sedlak (Xpepermint)", 57 | "license": "MIT", 58 | "devDependencies": { 59 | "@types/node": "^14.14.37", 60 | "ava": "3.15.0", 61 | "nyc": "^15.1.0", 62 | "ts-node": "^9.1.1", 63 | "typescript": "^4.2.3" 64 | }, 65 | "dependencies": { 66 | "@hayspec/spec": "^0.10.2", 67 | "fast-glob": "^3.2.5" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/hayspec-runner/src/core/runner.ts: -------------------------------------------------------------------------------- 1 | import { Spec } from '@hayspec/spec'; 2 | import * as glob from 'fast-glob'; 3 | 4 | /** 5 | * 6 | */ 7 | export interface RunnerResult { 8 | file: string; 9 | spec: Spec; 10 | } 11 | 12 | /** 13 | * 14 | */ 15 | export interface RunnerOptions { 16 | cwd?: string; 17 | deep?: number; 18 | dot?: boolean; 19 | } 20 | 21 | /** 22 | * 23 | */ 24 | export class Runner { 25 | protected options: RunnerOptions; 26 | protected onlyEnabled: boolean = false; 27 | public results: RunnerResult[] = []; 28 | 29 | /** 30 | * 31 | */ 32 | public constructor(options?: RunnerOptions) { 33 | this.options = { 34 | cwd: process.cwd(), 35 | deep: Infinity, 36 | dot: false, 37 | ...options, 38 | }; 39 | } 40 | 41 | /** 42 | * 43 | */ 44 | public async require(...patterns: string[]) { 45 | const options = { 46 | absolute: true, 47 | ...this.options, 48 | }; 49 | const files = await glob(patterns, options) as string[]; 50 | 51 | files.forEach((file) => { 52 | const spec = this.loadSpec(file); 53 | 54 | if (!spec) { 55 | return; 56 | } 57 | else if (spec.spec.hasOnly() && !this.onlyEnabled) { 58 | this.onlyEnabled = true; 59 | this.results = []; 60 | } 61 | 62 | if (this.onlyEnabled && spec.spec.hasOnly()) { 63 | this.results.push(spec); 64 | } 65 | else if (!this.onlyEnabled) { 66 | this.results.push(spec); 67 | } 68 | 69 | }); 70 | } 71 | 72 | /** 73 | * 74 | */ 75 | public clear () { 76 | this.results = []; 77 | return this; 78 | } 79 | 80 | /** 81 | * 82 | * NOTE: Due to different NPM package managers, the `instanceof` check my be 83 | * inconsistent thus the function checks for presence of the `test` method. 84 | */ 85 | protected loadSpec(file: string) { 86 | const spec = require(file); 87 | 88 | if (spec instanceof Spec) { 89 | return { file, spec }; 90 | } else if (spec.default instanceof Spec) { 91 | return { file, spec: spec.default }; 92 | } 93 | else { 94 | return null; 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /packages/hayspec-runner/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core/runner'; 2 | -------------------------------------------------------------------------------- /packages/hayspec-runner/src/tests/assets/all/ok.all.ts: -------------------------------------------------------------------------------- 1 | import { Spec } from '@hayspec/spec'; 2 | 3 | export default new Spec(); 4 | -------------------------------------------------------------------------------- /packages/hayspec-runner/src/tests/assets/all/sub/err.all.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hayspec/framework/4817ccf20a1bdccac91c2ae0b2f16551d5a621c1/packages/hayspec-runner/src/tests/assets/all/sub/err.all.ts -------------------------------------------------------------------------------- /packages/hayspec-runner/src/tests/assets/all/sub/ok.all.ts: -------------------------------------------------------------------------------- 1 | import { Spec } from '@hayspec/spec'; 2 | 3 | export default new Spec(); 4 | -------------------------------------------------------------------------------- /packages/hayspec-runner/src/tests/assets/only/err.only.ts: -------------------------------------------------------------------------------- 1 | import { Spec } from '@hayspec/spec'; 2 | 3 | const spec = new Spec(); 4 | 5 | spec.test('hasOnly() returns true', async (ctx) => { 6 | await ctx.sleep(1000); 7 | }); 8 | 9 | export default spec; 10 | -------------------------------------------------------------------------------- /packages/hayspec-runner/src/tests/assets/only/ok.only.ts: -------------------------------------------------------------------------------- 1 | import { Spec } from '@hayspec/spec'; 2 | 3 | const spec = new Spec(); 4 | 5 | spec.only('hasOnly() returns true', async (ctx) => { 6 | await ctx.sleep(1000); 7 | }); 8 | 9 | export default spec; 10 | -------------------------------------------------------------------------------- /packages/hayspec-runner/src/tests/runner.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Runner } from '..'; 3 | 4 | test('method `require` loads spec files based on pattern', async (t) => { 5 | const runner = new Runner(); 6 | await runner.require('./src/tests/assets/**/*.all.ts'); 7 | t.is(runner.results.length, 2); 8 | }); 9 | 10 | test('method `require` loads files with only method', async (t) => { 11 | const runner = new Runner(); 12 | await runner.require('./src/tests/assets/**/*.only.ts'); 13 | t.is(runner.results.length, 1); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/hayspec-runner/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "sourceMap": true, 8 | "outDir": "dist", 9 | "declaration": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/hayspec-spec/CHANGELOG.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hayspec/spec", 3 | "entries": [ 4 | { 5 | "version": "0.10.2", 6 | "tag": "@hayspec/spec_v0.10.2", 7 | "date": "Wed, 07 Apr 2021 09:51:28 GMT", 8 | "comments": {} 9 | }, 10 | { 11 | "version": "0.10.1", 12 | "tag": "@hayspec/spec_v0.10.1", 13 | "date": "Wed, 07 Apr 2021 09:51:23 GMT", 14 | "comments": {} 15 | }, 16 | { 17 | "version": "0.10.0", 18 | "tag": "@hayspec/spec_v0.10.0", 19 | "date": "Mon, 04 May 2020 09:30:44 GMT", 20 | "comments": {} 21 | }, 22 | { 23 | "version": "0.9.0", 24 | "tag": "@hayspec/spec_v0.9.0", 25 | "date": "Thu, 26 Sep 2019 17:35:19 GMT", 26 | "comments": {} 27 | }, 28 | { 29 | "version": "0.8.4", 30 | "tag": "@hayspec/spec_v0.8.4", 31 | "date": "Sat, 13 Jul 2019 01:23:01 GMT", 32 | "comments": {} 33 | }, 34 | { 35 | "version": "0.8.3", 36 | "tag": "@hayspec/spec_v0.8.3", 37 | "date": "Mon, 17 Dec 2018 18:41:04 GMT", 38 | "comments": {} 39 | }, 40 | { 41 | "version": "0.8.2", 42 | "tag": "@hayspec/spec_v0.8.2", 43 | "date": "Mon, 10 Dec 2018 21:06:44 GMT", 44 | "comments": {} 45 | }, 46 | { 47 | "version": "0.8.1", 48 | "tag": "@hayspec/spec_v0.8.1", 49 | "date": "Mon, 10 Dec 2018 20:16:37 GMT", 50 | "comments": {} 51 | }, 52 | { 53 | "version": "0.8.0", 54 | "tag": "@hayspec/spec_v0.8.0", 55 | "date": "Tue, 04 Dec 2018 18:16:48 GMT", 56 | "comments": {} 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /packages/hayspec-spec/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log - @hayspec/spec 2 | 3 | This log was last generated on Wed, 07 Apr 2021 09:51:28 GMT and should not be manually modified. 4 | 5 | ## 0.10.2 6 | Wed, 07 Apr 2021 09:51:28 GMT 7 | 8 | *Version update only* 9 | 10 | ## 0.10.1 11 | Wed, 07 Apr 2021 09:51:23 GMT 12 | 13 | *Version update only* 14 | 15 | ## 0.10.0 16 | Mon, 04 May 2020 09:30:44 GMT 17 | 18 | *Version update only* 19 | 20 | ## 0.9.0 21 | Thu, 26 Sep 2019 17:35:19 GMT 22 | 23 | *Version update only* 24 | 25 | ## 0.8.4 26 | Sat, 13 Jul 2019 01:23:01 GMT 27 | 28 | *Version update only* 29 | 30 | ## 0.8.3 31 | Mon, 17 Dec 2018 18:41:04 GMT 32 | 33 | *Version update only* 34 | 35 | ## 0.8.2 36 | Mon, 10 Dec 2018 21:06:44 GMT 37 | 38 | *Version update only* 39 | 40 | ## 0.8.1 41 | Mon, 10 Dec 2018 20:16:37 GMT 42 | 43 | *Version update only* 44 | 45 | ## 0.8.0 46 | Tue, 04 Dec 2018 18:16:48 GMT 47 | 48 | *Initial release* 49 | 50 | -------------------------------------------------------------------------------- /packages/hayspec-spec/README.md: -------------------------------------------------------------------------------- 1 | ![Build Status](https://travis-ci.org/hayspec/framework.svg?branch=master) [![NPM Version](https://badge.fury.io/js/@hayspec%2Fcore.svg)](https://badge.fury.io/js/%40hayspec%2Fcore) 2 | 3 | This package provides the main framework features for writing automatic tests. 4 | -------------------------------------------------------------------------------- /packages/hayspec-spec/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": ["dist/*"], 3 | "ext": "js,ts" 4 | } 5 | -------------------------------------------------------------------------------- /packages/hayspec-spec/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hayspec/spec", 3 | "version": "0.10.2", 4 | "description": "Core logic for Hayspec framework.", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "scripts": { 8 | "clean": "rm -Rf ./dist", 9 | "build": "npm run clean; npx tsc", 10 | "test": "npx nyc npx ava --verbose" 11 | }, 12 | "ava": { 13 | "extensions": [ 14 | "ts" 15 | ], 16 | "require": [ 17 | "ts-node/register" 18 | ], 19 | "files": [ 20 | "src/tests/*.test.ts", 21 | "src/tests/**/*.test.ts" 22 | ] 23 | }, 24 | "nyc": { 25 | "extension": [ 26 | ".ts" 27 | ], 28 | "require": [ 29 | "ts-node/register" 30 | ], 31 | "exclude": [ 32 | "src/tests" 33 | ] 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/hayspec/framework.git" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/hayspec/framework/issues" 41 | }, 42 | "homepage": "https://github.com/hayspec/framework#readme", 43 | "keywords": [ 44 | "test", 45 | "testing", 46 | "spec", 47 | "specification", 48 | "hay", 49 | "javascript", 50 | "typescript", 51 | "nodejs", 52 | "tdd", 53 | "unit", 54 | "cli" 55 | ], 56 | "author": "Kristijan Sedlak (Xpepermint)", 57 | "license": "MIT", 58 | "devDependencies": { 59 | "@types/node": "^14.14.37", 60 | "ava": "3.15.0", 61 | "nyc": "^15.1.0", 62 | "ts-node": "^9.1.1", 63 | "typescript": "^4.2.3" 64 | }, 65 | "dependencies": { 66 | "axios": "^0.21.1" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/asserts/deep-equal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | export default function deepEqual (x, y) { 5 | if (x === y) { 6 | return true; 7 | } 8 | else if ( 9 | (typeof x == "object" && x != null) 10 | && (typeof y == "object" && y != null) 11 | ) { 12 | if (Object.keys(x).length != Object.keys(y).length) { 13 | return false; 14 | } 15 | for (var prop in x) { 16 | if (y.hasOwnProperty(prop)) { 17 | if (!deepEqual(x[prop], y[prop])) { 18 | return false; 19 | } 20 | } 21 | else { 22 | return false; 23 | } 24 | } 25 | return true; 26 | } 27 | else { 28 | return false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/asserts/is.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | export default function is (x, y) { 5 | if (x === y) { 6 | return x !== 0 || 1 / x === 1 / y; 7 | } 8 | else { 9 | return x !== x && y !== y; 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/asserts/throws.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | export default function throws (fn: () => any) { 5 | try { 6 | const res = fn(); 7 | if (res instanceof Promise) { 8 | return res.then(() => false).catch(() => true); 9 | } 10 | else { 11 | return false; 12 | } 13 | } 14 | catch (e) { 15 | return true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/asserts/truthy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | export default function truthy (value) { 5 | return ( 6 | value === true 7 | || value === 'true' 8 | || value === 'TRUE' 9 | || value === 1 10 | || value === '1' 11 | || value === 'yes' 12 | || value === 'YES' 13 | || value === 'ok' 14 | || value === 'OK' 15 | || value === 'correct' 16 | || value === 'CORRECT' 17 | ); 18 | } -------------------------------------------------------------------------------- /packages/hayspec-spec/src/core/context.ts: -------------------------------------------------------------------------------- 1 | import { Stage } from './stage'; 2 | import { AssertionNote } from './types'; 3 | import truthy from '../asserts/truthy'; 4 | import is from '../asserts/is'; 5 | import throws from '../asserts/throws'; 6 | import deepEqual from '../asserts/deep-equal'; 7 | import { exec } from '../methods/exec'; 8 | import { sleep } from '../methods/sleep'; 9 | import { request, AxiosRequestConfig } from '../methods/request'; 10 | 11 | /** 12 | * 13 | */ 14 | interface AssertionRecipe { 15 | assertion: string; 16 | handler: () => (boolean | Promise); 17 | message?: string; 18 | } 19 | 20 | /** 21 | * 22 | */ 23 | export class Context { 24 | protected data: Data = {} as any; 25 | public stage: Stage; 26 | 27 | /** 28 | * 29 | */ 30 | public constructor(stage: Stage) { 31 | this.stage = stage; 32 | } 33 | 34 | /** 35 | * 36 | */ 37 | public get(k: Key) { 38 | return this.data[k] || this.stage.get(k); 39 | } 40 | 41 | /** 42 | * 43 | */ 44 | public set(k: Key, v: Value) { 45 | (this.data as any)[k] = v; 46 | } 47 | 48 | /** 49 | * 50 | */ 51 | public pass(message?: string) { 52 | return this.assert({ 53 | assertion: 'pass', 54 | handler: () => true, 55 | message, 56 | }); 57 | } 58 | 59 | /** 60 | * 61 | */ 62 | public fail(message?: any) { 63 | return this.assert({ 64 | assertion: 'fail', 65 | handler: () => false, 66 | message, 67 | }); 68 | } 69 | 70 | /** 71 | * 72 | */ 73 | public truthy(value: any, message?: any) { 74 | return this.assert({ 75 | assertion: 'truthy', 76 | handler: () => truthy(value), 77 | message, 78 | }); 79 | } 80 | 81 | /** 82 | * 83 | */ 84 | public falsy(value: any, message?: any) { 85 | return this.assert({ 86 | assertion: 'falsy', 87 | handler: () => !truthy(value), 88 | message, 89 | }); 90 | } 91 | 92 | /** 93 | * 94 | */ 95 | public true(value: any, message?: any) { 96 | return this.assert({ 97 | assertion: 'true', 98 | handler: () => !!value, 99 | message, 100 | }); 101 | } 102 | 103 | /** 104 | * 105 | */ 106 | public false(value: any, message?: any) { 107 | return this.assert({ 108 | assertion: 'false', 109 | handler: () => !value, 110 | message, 111 | }); 112 | } 113 | 114 | /** 115 | * 116 | */ 117 | public is(value: any, expected: any, message?: any) { 118 | return this.assert({ 119 | assertion: 'is', 120 | handler: () => is(value, expected), 121 | message, 122 | }); 123 | } 124 | 125 | /** 126 | * 127 | */ 128 | public not(value: any, expected: any, message?: any) { 129 | return this.assert({ 130 | assertion: 'not', 131 | handler: () => !is(value, expected), 132 | message, 133 | }); 134 | } 135 | 136 | /** 137 | * 138 | */ 139 | public throws(fn: () => any, message?: any) { 140 | return this.assert({ 141 | assertion: 'throws', 142 | handler: () => throws(fn), 143 | message, 144 | }); 145 | } 146 | 147 | /** 148 | * 149 | */ 150 | public notThrows(fn: () => any, message?: any) { 151 | return this.assert({ 152 | assertion: 'notThrows', 153 | handler: () => { 154 | const res = throws(fn); 155 | if (res instanceof Promise) { 156 | return res.then((res) => !res); 157 | } 158 | else { 159 | return !res; 160 | } 161 | }, 162 | message, 163 | }); 164 | } 165 | 166 | /** 167 | * 168 | */ 169 | public regex(exp: RegExp, value: string, message?: any) { 170 | return this.assert({ 171 | assertion: 'regex', 172 | handler: () => exp.test(value), 173 | message, 174 | }); 175 | } 176 | 177 | /** 178 | * 179 | */ 180 | public notRegex(exp: RegExp, value: string, message?: any) { 181 | return this.assert({ 182 | assertion: 'notRegex', 183 | handler: () => !exp.test(value), 184 | message, 185 | }); 186 | } 187 | 188 | /** 189 | * 190 | */ 191 | public deepEqual(value: any, expected: any, message?: any) { 192 | return this.assert({ 193 | assertion: 'deepEqual', 194 | handler: () => deepEqual(value, expected), 195 | message, 196 | }); 197 | } 198 | 199 | /** 200 | * 201 | */ 202 | public notDeepEqual(value: any, expected: any, message?: any) { 203 | return this.assert({ 204 | assertion: 'notDeepEqual', 205 | handler: () => !deepEqual(value, expected), 206 | message, 207 | }); 208 | } 209 | 210 | /** 211 | * 212 | */ 213 | public async sleep(time: number) { 214 | return sleep(time); 215 | } 216 | 217 | /** 218 | * 219 | */ 220 | public async request(config: AxiosRequestConfig) { 221 | return request(config); 222 | } 223 | 224 | /** 225 | * 226 | */ 227 | public async exec(command: string) { 228 | return exec(command); 229 | } 230 | 231 | /** 232 | * 233 | */ 234 | protected assert(recipe: AssertionRecipe) { 235 | const success = recipe.handler(); 236 | 237 | const buildResult = (success: boolean) => { 238 | return { 239 | type: 'AssertionNote', 240 | message: recipe.message || null, 241 | assertion: recipe.assertion || null, 242 | success: !!success, 243 | } as AssertionNote; 244 | } 245 | const printResult = (result: AssertionNote) => { 246 | this.stage.reporter.note(result); 247 | return result; 248 | }; 249 | 250 | if (success instanceof Promise) { 251 | return success.then((success) => { 252 | const result = buildResult(success); 253 | return printResult(result); 254 | }); 255 | } 256 | else { 257 | const result = buildResult(success); 258 | return printResult(result); 259 | } 260 | } 261 | 262 | } 263 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/core/reporter.ts: -------------------------------------------------------------------------------- 1 | import { SpecStartNote, SpecEndNote, TestStartNote, TestEndNote, AssertionNote } from './types'; 2 | 3 | /** 4 | * 5 | */ 6 | export type ReporterNote = SpecStartNote | SpecEndNote | TestStartNote | TestEndNote | AssertionNote; 7 | 8 | /** 9 | * 10 | */ 11 | export interface ReporterRecipe { 12 | onBegin?: () => void; 13 | onEnd?: () => void; 14 | onNote?: (note: ReporterNote, change: ReporterLevelChange) => void; 15 | onSpecStartNote?: (note: SpecStartNote) => void; 16 | onSpecEndNote?: (note: SpecEndNote) => void; 17 | onTestStartNote?: (note: TestStartNote) => void; 18 | onTestEndNote?: (note: TestEndNote) => void; 19 | onAssertionNote?: (note: AssertionNote) => void; 20 | } 21 | 22 | /** 23 | * 24 | */ 25 | export type ReporterLevelChange = -1 | 0 | 1; 26 | 27 | /** 28 | * 29 | */ 30 | export class Reporter { 31 | protected recipe: ReporterRecipe; 32 | public level: number = 0; 33 | 34 | /** 35 | * 36 | */ 37 | public constructor(recipe?: ReporterRecipe) { 38 | this.recipe = recipe || {}; 39 | } 40 | 41 | /** 42 | * 43 | */ 44 | public begin() { 45 | this.onBegin(); 46 | } 47 | 48 | /** 49 | * 50 | */ 51 | public end() { 52 | this.onEnd(); 53 | } 54 | 55 | /** 56 | * 57 | */ 58 | public note(note: ReporterNote) { 59 | const level = this.level; 60 | 61 | if (note.type === 'SpecStartNote') { 62 | this.level++; 63 | this.onSpecStartNote(note); 64 | } 65 | else if (note.type === 'SpecEndNote') { 66 | this.level--; 67 | this.onSpecEndNote(note); 68 | } 69 | else if (note.type === 'TestStartNote') { 70 | this.level++; 71 | this.onTestStartNote(note); 72 | } 73 | else if (note.type === 'TestEndNote') { 74 | this.level--; 75 | this.onTestEndNote(note); 76 | } 77 | else if (note.type === 'AssertionNote') { 78 | this.onAssertionNote(note); 79 | } 80 | 81 | const change = (level - this.level) as ReporterLevelChange; 82 | this.onNote(note, change); 83 | } 84 | 85 | /** 86 | * 87 | */ 88 | public reset() { 89 | this.level = 0; 90 | } 91 | 92 | /** 93 | * 94 | */ 95 | protected onBegin() { 96 | if (typeof this.recipe.onBegin === 'function') { 97 | this.recipe.onBegin(); 98 | } 99 | } 100 | 101 | /** 102 | * 103 | */ 104 | protected onEnd() { 105 | if (typeof this.recipe.onEnd === 'function') { 106 | this.recipe.onEnd(); 107 | } 108 | } 109 | 110 | /** 111 | * 112 | */ 113 | protected onSpecStartNote(note: SpecStartNote) { 114 | if (typeof this.recipe.onSpecStartNote === 'function') { 115 | this.recipe.onSpecStartNote(note); 116 | } 117 | } 118 | 119 | /** 120 | * 121 | */ 122 | protected onSpecEndNote(note: SpecEndNote) { 123 | if (typeof this.recipe.onSpecEndNote === 'function') { 124 | this.recipe.onSpecEndNote(note); 125 | } 126 | } 127 | 128 | /** 129 | * 130 | */ 131 | protected onTestStartNote(note: TestStartNote) { 132 | if (typeof this.recipe.onTestStartNote === 'function') { 133 | this.recipe.onTestStartNote(note); 134 | } 135 | } 136 | 137 | /** 138 | * 139 | */ 140 | protected onTestEndNote(note: TestEndNote) { 141 | if (typeof this.recipe.onTestEndNote === 'function') { 142 | this.recipe.onTestEndNote(note); 143 | } 144 | } 145 | 146 | /** 147 | * 148 | */ 149 | protected onAssertionNote(note: AssertionNote) { 150 | if (typeof this.recipe.onAssertionNote === 'function') { 151 | this.recipe.onAssertionNote(note); 152 | } 153 | } 154 | 155 | /** 156 | * 157 | */ 158 | protected onNote(note: ReporterNote, change: ReporterLevelChange) { 159 | if (typeof this.recipe.onNote === 'function') { 160 | this.recipe.onNote(note, change); 161 | } 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/core/spec.ts: -------------------------------------------------------------------------------- 1 | import { Stage } from './stage'; 2 | import { Context } from './context'; 3 | import { Reporter } from './reporter'; 4 | 5 | /** 6 | * 7 | */ 8 | type StageHandler = (stage: Stage) => (void | Promise); 9 | 10 | /** 11 | * 12 | */ 13 | type ContextHandler = (context: Context, stage: Stage) => (void | Promise); 14 | 15 | /** 16 | * 17 | */ 18 | interface PerformRecipes { 19 | message: string; 20 | handler?: ContextHandler 21 | spec?: Spec; 22 | } 23 | 24 | /** 25 | * 26 | */ 27 | export class Spec { 28 | protected beforeHandlers: StageHandler[] = []; 29 | protected beforeEachHandlers: ContextHandler[] = []; 30 | protected afterHandlers: StageHandler[] = []; 31 | protected afterEachHandlers: ContextHandler[] = []; 32 | protected performRecipes: PerformRecipes[] = []; 33 | protected onlyEnabled: boolean = false; 34 | protected _stage: Stage; 35 | public parent: Spec; 36 | 37 | /** 38 | * 39 | */ 40 | public constructor(stage?: Stage, parent?: Spec) { 41 | this.parent = parent || null; 42 | this.stage = stage || this.createStage(); 43 | } 44 | 45 | /** 46 | * 47 | */ 48 | public set stage(s: Stage) { 49 | if (this.parent) { 50 | this.parent.stage = s; 51 | } 52 | else { 53 | this._stage = s; 54 | } 55 | } 56 | 57 | /** 58 | * 59 | */ 60 | public get stage() { 61 | if (this.parent) { 62 | return this.parent.stage; 63 | } 64 | else { 65 | return this._stage; 66 | } 67 | } 68 | 69 | /** 70 | * 71 | */ 72 | public hasOnly() { 73 | return this.onlyEnabled; 74 | } 75 | 76 | /** 77 | * 78 | */ 79 | public isRoot() { 80 | return !this.parent; 81 | } 82 | 83 | /** 84 | * 85 | */ 86 | public before(handler: StageHandler, append: boolean = true) { 87 | if (append) { 88 | this.beforeHandlers.push(handler); 89 | } 90 | else { 91 | this.beforeHandlers.unshift(handler); 92 | } 93 | return this; 94 | } 95 | 96 | /** 97 | * 98 | */ 99 | public beforeEach(handler: ContextHandler, append: boolean = true) { 100 | if (append) { 101 | this.beforeEachHandlers.push(handler); 102 | } 103 | else { 104 | this.beforeEachHandlers.unshift(handler); 105 | } 106 | return this; 107 | } 108 | 109 | /** 110 | * 111 | */ 112 | public after(handler: StageHandler, append: boolean = true) { 113 | if (append) { 114 | this.afterHandlers.push(handler); 115 | } 116 | else { 117 | this.afterHandlers.unshift(handler); 118 | } 119 | return this; 120 | } 121 | 122 | /** 123 | * 124 | */ 125 | public afterEach(handler: ContextHandler, append: boolean = true) { 126 | if (append) { 127 | this.afterEachHandlers.push(handler); 128 | } 129 | else { 130 | this.afterEachHandlers.unshift(handler); 131 | } 132 | return this; 133 | } 134 | 135 | /** 136 | * 137 | */ 138 | public spec(message: string, spec: Spec) { 139 | const known = this.performRecipes.filter((r) => r.spec === spec).length > 0; 140 | if (!known) { 141 | spec.parent = this; 142 | spec.stage = this.stage; 143 | [].concat(this.beforeEachHandlers).reverse().forEach((h) => spec.beforeEach(h, false)); 144 | this.afterEachHandlers.forEach((h) => spec.afterEach(h)); 145 | } 146 | 147 | this.performRecipes.push({ message, spec }); 148 | 149 | return this; 150 | } 151 | 152 | /** 153 | * 154 | */ 155 | public test(message: string, handler: ContextHandler) { 156 | if (!this.onlyEnabled) { 157 | this.performRecipes.push({ message, handler }); 158 | } 159 | return this; 160 | } 161 | 162 | /** 163 | * 164 | */ 165 | public skip(message: string, handler?: ContextHandler) { 166 | this.performRecipes.push({ message, handler: null }); 167 | return this; 168 | } 169 | 170 | /** 171 | * 172 | */ 173 | public only(message: string, handler: ContextHandler) { 174 | if (!this.onlyEnabled) { 175 | this.performRecipes = this.performRecipes.filter((r) => !r.handler); 176 | this.onlyEnabled = true; 177 | } 178 | this.performRecipes.push({ message, handler }); 179 | return this; 180 | } 181 | 182 | /** 183 | * 184 | */ 185 | public async perform() { 186 | await this.performBegin(); 187 | await this.performBefore(); 188 | 189 | for (const recipe of this.performRecipes) { 190 | if (recipe.spec) { 191 | await this.performSpec(recipe); 192 | } 193 | else { 194 | await this.performTest(recipe); 195 | } 196 | } 197 | 198 | await this.performAfter(); 199 | await this.performEnd(); 200 | } 201 | 202 | /** 203 | * 204 | */ 205 | protected async performBegin() { 206 | if (this.isRoot()) { 207 | this.stage.reporter.begin(); 208 | } 209 | } 210 | 211 | /** 212 | * 213 | */ 214 | protected async performEnd() { 215 | if (this.isRoot()) { 216 | this.stage.reporter.end(); 217 | } 218 | } 219 | 220 | /** 221 | * 222 | */ 223 | protected async performSpec(recipe: PerformRecipes) { 224 | const start = Date.now(); 225 | 226 | this.stage.reporter.note({ 227 | type: 'SpecStartNote', 228 | message: recipe.message, 229 | }); 230 | 231 | await recipe.spec.perform(); 232 | 233 | this.stage.reporter.note({ 234 | type: 'SpecEndNote', 235 | duration: Date.now() - start, 236 | }); 237 | } 238 | 239 | /** 240 | * 241 | */ 242 | protected async performTest(recipe: PerformRecipes) { 243 | const start = Date.now(); 244 | 245 | this.stage.reporter.note({ 246 | type: 'TestStartNote', 247 | message: recipe.message, 248 | perform: !!recipe.handler, 249 | }); 250 | 251 | if (recipe.handler) { 252 | const context = this.createContext(); 253 | await this.performBeforeEach(context); 254 | await recipe.handler(context, this.stage); 255 | await this.performAfterEach(context); 256 | } 257 | 258 | this.stage.reporter.note({ 259 | type: 'TestEndNote', 260 | duration: Date.now() - start, 261 | }); 262 | } 263 | 264 | /** 265 | * 266 | */ 267 | protected async performBefore() { 268 | for (const handler of this.beforeHandlers) { 269 | await handler(this.stage); 270 | } 271 | } 272 | 273 | /** 274 | * 275 | */ 276 | protected async performAfter() { 277 | for (const handler of this.afterHandlers) { 278 | await handler(this.stage); 279 | } 280 | } 281 | 282 | /** 283 | * 284 | */ 285 | protected async performBeforeEach(context: Context) { 286 | for (const handler of this.beforeEachHandlers) { 287 | await handler(context, this.stage); 288 | } 289 | } 290 | 291 | /** 292 | * 293 | */ 294 | protected async performAfterEach(context: Context) { 295 | for (const handler of this.afterEachHandlers) { 296 | await handler(context, this.stage); 297 | } 298 | } 299 | 300 | /** 301 | * 302 | */ 303 | protected createStage() { 304 | const reporter = new Reporter(); 305 | return new Stage(reporter); 306 | } 307 | 308 | /** 309 | * 310 | */ 311 | protected createContext() { 312 | return new Context(this.stage); 313 | } 314 | 315 | }; 316 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/core/stage.ts: -------------------------------------------------------------------------------- 1 | import { Reporter } from "./reporter"; 2 | import { exec } from '../methods/exec'; 3 | import { sleep } from '../methods/sleep'; 4 | import { request, AxiosRequestConfig } from '../methods/request'; 5 | 6 | /** 7 | * 8 | */ 9 | export class Stage { 10 | protected data: Data = {} as any; 11 | public reporter: Reporter; 12 | 13 | /** 14 | * 15 | */ 16 | public constructor (reporter: Reporter) { 17 | this.reporter = reporter; 18 | } 19 | 20 | /** 21 | * 22 | */ 23 | public set(k: Key, v: Value) { 24 | (this.data as any)[k] = v; 25 | } 26 | 27 | /** 28 | * 29 | */ 30 | public get(k: Key) { 31 | return this.data[k]; 32 | } 33 | 34 | /** 35 | * 36 | */ 37 | public async sleep(time: number) { 38 | return sleep(time); 39 | } 40 | 41 | /** 42 | * 43 | */ 44 | public async request(config: AxiosRequestConfig) { 45 | return request(config); 46 | } 47 | 48 | /** 49 | * 50 | */ 51 | public async exec(command: string) { 52 | return exec(command); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/core/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | export interface SpecStartNote { 5 | type: 'SpecStartNote'; 6 | message: string; 7 | } 8 | 9 | /** 10 | * 11 | */ 12 | export interface SpecEndNote { 13 | type: 'SpecEndNote'; 14 | duration: number; 15 | } 16 | 17 | /** 18 | * 19 | */ 20 | export interface TestStartNote { 21 | type: 'TestStartNote'; 22 | message: string; 23 | perform: boolean; 24 | } 25 | 26 | /** 27 | * 28 | */ 29 | export interface TestEndNote { 30 | type: 'TestEndNote'; 31 | duration: number; 32 | } 33 | 34 | /** 35 | * 36 | */ 37 | export interface AssertionNote { 38 | type: 'AssertionNote'; 39 | message: string; 40 | assertion: string; 41 | success: boolean; 42 | } 43 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core/context'; 2 | export * from './core/reporter'; 3 | export * from './core/spec'; 4 | export * from './core/stage'; 5 | export * from './core/types'; 6 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/methods/exec.ts: -------------------------------------------------------------------------------- 1 | import * as cproc from 'child_process'; 2 | import * as util from 'util'; 3 | 4 | export const exec = util.promisify(cproc.exec); 5 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/methods/request.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; 2 | 3 | /** 4 | * Configuration interface. 5 | */ 6 | export { AxiosRequestConfig, AxiosResponse }; 7 | 8 | /** 9 | * Performs a HTTP request. 10 | * @param config Axios configuration. 11 | */ 12 | export async function request(config: AxiosRequestConfig): Promise { 13 | return axios.create({ 14 | baseURL: 'http://localhost:4445', 15 | })(config).catch((err) => { 16 | if (err.response) { 17 | return err.response; 18 | } 19 | throw err; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/methods/sleep.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms an object into web3 tuple type. 3 | * @param obj Web3 structure as object. 4 | */ 5 | export async function sleep(time): Promise { 6 | return new Promise((resolve) => { 7 | setTimeout(() => resolve(null), time); 8 | }) as any; 9 | } 10 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/tests/asserts/deep-equal.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import deepEqual from '../../asserts/deep-equal'; 3 | 4 | test('succeeds for equal values', async (t) => { 5 | t.true(deepEqual(true, true)); 6 | t.true(deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 })); 7 | t.true(deepEqual({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] })); 8 | t.true(deepEqual({ a: { b: { c: { d: false } } } }, { a: { b: { c: { d: false } } } })); 9 | }); 10 | 11 | test('fails for not equal values', async (t) => { 12 | t.false(deepEqual(true, false)); 13 | t.false(deepEqual({ a: 1, b: '2' }, { b: 2, a: 1 })); 14 | t.false(deepEqual({ a: [1, { b: '2' }] }, { a: [1, { b: 2 }] })); 15 | t.false(deepEqual({ a: { b: { c: { d: true } } } }, { a: { b: { c: { d: false } } } })); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/tests/asserts/is.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import is from '../../asserts/is'; 3 | 4 | test('succeeds for equal values', async (t) => { 5 | t.true(is(1, 1)); 6 | t.true(is(0, 0)); 7 | t.true(is(true, true)); 8 | t.true(is(false, false)); 9 | t.true(is('foo', 'foo')); 10 | }); 11 | 12 | test('fails for not equal values', async (t) => { 13 | t.false(is(1, 0)); 14 | t.false(is(1, '1')); 15 | t.false(is('1', 1)); 16 | t.false(is(false, true)); 17 | t.false(is('foo', 'bar')); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/tests/asserts/throws.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import throws from '../../asserts/throws'; 3 | 4 | test('succeeds for values that throw', async (t) => { 5 | t.true(throws(() => { throw new Error() })); 6 | t.true(await throws(() => Promise.reject())); 7 | }); 8 | 9 | test('succeeds for values that do not throw', async (t) => { 10 | t.false(throws(() => { return; })); 11 | t.false(await throws(() => Promise.resolve())); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/tests/asserts/truthy.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import truthy from '../../asserts/truthy'; 3 | 4 | test('succeeds for true values', async (t) => { 5 | t.true(truthy(true)); 6 | t.true(truthy('true')); 7 | t.true(truthy('TRUE')); 8 | t.true(truthy(1)); 9 | t.true(truthy('1')); 10 | t.true(truthy('yes')); 11 | t.true(truthy('YES')); 12 | t.true(truthy('ok')); 13 | t.true(truthy('OK')); 14 | t.true(truthy('correct')); 15 | t.true(truthy('CORRECT')); 16 | }); 17 | 18 | test('fails for false values', async (t) => { 19 | t.false(truthy(false)); 20 | t.false(truthy('false')); 21 | t.false(truthy('FALSE')); 22 | t.false(truthy(0)); 23 | t.false(truthy('0')); 24 | t.false(truthy('no')); 25 | t.false(truthy('NO')); 26 | t.false(truthy('bad')); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/tests/core/context.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Context, Stage } from '../..'; 3 | import { Reporter } from '../../core/reporter'; 4 | 5 | const reporter = new Reporter(); 6 | 7 | interface Data { 8 | id: number; 9 | name: string; 10 | } 11 | 12 | test('methods `set()` and `get()` manages values', async (t) => { 13 | const stage = new Stage(reporter); 14 | const context = new Context(stage); 15 | context.set('id', 100); 16 | context.set('name', 'foo'); 17 | const id = context.get('id'); // typescript should show `integer` type 18 | const name = context.get('name'); // typescript should show `string` type 19 | t.is(id, 100); 20 | t.is(name, 'foo'); 21 | }); 22 | 23 | test('methods `get()` inherits values from stage', async (t) => { 24 | const stage = new Stage(reporter); 25 | const context = new Context(stage); 26 | stage.set('id', 100); 27 | context.set('name', 'foo'); 28 | const id = context.get('id'); // typescript should show `integer` type 29 | const name = context.get('name'); // typescript should show `string` type 30 | t.is(id, 100); 31 | t.is(name, 'foo'); 32 | }); 33 | 34 | test('method `pass()` passes the test', async (t) => { 35 | const stage = new Stage(reporter); 36 | const context = new Context(stage); 37 | const results = [ 38 | context.pass(), 39 | context.pass('foo'), 40 | ]; 41 | t.deepEqual(results, [ 42 | { 43 | type: 'AssertionNote', 44 | message: null, 45 | assertion: 'pass', 46 | success: true, 47 | }, 48 | { 49 | type: 'AssertionNote', 50 | message: 'foo', 51 | assertion: 'pass', 52 | success: true, 53 | }, 54 | ]); 55 | }); 56 | 57 | test('method `fail()` fails the test', async (t) => { 58 | const stage = new Stage(reporter); 59 | const context = new Context(stage); 60 | const results = [ 61 | context.fail(), 62 | context.fail('foo'), 63 | ]; 64 | t.deepEqual(results, [ 65 | { 66 | type: 'AssertionNote', 67 | message: null, 68 | assertion: 'fail', 69 | success: false, 70 | }, 71 | { 72 | type: 'AssertionNote', 73 | message: 'foo', 74 | assertion: 'fail', 75 | success: false, 76 | }, 77 | ]); 78 | }); 79 | 80 | test('method `truthy()` asserts that value is truthy', async (t) => { 81 | const stage = new Stage(reporter); 82 | const context = new Context(stage); 83 | const results = [ 84 | context.truthy(true), 85 | context.truthy(false, 'foo'), 86 | ]; 87 | t.deepEqual(results, [ 88 | { 89 | type: 'AssertionNote', 90 | message: null, 91 | assertion: 'truthy', 92 | success: true, 93 | }, 94 | { 95 | type: 'AssertionNote', 96 | message: 'foo', 97 | assertion: 'truthy', 98 | success: false, 99 | }, 100 | ]); 101 | }); 102 | 103 | test('method `falsy()` asserts that value is falsy', async (t) => { 104 | const stage = new Stage(reporter); 105 | const context = new Context(stage); 106 | const results = [ 107 | context.falsy('false'), 108 | context.falsy('true', 'foo'), 109 | ]; 110 | t.deepEqual(results, [ 111 | { 112 | type: 'AssertionNote', 113 | message: null, 114 | assertion: 'falsy', 115 | success: true, 116 | }, 117 | { 118 | type: 'AssertionNote', 119 | message: 'foo', 120 | assertion: 'falsy', 121 | success: false, 122 | }, 123 | ]); 124 | }); 125 | 126 | test('method `true()` asserts that value is true', async (t) => { 127 | const stage = new Stage(reporter); 128 | const context = new Context(stage); 129 | const results = [ 130 | context.true(true), 131 | context.true(false, 'foo'), 132 | ]; 133 | t.deepEqual(results, [ 134 | { 135 | type: 'AssertionNote', 136 | message: null, 137 | assertion: 'true', 138 | success: true, 139 | }, 140 | { 141 | type: 'AssertionNote', 142 | message: 'foo', 143 | assertion: 'true', 144 | success: false, 145 | }, 146 | ]); 147 | }); 148 | 149 | test('method `false()` asserts that value is false', async (t) => { 150 | const stage = new Stage(reporter); 151 | const context = new Context(stage); 152 | const results = [ 153 | context.false(false), 154 | context.false(true, 'foo'), 155 | ]; 156 | t.deepEqual(results, [ 157 | { 158 | type: 'AssertionNote', 159 | message: null, 160 | assertion: 'false', 161 | success: true, 162 | }, 163 | { 164 | type: 'AssertionNote', 165 | message: 'foo', 166 | assertion: 'false', 167 | success: false, 168 | }, 169 | ]); 170 | }); 171 | 172 | test('method `is()` asserts that two values are equal', async (t) => { 173 | const stage = new Stage(reporter); 174 | const context = new Context(stage); 175 | const results = [ 176 | context.is('foo', 'foo'), 177 | context.is(100, 200, 'foo'), 178 | ]; 179 | t.deepEqual(results, [ 180 | { 181 | type: 'AssertionNote', 182 | message: null, 183 | assertion: 'is', 184 | success: true, 185 | }, 186 | { 187 | type: 'AssertionNote', 188 | message: 'foo', 189 | assertion: 'is', 190 | success: false, 191 | }, 192 | ]); 193 | }); 194 | 195 | test('method `not()` asserts that two values are not equal', async (t) => { 196 | const stage = new Stage(reporter); 197 | const context = new Context(stage); 198 | const results = [ 199 | context.not('foo', 'bar'), 200 | context.not(100, 100, 'foo'), 201 | ]; 202 | t.deepEqual(results, [ 203 | { 204 | type: 'AssertionNote', 205 | message: null, 206 | assertion: 'not', 207 | success: true, 208 | }, 209 | { 210 | type: 'AssertionNote', 211 | message: 'foo', 212 | assertion: 'not', 213 | success: false, 214 | }, 215 | ]); 216 | }); 217 | 218 | test('method `throws()` asserts that function throws an error', async (t) => { 219 | const stage = new Stage(reporter); 220 | const context = new Context(stage); 221 | const results = [ 222 | context.throws(() => { throw new Error(); }), 223 | await context.throws(() => Promise.reject(), 'foo'), 224 | context.throws(() => { return; }, 'foo'), 225 | await context.throws(() => Promise.resolve()), 226 | ]; 227 | t.deepEqual(results, [ 228 | { 229 | type: 'AssertionNote', 230 | message: null, 231 | assertion: 'throws', 232 | success: true, 233 | }, 234 | { 235 | type: 'AssertionNote', 236 | message: 'foo', 237 | assertion: 'throws', 238 | success: true, 239 | }, 240 | { 241 | type: 'AssertionNote', 242 | message: 'foo', 243 | assertion: 'throws', 244 | success: false, 245 | }, 246 | { 247 | type: 'AssertionNote', 248 | message: null, 249 | assertion: 'throws', 250 | success: false, 251 | }, 252 | ]); 253 | }); 254 | 255 | test('method `notThrows()` asserts that function does not throw an error', async (t) => { 256 | const stage = new Stage(reporter); 257 | const context = new Context(stage); 258 | const results = [ 259 | context.notThrows(() => { return; }), 260 | await context.notThrows(() => Promise.resolve(), 'foo'), 261 | context.notThrows(() => { throw new Error(); }, 'foo'), 262 | await context.notThrows(() => Promise.reject()), 263 | ]; 264 | t.deepEqual(results, [ 265 | { 266 | type: 'AssertionNote', 267 | message: null, 268 | assertion: 'notThrows', 269 | success: true, 270 | }, 271 | { 272 | type: 'AssertionNote', 273 | message: 'foo', 274 | assertion: 'notThrows', 275 | success: true, 276 | }, 277 | { 278 | type: 'AssertionNote', 279 | message: 'foo', 280 | assertion: 'notThrows', 281 | success: false, 282 | }, 283 | { 284 | type: 'AssertionNote', 285 | message: null, 286 | assertion: 'notThrows', 287 | success: false, 288 | }, 289 | ]); 290 | }); 291 | 292 | test('method `regex()` asserts that string maches regular expression', async (t) => { 293 | const stage = new Stage(reporter); 294 | const context = new Context(stage); 295 | const results = [ 296 | context.regex(/bar/, 'foo bar baz'), 297 | context.regex(/zed/, 'foo bar baz', 'zed'), 298 | ]; 299 | t.deepEqual(results, [ 300 | { 301 | type: 'AssertionNote', 302 | message: null, 303 | assertion: 'regex', 304 | success: true, 305 | }, 306 | { 307 | type: 'AssertionNote', 308 | message: 'zed', 309 | assertion: 'regex', 310 | success: false, 311 | }, 312 | ]); 313 | }); 314 | 315 | test('method `notRegex()` asserts that string does not maches regular expression', async (t) => { 316 | const stage = new Stage(reporter); 317 | const context = new Context(stage); 318 | const results = [ 319 | context.notRegex(/bar/, 'foo bar baz'), 320 | context.notRegex(/zed/, 'foo bar baz', 'zed'), 321 | ]; 322 | t.deepEqual(results, [ 323 | { 324 | type: 'AssertionNote', 325 | message: null, 326 | assertion: 'notRegex', 327 | success: false, 328 | }, 329 | { 330 | type: 'AssertionNote', 331 | message: 'zed', 332 | assertion: 'notRegex', 333 | success: true, 334 | }, 335 | ]); 336 | }); 337 | 338 | test('method `deepEqual()` asserts that two objects are equal', async (t) => { 339 | const stage = new Stage(reporter); 340 | const context = new Context(stage); 341 | const results = [ 342 | context.deepEqual({ a: 1 }, { a: 1 }), 343 | context.deepEqual({ a: 1 }, { a: 2 }, 'foo'), 344 | ]; 345 | t.deepEqual(results, [ 346 | { 347 | type: 'AssertionNote', 348 | message: null, 349 | assertion: 'deepEqual', 350 | success: true, 351 | }, 352 | { 353 | type: 'AssertionNote', 354 | message: 'foo', 355 | assertion: 'deepEqual', 356 | success: false, 357 | }, 358 | ]); 359 | }); 360 | 361 | test('method `notDeepEqual()` asserts that two objects are equal', async (t) => { 362 | const stage = new Stage(reporter); 363 | const context = new Context(stage); 364 | const results = [ 365 | context.notDeepEqual({ a: 1 }, { a: 2 }), 366 | context.notDeepEqual({ a: 1 }, { a: 1 }, 'foo'), 367 | ]; 368 | t.deepEqual(results, [ 369 | { 370 | type: 'AssertionNote', 371 | message: null, 372 | assertion: 'notDeepEqual', 373 | success: true, 374 | }, 375 | { 376 | type: 'AssertionNote', 377 | message: 'foo', 378 | assertion: 'notDeepEqual', 379 | success: false, 380 | }, 381 | ]); 382 | }); 383 | 384 | test('method `sleep()` continues with timeout', async (t) => { 385 | const times = [0, 0, 0]; 386 | const stage = new Stage(reporter); 387 | const context = new Context(stage); 388 | times[0] = Date.now(); 389 | await stage.sleep(2000); 390 | times[1] = Date.now(); 391 | await context.sleep(2000); 392 | times[2] = Date.now(); 393 | t.true(times[1] >= times[0] + 2000); 394 | t.true(times[2] >= times[0] + 4000); 395 | }); 396 | 397 | test('methods `request()` returns supertest instance', async (t) => { 398 | const stage = new Stage(reporter); 399 | const context = new Context(stage); 400 | const res = await context.request({ url: 'https://jsonplaceholder.typicode.com/todos/1' }); 401 | t.is(res.status, 200); 402 | t.is(res.data.userId, 1); 403 | }); 404 | 405 | test('methods `exec()` returns terminal command result', async (t) => { 406 | const stage = new Stage(reporter); 407 | const context = new Context(stage); 408 | const { stdout, stderr } = await context.exec('echo "foo"'); 409 | t.true(stdout.indexOf('foo') !== -1); 410 | t.true(stderr === ''); 411 | }); 412 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/tests/core/reporter.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Reporter } from '../../core/reporter'; 3 | 4 | test('triggers recipe callbacks', async (t) => { 5 | const stat = { 6 | onBegin: 0, 7 | onEnd: 0, 8 | onNote: 0, 9 | onSpecStartNote: 0, 10 | onSpecEndNote: 0, 11 | onTestStartNote: 0, 12 | onTestEndNote: 0, 13 | onAssertionNote: 0, 14 | }; 15 | const reporter = new Reporter({ 16 | onBegin: () => stat.onBegin++, 17 | onEnd: () => stat.onEnd++, 18 | onNote: () => stat.onNote++, 19 | onSpecStartNote: () => stat.onSpecStartNote++, 20 | onSpecEndNote: () => stat.onSpecEndNote++, 21 | onTestStartNote: () => stat.onTestStartNote++, 22 | onTestEndNote: () => stat.onTestEndNote++, 23 | onAssertionNote: () => stat.onAssertionNote++, 24 | }); 25 | reporter.begin(); 26 | reporter.note({ 27 | type: 'SpecStartNote', 28 | message: 'foo', 29 | }); 30 | reporter.note({ 31 | type: 'SpecEndNote', 32 | duration: 0, 33 | }); 34 | reporter.note({ 35 | type: 'TestStartNote', 36 | message: 'foo', 37 | perform: true, 38 | }); 39 | reporter.note({ 40 | type: 'TestEndNote', 41 | duration: 0, 42 | }); 43 | reporter.note({ 44 | type: 'AssertionNote', 45 | message: 'foo', 46 | assertion: 'is', 47 | success: true, 48 | }); 49 | reporter.end(); 50 | t.deepEqual(stat, { 51 | onBegin: 1, 52 | onEnd: 1, 53 | onNote: 5, 54 | onSpecStartNote: 1, 55 | onSpecEndNote: 1, 56 | onTestStartNote: 1, 57 | onTestEndNote: 1, 58 | onAssertionNote: 1, 59 | }); 60 | }); 61 | 62 | test('memorizes spec block level', async (t) => { 63 | const reporter = new Reporter(); 64 | reporter.note({ 65 | type: 'SpecStartNote', 66 | message: 'foo', 67 | }); 68 | reporter.note({ 69 | type: 'TestStartNote', 70 | message: 'foo', 71 | perform: true, 72 | }); 73 | reporter.note({ 74 | type: 'SpecEndNote', 75 | duration: 0, 76 | }); 77 | t.is(reporter.level, 1); 78 | }); 79 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/tests/core/spec.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Spec } from '../..'; 3 | 4 | test('method isRoot() indicates if the spec is nested or not', async (t) => { 5 | const results = []; 6 | const spec1 = new Spec(); 7 | const spec0 = new Spec(); 8 | spec0.spec('', spec1); 9 | await spec0.perform(); 10 | t.true(spec0.isRoot()); 11 | t.false(spec1.isRoot()); 12 | }); 13 | 14 | test('method perform() executes spec stack', async (t) => { 15 | const results = []; 16 | const spec1 = new Spec(); 17 | spec1.before(() => { results.push('1-before-0'); }); 18 | spec1.after(() => { results.push('1-after-0'); }); 19 | spec1.beforeEach(() => { results.push('1-beforeeach-0'); }); 20 | spec1.afterEach(() => { results.push('1-aftereach-0'); }); 21 | spec1.test('', () => { results.push('1-0'); }); 22 | spec1.test('', () => { results.push('1-1'); }); 23 | const spec0 = new Spec(); 24 | spec0.test('', () => { results.push('0-0'); }); 25 | spec0.before(() => { results.push('0-before-0'); }); 26 | spec0.before(() => { results.push('0-before-1'); }); 27 | spec0.beforeEach(() => { results.push('0-beforeeach-0'); }); 28 | spec0.beforeEach(() => { results.push('0-beforeeach-1'); }); 29 | spec0.after(() => { results.push('0-after-0'); }); 30 | spec0.afterEach(() => { results.push('0-aftereach-0'); }); 31 | spec0.before(() => { results.push('0-before-2'); }); 32 | spec0.beforeEach(() => { results.push('0-beforeeach-2'); }); 33 | spec0.after(() => { results.push('0-after-1'); }); 34 | spec0.after(() => { results.push('0-after-2'); }); 35 | spec0.afterEach(() => { results.push('0-aftereach-1'); }); 36 | spec0.afterEach(() => { results.push('0-aftereach-2'); }); 37 | spec0.spec('', spec1); 38 | spec0.spec('', spec1); 39 | spec0.test('', () => { results.push('0-1'); }); 40 | await spec0.perform(); 41 | t.deepEqual(results, [ 42 | '0-before-0', 43 | '0-before-1', 44 | '0-before-2', 45 | '0-beforeeach-0', 46 | '0-beforeeach-1', 47 | '0-beforeeach-2', 48 | '0-0', 49 | '0-aftereach-0', 50 | '0-aftereach-1', 51 | '0-aftereach-2', 52 | '1-before-0', 53 | '0-beforeeach-0', 54 | '0-beforeeach-1', 55 | '0-beforeeach-2', 56 | '1-beforeeach-0', 57 | '1-0', 58 | '1-aftereach-0', 59 | '0-aftereach-0', 60 | '0-aftereach-1', 61 | '0-aftereach-2', 62 | '0-beforeeach-0', 63 | '0-beforeeach-1', 64 | '0-beforeeach-2', 65 | '1-beforeeach-0', 66 | '1-1', 67 | '1-aftereach-0', 68 | '0-aftereach-0', 69 | '0-aftereach-1', 70 | '0-aftereach-2', 71 | '1-after-0', 72 | '1-before-0', 73 | '0-beforeeach-0', 74 | '0-beforeeach-1', 75 | '0-beforeeach-2', 76 | '1-beforeeach-0', 77 | '1-0', 78 | '1-aftereach-0', 79 | '0-aftereach-0', 80 | '0-aftereach-1', 81 | '0-aftereach-2', 82 | '0-beforeeach-0', 83 | '0-beforeeach-1', 84 | '0-beforeeach-2', 85 | '1-beforeeach-0', 86 | '1-1', 87 | '1-aftereach-0', 88 | '0-aftereach-0', 89 | '0-aftereach-1', 90 | '0-aftereach-2', 91 | '1-after-0', 92 | '0-beforeeach-0', 93 | '0-beforeeach-1', 94 | '0-beforeeach-2', 95 | '0-1', 96 | '0-aftereach-0', 97 | '0-aftereach-1', 98 | '0-aftereach-2', 99 | '0-after-0', 100 | '0-after-1', 101 | '0-after-2', 102 | ]); 103 | }); 104 | 105 | test('method perform() ignores skipped tests', async (t) => { 106 | const results = []; 107 | const spec0 = new Spec(); 108 | spec0.skip('', () => { results.push('0-0'); }); 109 | spec0.before(() => { results.push('0-before-0'); }); 110 | spec0.beforeEach(() => { results.push('0-beforeeach-0'); }); 111 | spec0.after(() => { results.push('0-after-0'); }); 112 | spec0.afterEach(() => { results.push('0-aftereach-0'); }); 113 | spec0.before(() => { results.push('0-before-1'); }); 114 | spec0.beforeEach(() => { results.push('0-beforeeach-1'); }); 115 | spec0.after(() => { results.push('0-after-1'); }); 116 | spec0.afterEach(() => { results.push('0-aftereach-1'); }); 117 | spec0.test('', () => { results.push('0-1'); }); 118 | await spec0.perform(); 119 | t.deepEqual(results, [ 120 | '0-before-0', 121 | '0-before-1', 122 | '0-beforeeach-0', 123 | '0-beforeeach-1', 124 | '0-1', 125 | '0-aftereach-0', 126 | '0-aftereach-1', 127 | '0-after-0', 128 | '0-after-1', 129 | ]); 130 | }); 131 | 132 | test('method perform() performs only selected tests', async (t) => { 133 | const results = []; 134 | const spec0 = new Spec(); 135 | spec0.before(() => { results.push('0-before-0'); }); 136 | spec0.beforeEach(() => { results.push('0-beforeeach-0'); }); 137 | spec0.after(() => { results.push('0-after-0'); }); 138 | spec0.afterEach(() => { results.push('0-aftereach-0'); }); 139 | spec0.before(() => { results.push('0-before-1'); }); 140 | spec0.beforeEach(() => { results.push('0-beforeeach-1'); }); 141 | spec0.after(() => { results.push('0-after-1'); }); 142 | spec0.afterEach(() => { results.push('0-aftereach-1'); }); 143 | spec0.test('', () => { results.push('0-0'); }); 144 | spec0.only('', () => { results.push('0-1'); }); 145 | spec0.test('', () => { results.push('0-2'); }); 146 | await spec0.perform(); 147 | t.deepEqual(results, [ 148 | '0-before-0', 149 | '0-before-1', 150 | '0-beforeeach-0', 151 | '0-beforeeach-1', 152 | '0-1', 153 | '0-aftereach-0', 154 | '0-aftereach-1', 155 | '0-after-0', 156 | '0-after-1', 157 | ]); 158 | }); 159 | 160 | test('method spec() appends new spec with shared stage instance', async (t) => { 161 | const spec2 = new Spec(); 162 | const spec1 = new Spec(); 163 | const spec0 = new Spec(); 164 | spec1.spec('', spec2); 165 | spec0.spec('', spec1); 166 | t.true(spec2.stage === spec1.stage); 167 | t.true(spec2.stage === spec0.stage); 168 | t.true(spec1.stage === spec0.stage); 169 | }); 170 | 171 | test('context instance is shared between atomic stack', async (t) => { 172 | const spec = new Spec(); 173 | const ctxs = []; 174 | spec.beforeEach((ctx) => { 175 | ctxs.push(ctx); 176 | }); 177 | spec.test('', (ctx) => { 178 | ctxs.push(ctx); 179 | }); 180 | spec.afterEach((ctx) => { 181 | ctxs.push(ctx); 182 | }); 183 | t.true(ctxs[0] === ctxs[1]); 184 | t.true(ctxs[1] === ctxs[2]); 185 | t.true(ctxs[0] === ctxs[2]); 186 | }); 187 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/tests/core/stage.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Stage } from '../..'; 3 | import { Reporter } from '../../core/reporter'; 4 | 5 | const reporter = new Reporter(); 6 | 7 | interface Data { 8 | id: number; 9 | name: string; 10 | } 11 | 12 | test('methods `set()` and `get()` manages values', async (t) => { 13 | const stage = new Stage(reporter); 14 | stage.set('id', 100); 15 | stage.set('name', 'foo'); 16 | const id = stage.get('id'); // typescript should show `integer` type 17 | const name = stage.get('name'); // typescript should show `string` type 18 | t.is(id, 100); 19 | t.is(name, 'foo'); 20 | }); 21 | 22 | test('method `sleep()` continues with timeout', async (t) => { 23 | const times = [0, 0, 0]; 24 | const stage = new Stage(reporter); 25 | times[0] = Date.now(); 26 | await stage.sleep(2000); 27 | times[1] = Date.now(); 28 | await stage.sleep(2000); 29 | times[2] = Date.now(); 30 | t.true(times[1] >= times[0] + 2000); 31 | t.true(times[2] >= times[0] + 4000); 32 | }); 33 | 34 | test('methods `request()` returns supertest instance', async (t) => { 35 | const stage = new Stage(reporter); 36 | const res = await stage.request({ method: 'get', url: 'http://google.com' }); 37 | t.is(res.status, 200); 38 | }); 39 | 40 | test('methods `exec()` returns terminal command result', async (t) => { 41 | const stage = new Stage(reporter); 42 | const { stdout, stderr } = await stage.exec('echo "foo"'); 43 | t.true(stdout.indexOf('foo') !== -1); 44 | t.true(stderr === ''); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/tests/index.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import * as hayspec from '..'; 3 | 4 | test('exposes Spec class', async (t) => { 5 | t.is(!!hayspec.Spec, true); 6 | }); 7 | 8 | test('exposes Stage class', async (t) => { 9 | t.is(!!hayspec.Stage, true); 10 | }); 11 | 12 | test('exposes Context class', async (t) => { 13 | t.is(!!hayspec.Context, true); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/tests/methods/exec.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { exec } from '../../methods/exec'; 3 | 4 | test('executes terminal command', async (t) => { 5 | const { stdout, stderr } = await exec('echo "foo"'); 6 | t.true(stdout.indexOf('foo') !== -1); 7 | t.true(stderr === ''); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/tests/methods/request.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { request } from '../../methods/request'; 3 | 4 | test('performs a http request', async (t) => { 5 | const res = await request({ url: 'https://jsonplaceholder.typicode.com/todos/1' }); 6 | t.is(res.status, 200); 7 | t.is(res.data.userId, 1); 8 | }); 9 | 10 | test('handles errors', async (t) => { 11 | const res = await request({ url: 'https://jsonplaceholder.typicode.com/foo', method: 'post' }); 12 | t.is(res.status, 404); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/hayspec-spec/src/tests/methods/sleep.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { sleep } from '../../methods/sleep'; 3 | 4 | test('continues with timeout', async (t) => { 5 | const start = Date.now(); 6 | await sleep(2000); 7 | t.true(Date.now() >= start + 2000); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/hayspec-spec/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "sourceMap": true, 8 | "outDir": "dist", 9 | "declaration": true, 10 | "lib": ["es2015.promise", "es2015.iterable", "es5"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /rush.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json", 3 | "rushVersion": "5.5.1", 4 | "npmVersion": "7.8.0", 5 | "nodeSupportedVersionRange": ">=11.0.0", 6 | "gitPolicy": {}, 7 | "repository": {}, 8 | "eventHooks": { 9 | "preRushInstall": [], 10 | "postRushInstall": [], 11 | "preRushBuild": [], 12 | "postRushBuild": [] 13 | }, 14 | "projects": [ 15 | { 16 | "packageName": "@hayspec/cli", 17 | "projectFolder": "packages/hayspec-cli", 18 | "versionPolicyName": "patchAll" 19 | }, 20 | { 21 | "packageName": "@hayspec/init", 22 | "projectFolder": "packages/hayspec-init", 23 | "versionPolicyName": "patchAll" 24 | }, 25 | { 26 | "packageName": "@hayspec/reporter", 27 | "projectFolder": "packages/hayspec-reporter", 28 | "versionPolicyName": "patchAll" 29 | }, 30 | { 31 | "packageName": "@hayspec/runner", 32 | "projectFolder": "packages/hayspec-runner", 33 | "versionPolicyName": "patchAll" 34 | }, 35 | { 36 | "packageName": "@hayspec/spec", 37 | "projectFolder": "packages/hayspec-spec", 38 | "versionPolicyName": "patchAll" 39 | } 40 | ] 41 | } 42 | --------------------------------------------------------------------------------