├── .all-contributorsrc
├── .eslintignore
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── release.yml
├── .gitignore
├── .npmrc
├── .nycrc
├── .prettierrc
├── .yarnrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── for-readme
└── test-angular.jpg
├── lerna.json
├── package.json
├── packages
├── jasmine-single
│ ├── .all-contributorsrc
│ ├── .core-version
│ ├── .npmignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jasmine.json
│ ├── package.json
│ ├── spec
│ │ └── jasmine-single.spec.ts
│ ├── tsconfig.build.json
│ └── tsconfig.json
├── jest-single
│ ├── .all-contributorsrc
│ ├── .core-version
│ ├── .npmignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── spec
│ │ └── jest-single.spec.ts
│ ├── tsconfig.build.json
│ └── tsconfig.json
└── karma-jasmine-single
│ ├── .all-contributorsrc
│ ├── .npmignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── karma.conf.js
│ ├── lib
│ ├── karma-jasmine-single.js
│ └── karma-jasmine-single.spec.js
│ └── package.json
├── shared
└── single-core
│ └── single-core.ts
├── update-core-hash.js
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "single",
3 | "projectOwner": "hirezio",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": true,
11 | "commitConvention": "angular",
12 | "contributors": [
13 | {
14 | "login": "shairez",
15 | "name": "Shai Reznik",
16 | "avatar_url": "https://avatars1.githubusercontent.com/u/1430726?v=4",
17 | "profile": "https://www.hirez.io/?utm_medium=Open_Source&utm_source=Github&utm_campaign=Lead_Generation&utm_content=single--all-contributors-profile-link",
18 | "contributions": [
19 | "code",
20 | "doc",
21 | "ideas",
22 | "infra",
23 | "maintenance",
24 | "mentoring",
25 | "review",
26 | "test"
27 | ]
28 | },
29 | {
30 | "login": "maartentibau",
31 | "name": "Maarten Tibau",
32 | "avatar_url": "https://avatars.githubusercontent.com/u/4103756?v=4",
33 | "profile": "https://www.webtrix.be",
34 | "contributions": [
35 | "doc",
36 | "infra"
37 | ]
38 | },
39 | {
40 | "login": "benjamingr",
41 | "name": "Benjamin Gruenbaum",
42 | "avatar_url": "https://avatars.githubusercontent.com/u/1315533?v=4",
43 | "profile": "https://stackoverflow.com/users/1348195/benjamin-gruenbaum",
44 | "contributions": [
45 | "code",
46 | "ideas",
47 | "maintenance"
48 | ]
49 | },
50 | {
51 | "login": "awarrington0895",
52 | "name": "Alex ",
53 | "avatar_url": "https://avatars.githubusercontent.com/u/12807806?v=4",
54 | "profile": "https://github.com/awarrington0895",
55 | "contributions": [
56 | "code",
57 | "maintenance"
58 | ]
59 | }
60 | ],
61 | "contributorsPerLine": 7,
62 | "skipCi": true,
63 | "commitType": "docs"
64 | }
65 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | coverage
4 | .eslintrc.*
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | plugins: [
5 | '@typescript-eslint',
6 | ],
7 | extends: [
8 | 'eslint:recommended',
9 | 'plugin:@typescript-eslint/recommended',
10 | "prettier"
11 | ],
12 | rules: {
13 | '@typescript-eslint/no-inferrable-types': "off",
14 | '@typescript-eslint/no-explicit-any': "off",
15 | 'no-undef': "off"
16 | },
17 | overrides: [
18 | {
19 | files: ["**/*.spec.ts"],
20 | rules: {
21 | "@typescript-eslint/no-explicit-any": "off",
22 | "@typescript-eslint/no-unused-vars": "off",
23 | "@typescript-eslint/ban-types": "off",
24 | "@typescript-eslint/no-empty-function": "off"
25 | }
26 | }
27 | ],
28 |
29 | };
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 |
28 | - OS: [e.g. iOS]
29 | - Browser [e.g. chrome, safari]
30 | - Version [e.g. 22]
31 |
32 | **Smartphone (please complete the following information):**
33 |
34 | - Device: [e.g. iPhone6]
35 | - OS: [e.g. iOS8.1]
36 | - Browser [e.g. stock browser, safari]
37 | - Version [e.g. 22]
38 |
39 | **Additional context**
40 | Add any other context about the problem here.
41 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | push:
4 | branches:
5 | - main
6 | - staging
7 | pull_request:
8 | jobs:
9 | build:
10 | name: Build and test on node ${{ matrix.node }}
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | node: ['14', '16']
15 | env:
16 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
18 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
19 | steps:
20 | - name: Checkout Repo
21 | uses: actions/checkout@v2
22 | with:
23 | fetch-depth: '0'
24 | - name: Setup Node
25 | uses: actions/setup-node@v2-beta
26 | with:
27 | node-version: ${{ matrix.node }}
28 | registry-url: 'https://registry.npmjs.org'
29 | - name: Get yarn cache dir
30 | id: yarn-cache-dir-path
31 | run: echo "::set-output name=dir::$(yarn cache dir)"
32 |
33 | - name: Cache Dependencies
34 | uses: actions/cache@v2
35 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
36 | with:
37 | path: |
38 | ${{ steps.yarn-cache-dir-path.outputs.dir }}
39 | node_modules
40 | */*/node_modules
41 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
42 | restore-keys: |
43 | ${{ runner.os }}-yarn-
44 |
45 | - name: Install dependencies
46 | run: yarn install --frozen-lockfile
47 |
48 | - name: Build
49 | run: yarn build
50 |
51 | - name: Run Lint and Tests
52 | run: yarn test:full
53 |
54 | release:
55 | name: Release
56 | if: github.event_name == 'push' && github.ref == 'refs/heads/main'
57 | needs: build
58 | runs-on: ubuntu-latest
59 |
60 | env:
61 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
63 | steps:
64 | - name: Checkout Repo
65 | uses: actions/checkout@v2
66 | with:
67 | fetch-depth: '0'
68 |
69 | - name: Setup Node
70 | uses: actions/setup-node@v2
71 | with:
72 | node-version: '16'
73 | registry-url: 'https://registry.npmjs.org'
74 | scope: '@hirez_io'
75 |
76 | - name: Get yarn cache dir
77 | id: yarn-cache-dir-path
78 | run: echo "::set-output name=dir::$(yarn cache dir)"
79 |
80 | - name: Cache Dependencies
81 | uses: actions/cache@v2
82 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
83 | with:
84 | path: |
85 | ${{ steps.yarn-cache-dir-path.outputs.dir }}
86 | node_modules
87 | */*/node_modules
88 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
89 | restore-keys: |
90 | ${{ runner.os }}-yarn-
91 |
92 | - name: Install dependencies
93 | run: yarn install --frozen-lockfile
94 |
95 | - name: Build
96 | run: yarn build
97 |
98 | - name: Run Lint and Tests
99 | run: yarn test:full
100 |
101 | - name: Upload coverage reports
102 | uses: codecov/codecov-action@v1
103 | with:
104 | files: ./packages/jasmine-single/coverage/lcov.info, ./packages/jest-single/coverage/lcov.info
105 |
106 | - name: Configure CI Git User
107 | run: |
108 | git config --global user.name '@hirezio'
109 | git config --global user.email 'hirezio@users.noreply.github.com'
110 |
111 | - name: Update Version
112 | if: github.event_name == 'push' && github.ref == 'refs/heads/main'
113 | run: yarn lerna:version:ci
114 |
115 | - name: Check Authentication with Registry
116 | env:
117 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
118 | run: npm whoami
119 |
120 | - name: Publish to npm
121 | if: github.event_name == 'push' && github.ref == 'refs/heads/main'
122 | env:
123 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
124 | run: yarn lerna:publish
125 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | temp-src
4 | coverage
5 | .nyc_output
6 | src/**/*.js*
7 | **/*.d.ts
8 | .vscode
9 | .cache
10 | .idea
11 | TODO
12 | yarn-error.log
13 | lerna-debug.log
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
--------------------------------------------------------------------------------
/.nycrc:
--------------------------------------------------------------------------------
1 | {
2 | "report-dir": "packages/jasmine-single/coverage",
3 | "temp-dir": "packages/jasmine-single/.nyc_output",
4 | "check-coverage": true,
5 |
6 | "include": [
7 | "shared/single-core/single-core.ts"
8 | ],
9 | "branches": 100,
10 | "functions": 90,
11 | "lines": 90,
12 | "statements": 90
13 | }
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 90,
3 | "singleQuote": true,
4 | "useTabs": false,
5 | "tabWidth": 2,
6 | "semi": true,
7 | "bracketSpacing": true
8 | }
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | save-prefix ""
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | We as members, contributors, and leaders pledge to make participation in our
7 | community a harassment-free experience for everyone, regardless of age, body
8 | size, visible or invisible disability, ethnicity, sex characteristics, gender
9 | identity and expression, level of experience, education, socio-economic status,
10 | nationality, personal appearance, race, religion, or sexual identity
11 | and orientation.
12 |
13 | We pledge to act and interact in ways that contribute to an open, welcoming,
14 | diverse, inclusive, and healthy community.
15 |
16 | ## Our Standards
17 |
18 | Examples of behavior that contributes to a positive environment for our
19 | community include:
20 |
21 | * Demonstrating empathy and kindness toward other people
22 | * Being respectful of differing opinions, viewpoints, and experiences
23 | * Giving and gracefully accepting constructive feedback
24 | * Accepting responsibility and apologizing to those affected by our mistakes,
25 | and learning from the experience
26 | * Focusing on what is best not just for us as individuals, but for the
27 | overall community
28 |
29 | Examples of unacceptable behavior include:
30 |
31 | * The use of sexualized language or imagery, and sexual attention or
32 | advances of any kind
33 | * Trolling, insulting or derogatory comments, and personal or political attacks
34 | * Public or private harassment
35 | * Publishing others' private information, such as a physical or email
36 | address, without their explicit permission
37 | * Other conduct which could reasonably be considered inappropriate in a
38 | professional setting
39 |
40 | ## Enforcement Responsibilities
41 |
42 | Community leaders are responsible for clarifying and enforcing our standards of
43 | acceptable behavior and will take appropriate and fair corrective action in
44 | response to any behavior that they deem inappropriate, threatening, offensive,
45 | or harmful.
46 |
47 | Community leaders have the right and responsibility to remove, edit, or reject
48 | comments, commits, code, wiki edits, issues, and other contributions that are
49 | not aligned to this Code of Conduct, and will communicate reasons for moderation
50 | decisions when appropriate.
51 |
52 | ## Scope
53 |
54 | This Code of Conduct applies within all community spaces, and also applies when
55 | an individual is officially representing the community in public spaces.
56 | Examples of representing our community include using an official e-mail address,
57 | posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event.
59 |
60 | ## Enforcement
61 |
62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
63 | reported to the community leaders responsible for enforcement at
64 | conduct@hirez.io.
65 | All complaints will be reviewed and investigated promptly and fairly.
66 |
67 | All community leaders are obligated to respect the privacy and security of the
68 | reporter of any incident.
69 |
70 | ## Enforcement Guidelines
71 |
72 | Community leaders will follow these Community Impact Guidelines in determining
73 | the consequences for any action they deem in violation of this Code of Conduct:
74 |
75 | ### 1. Correction
76 |
77 | **Community Impact**: Use of inappropriate language or other behavior deemed
78 | unprofessional or unwelcome in the community.
79 |
80 | **Consequence**: A private, written warning from community leaders, providing
81 | clarity around the nature of the violation and an explanation of why the
82 | behavior was inappropriate. A public apology may be requested.
83 |
84 | ### 2. Warning
85 |
86 | **Community Impact**: A violation through a single incident or series
87 | of actions.
88 |
89 | **Consequence**: A warning with consequences for continued behavior. No
90 | interaction with the people involved, including unsolicited interaction with
91 | those enforcing the Code of Conduct, for a specified period of time. This
92 | includes avoiding interactions in community spaces as well as external channels
93 | like social media. Violating these terms may lead to a temporary or
94 | permanent ban.
95 |
96 | ### 3. Temporary Ban
97 |
98 | **Community Impact**: A serious violation of community standards, including
99 | sustained inappropriate behavior.
100 |
101 | **Consequence**: A temporary ban from any sort of interaction or public
102 | communication with the community for a specified period of time. No public or
103 | private interaction with the people involved, including unsolicited interaction
104 | with those enforcing the Code of Conduct, is allowed during this period.
105 | Violating these terms may lead to a permanent ban.
106 |
107 | ### 4. Permanent Ban
108 |
109 | **Community Impact**: Demonstrating a pattern of violation of community
110 | standards, including sustained inappropriate behavior, harassment of an
111 | individual, or aggression toward or disparagement of classes of individuals.
112 |
113 | **Consequence**: A permanent ban from any sort of public interaction within
114 | the community.
115 |
116 | ## Attribution
117 |
118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119 | version 2.0, available at
120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
121 |
122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
123 | enforcement ladder](https://github.com/mozilla/diversity).
124 |
125 | [homepage]: https://www.contributor-covenant.org
126 |
127 | For answers to common questions about this code of conduct, see the FAQ at
128 | https://www.contributor-covenant.org/faq. Translations are available at
129 | https://www.contributor-covenant.org/translations.
130 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guidelines
2 |
3 | We would love for you to contribute to this project.
4 | As a contributor, here are the guidelines we would like you to follow:
5 |
6 | ## Be Kind - Code of Conduct
7 |
8 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md)
9 |
10 | ## Found a bug? Want a feature? - Please submit an Issue
11 |
12 | [Choose an issue template](https://github.com/hirezio/single/issues/new/choose) to file a bug report / feature request.
13 |
14 | ## Want to contribute code? please submit a Pull Request (PR), but first...
15 |
16 | .
17 |
18 | ### ✅ 1. [Search this repo first](https://github.com/hirezio/single/pulls)...
19 |
20 | for an open or closed PR that relates to the change you want to introduce.
21 |
22 | .
23 |
24 | ### ✅ 2. **Before you start coding - find / create an issue**
25 |
26 | **Make sure there's an issue** describing the problem you're fixing, or documents the design for the feature you'd like to add.
27 | Discussing the design up front helps to ensure that we're ready to accept your work.
28 |
29 | **Don't waste your time working on code before you got a 👍 in an issue comment.**
30 |
31 | .
32 |
33 | ### ✅ 3. Fork the this repo and create a branch.
34 |
35 | - Hit that "Fork" button above (in this repo's github page).
36 |
37 | 
38 |
39 | - git clone your fork
40 |
41 | `git clone YOUR_FORK_URL`
42 |
43 | Get your url by from here 👇
44 |
45 | 
46 |
47 | - Create a new branch locally in your fork's repo
48 |
49 | ```shell
50 | git checkout -b my-fix-branch main
51 | ```
52 |
53 | ⚠ **IMPORTANT:** In this project the most important file is `shared/single-core/single-core.ts` -
54 | It is the actual implementation of both `jasmine-single` and `jest-single`.
55 | So if you change anything you need to verify that there is a test in both of the frameworks spec files.
56 |
57 | .
58 |
59 | ### ✅ 4. Make sure you add / modify tests
60 |
61 | Run `yarn test:full` to make sure there aren't any errors
62 |
63 | .
64 |
65 | ### ✅ 5. Commit your changes using commitizen:
66 |
67 | Instead of `git commit` use the following command:
68 |
69 | ```shell
70 | yarn commit
71 | ```
72 |
73 | It will then ask you a bunch of questions.
74 |
75 | **For "scope" please choose from the following options:**
76 |
77 | | Scope name | Description |
78 | | ------------------- | --------------------------------------------------- |
79 | | core | a change related to the file `single-core.ts` |
80 | | jest-single | a change related to `@hirez_io/jest-single` |
81 | | jasmine-single | a change related to `@hirez_io/jasmine-single` |
82 | | karma-jasmine-single | a change related to `@hirez_io/karma-jasmine-single` |
83 | | global | any change that doesn't fall under the above scopes |
84 |
85 | This will create a descriptive commit message that follows the
86 | [Angular commit message convention](#commit-message-format).
87 |
88 | This is necessary to generate meaningful release notes / CHANGELOG automatically.
89 |
90 | .
91 |
92 | ### ✅ 6. Push your branch to GitHub:
93 |
94 | ```shell
95 | git push origin my-fix-branch
96 | ```
97 |
98 | .
99 |
100 | ### ✅ 7. In GitHub, create a pull request for `hirezio/single:main`.
101 |
102 | Make sure you check the following checkbox "Allow edits from maintainers" -
103 |
104 | 
105 |
106 | If you need to update your PR for some reason -
107 |
108 | - Make the required updates.
109 |
110 | - Re-run the tests to ensure tests are still passing `yarn test:full`
111 |
112 | - Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
113 |
114 | ```shell
115 | git rebase main -i
116 | git push -f
117 | ```
118 |
119 | .
120 |
121 | ### ✅ 8. After your pull request is merged - delete your PR branch
122 |
123 | After your pull request is merged, you can safely delete your branch and pull the changes from the main (upstream) repository:
124 |
125 | - Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:
126 |
127 | ```shell
128 | git push origin --delete my-fix-branch
129 | ```
130 |
131 | - Check out the main branch:
132 |
133 | ```shell
134 | git checkout main -f
135 | ```
136 |
137 | - Delete the local branch:
138 |
139 | ```shell
140 | git branch -D my-fix-branch
141 | ```
142 |
143 | - Update your main with the latest upstream version:
144 |
145 | ```shell
146 | git pull --ff upstream main
147 | ```
148 |
149 | .
150 |
151 | ### ✅ 9. That's it! Thank you for your contribution! 🙏💓
152 |
153 | [commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
154 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Shai Reznik, HiRez.io
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # (jasmine | jest)-single Monorepo
2 |
3 |
4 | [](#contributors-)
5 |
6 |
7 | ## Packages
8 |
9 | This repository contains the HiRez.io's versions of:
10 |
11 | | Project | Status | Description |
12 | | ------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------- |
13 | | [@hirez_io/jest-single] | [![@hirez_io/jest-single-status]][@hirez_io/jest-single-package] | Jest addon that adds the "given when then" syntax |
14 | | [@hirez_io/jasmine-single] | [![@hirez_io/jasmine-single-status]][@hirez_io/jasmine-single-package] | Jasmine addon that adds the "given when then" syntax |
15 | | [@hirez_io/karma-jasmine-single] | [![@hirez_io/karma-jasmine-single-status]][@hirez_io/karma-jasmine-single-package] | Karma plugin for `@hirez_io/jasmine-single` |
16 |
17 | [@hirez_io/jest-single]: https://github.com/hirezio/single/tree/main/packages/jest-single
18 | [@hirez_io/jasmine-single]: https://github.com/hirezio/single/tree/main/packages/jasmine-single
19 | [@hirez_io/karma-jasmine-single]: https://github.com/hirezio/single/tree/main/packages/karma-jasmine-single
20 | [@hirez_io/jest-single-status]: https://img.shields.io/npm/v/@hirez_io/jest-single.svg
21 | [@hirez_io/jest-single-package]: https://npmjs.com/package/@hirez_io/jest-single
22 | [@hirez_io/jasmine-single-status]: https://img.shields.io/npm/v/@hirez_io/jasmine-single.svg
23 | [@hirez_io/jasmine-single-package]: https://npmjs.com/package/@hirez_io/jasmine-single
24 | [@hirez_io/karma-jasmine-single-status]: https://img.shields.io/npm/v/@hirez_io/karma-jasmine-single.svg
25 | [@hirez_io/karma-jasmine-single-package]: https://npmjs.com/package/@hirez_io/karma-jasmine-single
26 |
27 | ## Contributing
28 |
29 | Want to contribute? Yayy! 🎉
30 |
31 | Please read and follow our [Contributing Guidelines](CONTRIBUTING.md) to learn what are the right steps to take before contributing your time, effort and code.
32 |
33 | Thanks 🙏
34 |
35 | ## Code Of Conduct
36 |
37 | Be kind to each other and please read our [code of conduct](CODE_OF_CONDUCT.md).
38 |
39 |
40 |
41 |
42 | ## Contributors ✨
43 |
44 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
45 |
46 |
47 |
48 |
49 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
66 |
67 |
68 |
69 | ## License
70 |
71 | MIT
72 |
73 | ## Want to learn more?
74 |
75 |
83 |
--------------------------------------------------------------------------------
/for-readme/test-angular.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hirezio/single/9a0d2ef38d2a07fadb7fb3be5fe6e1cf226b084d/for-readme/test-angular.jpg
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": ["packages/*"],
3 | "npmClient": "yarn",
4 | "useWorkspaces": true,
5 | "version": "independent",
6 | "command": {
7 | "publish": {
8 | "conventionalCommits": true
9 | },
10 | "version": {
11 | "conventionalCommits": true,
12 | "conventionalGraduate":"*",
13 | "push": true,
14 | "gitTagVersion": true,
15 | "createRelease": "github",
16 | "allowBranch": "main",
17 | "message": "chore(release): publish new version"
18 | }
19 | },
20 | "ignoreChanges": ["**/*.md", "**/*.spec.ts", "**/*.spec.js", "**/.all-contributorsrc"]
21 | }
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "single",
3 | "version": "0.0.0",
4 | "author": {
5 | "name": "Shai Reznik",
6 | "company": "HiRez.io"
7 | },
8 | "license": "MIT",
9 | "private": true,
10 | "scripts": {
11 | "commit": "git-cz",
12 | "build": "lerna run build",
13 | "format:fix": "pretty-quick --staged",
14 | "lerna:publish": "lerna publish from-git --yes",
15 | "lerna:version": "lerna version",
16 | "lerna:version:ci": "lerna version --yes",
17 | "lint": "eslint . --ext .js,.ts",
18 | "lint:commitmsg": "commitlint -E HUSKY_GIT_PARAMS",
19 | "test": "run-s test:jasmine:coverage test:jest",
20 | "test:full": "run-s lint test",
21 | "test:jasmine": "ts-node --project packages/jasmine-single/tsconfig.json -r tsconfig-paths/register node_modules/jasmine/bin/jasmine.js JASMINE_CONFIG_PATH=packages/jasmine-single/jasmine.json",
22 | "test:jasmine:coverage": "nyc -r lcov --r text-summary -e .ts -x \"**/*.spec.ts\" yarn test:jasmine",
23 | "test:jasmine:watch": "nodemon --ext ts --watch \"shared/single-core/**/*.ts\" --watch \"packages/jasmine-single/**/*.ts\" --exec \"yarn test:jasmine:coverage\"",
24 | "test:jest": "jest -c packages/jest-single/jest.config.js",
25 | "test:jest:watch": "jest -c packages/jest-single/jest.config.js --watchAll",
26 | "update-core-hash": "node update-core-hash"
27 | },
28 | "workspaces": [
29 | "packages/*"
30 | ],
31 | "config": {
32 | "commitizen": {
33 | "path": "./node_modules/cz-conventional-changelog"
34 | }
35 | },
36 | "husky": {
37 | "hooks": {
38 | "pre-commit": "yarn format:fix",
39 | "commit-msg": "yarn lint:commitmsg",
40 | "post-commit": "yarn update-core-hash"
41 | }
42 | },
43 | "commitlint": {
44 | "extends": [
45 | "@commitlint/config-conventional"
46 | ]
47 | },
48 | "devDependencies": {
49 | "@commitlint/cli": "16.2.1",
50 | "@commitlint/config-conventional": "16.2.1",
51 | "@types/node": "17.0.21",
52 | "@typescript-eslint/eslint-plugin": "5.14.0",
53 | "@typescript-eslint/parser": "5.14.0",
54 | "all-contributors-cli": "6.20.0",
55 | "commitizen": "4.2.4",
56 | "cz-conventional-changelog": "3.3.0",
57 | "doctoc": "2.1.0",
58 | "eslint": "8.11.0",
59 | "eslint-config-prettier": "8.5.0",
60 | "execa": "6.1.0",
61 | "husky": "7.0.4",
62 | "lerna": "^4.0.0",
63 | "npm-run-all": "^4.1.5",
64 | "prettier": "2.5.1",
65 | "pretty-quick": "3.1.3",
66 | "rimraf": "^3.0.2",
67 | "ts-node": "10.7.0",
68 | "typescript": "4.6.2"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/packages/jasmine-single/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "single",
3 | "projectOwner": "hirezio",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md",
8 | "../../README.md"
9 | ],
10 | "imageSize": 100,
11 | "commit": true,
12 | "commitConvention": "angular",
13 | "contributors": [
14 | {
15 | "login": "shairez",
16 | "name": "Shai Reznik",
17 | "avatar_url": "https://avatars1.githubusercontent.com/u/1430726?v=4",
18 | "profile": "https://www.hirez.io/?utm_medium=Open_Source&utm_source=Github&utm_campaign=Lead_Generation&utm_content=jasmine-single--all-contributors-profile-link",
19 | "contributions": [
20 | "code",
21 | "doc",
22 | "ideas",
23 | "infra",
24 | "maintenance",
25 | "mentoring",
26 | "review",
27 | "test"
28 | ]
29 | },
30 | {
31 | "login": "maartentibau",
32 | "name": "Maarten Tibau",
33 | "avatar_url": "https://avatars.githubusercontent.com/u/4103756?v=4",
34 | "profile": "https://www.webtrix.be",
35 | "contributions": [
36 | "doc",
37 | "infra"
38 | ]
39 | },
40 | {
41 | "login": "benjamingr",
42 | "name": "Benjamin Gruenbaum",
43 | "avatar_url": "https://avatars.githubusercontent.com/u/1315533?v=4",
44 | "profile": "https://stackoverflow.com/users/1348195/benjamin-gruenbaum",
45 | "contributions": [
46 | "code",
47 | "ideas",
48 | "maintenance"
49 | ]
50 | }
51 | ],
52 | "contributorsPerLine": 7
53 | }
54 |
--------------------------------------------------------------------------------
/packages/jasmine-single/.core-version:
--------------------------------------------------------------------------------
1 | 3f24aa9551ab834f07b3464b2043721fb3b55edf
--------------------------------------------------------------------------------
/packages/jasmine-single/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | .nyc_output
--------------------------------------------------------------------------------
/packages/jasmine-single/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | ## [1.1.6](https://github.com/hirezio/single/compare/@hirez_io/jasmine-single@1.1.5...@hirez_io/jasmine-single@1.1.6) (2023-08-25)
7 |
8 | **Note:** Version bump only for package @hirez_io/jasmine-single
9 |
10 |
11 |
12 |
13 |
14 | ## [1.1.5](https://github.com/hirezio/single/compare/@hirez_io/jasmine-single@1.1.4...@hirez_io/jasmine-single@1.1.5) (2022-06-04)
15 |
16 |
17 | ### Bug Fixes
18 |
19 | * **global:** fix fgiven description ([dc75bd9](https://github.com/hirezio/single/commit/dc75bd9ff24b6f4f5b50ed734e1ffed99b9c46ae))
20 | * **global:** fix unhandled promise exceptions bug ([06f4934](https://github.com/hirezio/single/commit/06f4934c607aeec06520874a2563e2bd002a1337))
21 |
22 |
23 |
24 |
25 |
26 | ## [1.1.4](https://github.com/hirezio/single/compare/@hirez_io/jasmine-single@1.1.3...@hirez_io/jasmine-single@1.1.4) (2022-03-13)
27 |
28 | **Note:** Version bump only for package @hirez_io/jasmine-single
29 |
30 |
31 |
32 |
33 |
34 | ## [1.1.3](https://github.com/hirezio/single/compare/@hirez_io/jasmine-single@1.1.2...@hirez_io/jasmine-single@1.1.3) (2022-03-13)
35 |
36 | **Note:** Version bump only for package @hirez_io/jasmine-single
37 |
38 |
39 |
40 |
41 |
42 | ## [1.1.2](https://github.com/hirezio/single/compare/@hirez_io/jasmine-single@1.1.1...@hirez_io/jasmine-single@1.1.2) (2022-03-02)
43 |
44 | **Note:** Version bump only for package @hirez_io/jasmine-single
45 |
46 |
47 |
48 |
49 |
50 | ## [1.1.1](https://github.com/hirezio/single/compare/@hirez_io/jasmine-single@1.1.0...@hirez_io/jasmine-single@1.1.1) (2021-08-08)
51 |
52 | **Note:** Version bump only for package @hirez_io/jasmine-single
53 |
54 |
55 |
56 |
57 |
58 | # [1.1.0](https://github.com/hirezio/single/compare/@hirez_io/jasmine-single@1.0.0...@hirez_io/jasmine-single@1.1.0) (2021-08-08)
59 |
60 |
61 | ### Features
62 |
63 | * **global:** add support for `done` and update README ([6082c27](https://github.com/hirezio/single/commit/6082c2710153ea0a5288a25457a7a78828a7b48d))
64 |
65 |
66 |
67 |
68 |
69 | # 1.0.0 (2021-07-29)
70 |
71 | **Note:** Version bump only for package @hirez_io/jasmine-single
72 |
--------------------------------------------------------------------------------
/packages/jasmine-single/README.md:
--------------------------------------------------------------------------------
1 | # `@hirez_io/jasmine-single` 📃👌
2 |
3 | A jasmine addon that helps you write 'Single-Action Tests' by breaking them into a **"given / when / then"** structure.
4 |
5 | [](https://www.npmjs.org/package/@hirez_io/jasmine-single)
6 | [](http://npm-stat.com/charts.html?package=@hirez_io/jasmine-single&from=2017-07-26)
7 | [](https://codecov.io/gh/hirezio/single)
8 | 
9 | [](https://lerna.js.org/)
10 | [](../../CODE_OF_CONDUCT.md)
11 | [](https://opensource.org/licenses/MIT)
12 | [](#contributors-)
13 |
14 |
15 |
23 |
24 |
25 |
26 | # Table of Contents
27 |
28 |
29 |
30 |
31 | - [Installation](#installation)
32 |
33 | - [Using TypeScript?](#using-typescript)
34 |
35 | - [Using karma?](#using-karma)
36 |
37 | - [What are "single-action" tests?](#what-are-single-action-tests)
38 |
39 | - [Why writing single-action tests is good?](#why-writing-single-action-tests-is-good)
40 |
41 | - [How to write single-action tests?](#how-to-write-single-action-tests)
42 |
43 | - [What's wrong with using `it()` for single-action tests?](#whats-wrong-with-using-it-for-single-action-tests)
44 |
45 | - [Usage](#usage)
46 |
47 | - [▶ The basic testing structure](#%E2%96%B6-the-basic-testing-structure)
48 |
49 | - [▶ Meaningful error messages](#%E2%96%B6-meaningful-error-messages)
50 |
51 | - [▶ `async` / `await` support](#%E2%96%B6-async--await-support)
52 |
53 | - [▶ `done()` function support](#%E2%96%B6-done-function-support)
54 | - [Contributing](#contributing)
55 | - [Code Of Conduct](#code-of-conduct)
56 | - [License](#license)
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | ## Installation
65 |
66 | ```
67 | yarn add -D @hirez_io/jasmine-single
68 | ```
69 |
70 | or
71 |
72 | ```
73 | npm install -D @hirez_io/jasmine-single
74 | ```
75 |
76 | ### Using TypeScript?
77 |
78 |
79 | ⚠ CLICK HERE TO EXPAND
80 |
81 |
82 |
83 | You should add `@hirez_io/jasmine-single` to your `types` property under `compilerOptions` in your `tsconfig.json` (or `tsconfig.spec.json`) like this:
84 |
85 | ```js
86 | // tsconfig.json or tsconfig.spec.json
87 |
88 | {
89 | ...
90 | "compilerOptions": {
91 | "types": [
92 | "jasmine",
93 | "@hirez_io/jasmine-single", // 👈 ADD THIS
94 |
95 | // ...any other types you might have...
96 | ],
97 | ...
98 | }
99 | ...
100 | }
101 | ```
102 |
103 | ⚠ **ATTENTION:** If you have `typeRoots` configured like this -
104 |
105 | ```ts
106 | "compilerOptions": {
107 | "typeRoots": [
108 | "node_modules/@types"
109 | ],
110 | }
111 | ```
112 |
113 | You should add `"node_modules"` like this -
114 |
115 | ```ts
116 | "compilerOptions": {
117 | "typeRoots": [
118 | "node_modules/@types",
119 | "node_modules/@hirez_io" // 👈 ADD THIS
120 | ],
121 | }
122 | ```
123 |
124 | or else it won't find `@hirez_io/jasmine-single` global types.
125 | ### ⚠ **VS CODE USERS:**
126 |
127 | Add the above configuration (`types` and/or `typeRoots`) to your `tsconfig.json` specifically or else it would not recognize the global types.
128 |
129 |
130 |
131 |
132 |
133 | ### Using karma?
134 |
135 |
136 | ⚠ CLICK HERE TO EXPAND
137 |
138 |
139 |
140 | `@hirez_io/jasmine-single` has a dependency on `@hirez_io/karma-jasmine-single` which is a karma plugin (inspired by [karma-jasmine-given](https://github.com/kirisu/karma-jasmine-given)) I rewrote to save you the hassle of loading the library script yourself.
141 |
142 | So it will automatically installs `@hirez_io/karma-jasmine-single` for you 😎
143 |
144 | Here's how to modify your `karma.conf.js`:
145 |
146 | ```js
147 | // karma.conf.js
148 |
149 | module.exports = function(config) {
150 | config.set({
151 |
152 | plugins: [
153 | require('karma-jasmine'),
154 | require('@hirez_io/karma-jasmine-single'), // 👈 ADD THIS
155 | require('karma-chrome-launcher')
156 | // other plugins you might have...
157 | ],
158 |
159 | frameworks: [
160 | '@hirez_io/jasmine-single', // 👈 ADD THIS
161 | 'jasmine',
162 | // other frameworks...
163 | ],
164 |
165 | // ...
166 | ```
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | ## What are "single-action" tests?
175 |
176 | A single-action test is a test with only one action. (CAPTAIN OBVIOUS! 🦸♂️😅)
177 |
178 | Normally, you setup the environment, you call an action and then you check the output.
179 |
180 | #### What's an action?
181 |
182 | Well... it can be a method call, a button click or anything else our test is checking.
183 |
184 | The big idea here is that it should be only **ONE ACTION PER TEST**.
185 |
186 |
187 |
188 | ## Why writing single-action tests is good?
189 |
190 | Single action tests are more "Test Effective" compared to multi-action tests.
191 |
192 | The benefits of single-action tests:
193 |
194 | ✅ Your tests will **break less often** (making them more effective)
195 |
196 | ✅ Whenever something breaks, you have **only one "action" code to debug**
197 |
198 | ✅ They promote **better coverage** (easier to see which cases are still missing)
199 |
200 | ✅ They give you **better structure** (every part of your test has a clear goal)
201 |
202 |
203 |
204 |
205 | ## How to write single-action tests?
206 |
207 | This library follows the natural "`given`, `when` and `then`" structure (some of you know it as "Arrange, Act, Assert").
208 |
209 | This means every test has only 3 parts to it, no more.
210 |
211 | ```ts
212 | describe('addTwo', () => {
213 |
214 | // This is where you setup your environment / inputs
215 | given('first number is 1', () => {
216 | const firstNum = 1;
217 |
218 | // This is where you call the action under test
219 | when('adding 2 to the first number', () => {
220 | const actualResult = addTwo(firstNum);
221 |
222 | // This is where you check the outcome
223 | then('result should be 3', () => {
224 | expect(actualResult).toEqual(3);
225 | });
226 | });
227 | });
228 |
229 | });
230 |
231 | ```
232 |
233 | It also prints a nicer test description when there's an error:
234 |
235 | ```
236 | CONSOLE OUTPUT:
237 | ~~~~~~~~~~~~~~
238 |
239 | GIVEN first number is 1
240 | WHEN adding 2 to the first number
241 | THEN result should be 3
242 | ```
243 |
244 |
245 |
246 | ## What's wrong with using `it()` for single-action tests?
247 |
248 | Did you know that the most common way of writing JavaScript tests dates back to 2005? 😅
249 |
250 | Jasmine, which was created in 2009 was inspired by Ruby's testing framework - RSpec which was created in 2005.
251 |
252 |
253 | Originally, RSpec introduced the syntax of "`describe` > `context` > `it`", where `context` was meant to be used as the "setup" part of the test.
254 |
255 | Unfortunately, the `context` wasn't ported to Jasmine so we got used to writing our tests in the "`describe` > `it`" structure... which is more limited.
256 |
257 |
258 |
259 | _Here are a couple of limitations with the common `it()` structure:_
260 |
261 | ### ❌ 1. It promotes partial or awkward descriptions of tests
262 |
263 | The word "it" kinda forces you to begin the description with "should" which leads to focusing specifically on just the "outcome" part of the test (the `then`).
264 |
265 | But if you want to add more context (like what should be the input that causes that outcome) things start to get messy.
266 |
267 | Because there isn't a clear convention, people tend to invent their own on the fly which creates inconsistency.
268 |
269 |
270 | **Example:**
271 | ```ts
272 | it('should do X only when environment is Y and also called by Z But only if...you get the point', ()=> ...)
273 | ```
274 |
275 |
276 |
277 | ### ❌ 2. Nothing prevents you from writing multi-action tests
278 |
279 | This mixes up testing structures and making them harder to understand
280 |
281 | **Example:**
282 | ```ts
283 | it('should transform the products', ()=> {
284 |
285 | // SETUP
286 | const fakeProducts = [...];
287 |
288 | // ACTION
289 | const result = classUnderTest.transformProducts(fakeProducts);
290 |
291 | // OUTCOME
292 | const transformedProducts = [...];
293 | expect(result).toEqual(transformedProducts);
294 |
295 | // ACTION
296 | const result2 = classUnderTest.transformProducts();
297 |
298 | // OUTCOME
299 | expect(result2).toEqual( [] );
300 |
301 |
302 | // this 👆 is a multi-action test.
303 |
304 | })
305 | ```
306 |
307 |
308 | ### ❌ 3. Detailed descriptions can get out of date more easily
309 |
310 | The farther the description is from the actual implementation the less likely you'll remember to update it when the test code changes
311 |
312 | **Example:**
313 | ```ts
314 | test('GIVEN valid products and metadata returned successfully WHEN destroying the products THEN they should get decorated', ()=> {
315 |
316 | const fakeProducts = [...];
317 | const fakeMetadata = [...];
318 | mySpy.getMetadata.and.returnValue(fakeMetadata);
319 |
320 | const result = classUnderTest.transformProducts(fakeProducts);
321 |
322 | const decoratedProducts = [...];
323 | expect(result).toEqual(decoratedProducts);
324 |
325 | })
326 | ```
327 |
328 |
329 | #### Did you spot the typo? 👆😅
330 |
331 |
332 | (it should be _"transforming"_ instead of _"destroying"_)
333 |
334 |
335 |
336 | Compare that to -
337 |
338 | ```ts
339 |
340 | given('valid products and metadata returned successfully', () => {
341 | const fakeProducts = [...];
342 | const fakeMetadata = [...];
343 | mySpy.getMetadata.and.returnValue(fakeMetadata);
344 |
345 | // 👇 --> easier to spot as it's closer to the implementation
346 | when('destroying the products', () => {
347 | const result = classUnderTest.transformProducts(fakeProducts);
348 |
349 | then('they should get decorated', () => {
350 | const decoratedProducts = [...];
351 | expect(result).toEqual(decoratedProducts);
352 | });
353 | });
354 | });
355 |
356 |
357 | ```
358 |
359 |
360 |
361 |
362 |
363 | ## Usage
364 |
365 | ### ▶ The basic testing structure
366 |
367 | The basic structure is a nesting of these 3 functions:
368 |
369 | ```ts
370 | given(description, () => {
371 | when(description, () => {
372 | then(description, () => {
373 |
374 | })
375 | })
376 | })
377 |
378 |
379 | ```
380 |
381 | **EXAMPLE:**
382 | ```ts
383 | describe('addTwo', () => {
384 |
385 | // This is where you setup your environment / inputs
386 | given('first number is 1', () => {
387 | const firstNum = 1;
388 |
389 | // This is where you call the action under test
390 | when('adding 2 to the first number', () => {
391 | const actualResult = addTwo(firstNum);
392 |
393 | // This is where you check the outcome
394 | then('result should be 3', () => {
395 | expect(actualResult).toEqual(3);
396 | });
397 | });
398 | });
399 |
400 | });
401 |
402 | ```
403 |
404 | Under the hood it creates a regular `it()` test with a combination of all the descriptions:
405 |
406 | ```
407 | CONSOLE OUTPUT:
408 | ~~~~~~~~~~~~~~
409 |
410 | GIVEN first number is 1
411 | WHEN adding 2 to the first number
412 | THEN result should be 3
413 | ```
414 |
415 |
416 |
417 | ### ▶ Meaningful error messages
418 |
419 | This library will throw an error if you deviate from the `given > when > then` structure.
420 |
421 | So you won't be tempted to accidentally turn your single-action test into a multi-action one.
422 |
423 | ```ts
424 | describe('addTwo', () => {
425 |
426 | // 👉 ATTENTION: You cannot start with a "when()" or a "then()"
427 | // the test MUST start with a "given()"
428 |
429 |
430 | given('first number is 1', () => {
431 | const firstNum = 1;
432 |
433 | // 👉 ATTENTION: You cannot add here a "then()" function directly
434 | // or another "given()" function
435 |
436 | when('adding 2 to the first number', () => {
437 | const actualResult = addTwo(firstNum);
438 |
439 | // 👉 ATTENTION: You cannot add here a "given()" function
440 | // or another "when()" function
441 |
442 | then('result should be 3', () => {
443 | expect(actualResult).toEqual(3);
444 |
445 |
446 | // 👉 ATTENTION: You cannot add here a "given()" function
447 | // or a "when()" function or another "then()"
448 | });
449 | });
450 | });
451 |
452 | });
453 |
454 | ```
455 |
456 |
457 | ### ▶ `async` / `await` support
458 |
459 | **Example:**
460 |
461 | ```ts
462 | describe('addTwo', () => {
463 |
464 | given('first number is 1', () => {
465 | const firstNum = 1;
466 | // 👇
467 | when('adding 2 to the first number', async () => {
468 | const actualResult = await addTwo(firstNum);
469 |
470 | then('result should be 3', () => {
471 | expect(actualResult).toEqual(3);
472 | });
473 | });
474 | });
475 |
476 | });
477 |
478 |
479 | ```
480 |
481 | ### ▶ `done()` function support
482 |
483 | The `given` function supports the (old) async callback way of using a `done()` function to signal when the test is completed.
484 |
485 | ```ts
486 | describe('addTwo', () => {
487 | // 👇
488 | given('first number is 1', (done) => {
489 | const firstNum = 1;
490 |
491 | when('adding 2 to the first number', () => {
492 | const actualResult = addTwo(firstNum, function callback() {
493 |
494 | then('result should be 3', () => {
495 | expect(actualResult).toEqual(3);
496 | done();
497 | });
498 |
499 | });
500 | });
501 |
502 | });
503 |
504 | });
505 |
506 |
507 | ```
508 |
509 | ℹ It also supports `done(error)` or `done.fail(error)` for throwing async errors.
510 |
511 |
512 |
513 | ## Contributing
514 |
515 | Want to contribute? Yayy! 🎉
516 |
517 | Please read and follow our [Contributing Guidelines](../../CONTRIBUTING.md) to learn what are the right steps to take before contributing your time, effort and code.
518 |
519 | Thanks 🙏
520 |
521 |
522 |
523 | ## Code Of Conduct
524 |
525 | Be kind to each other and please read our [code of conduct](../../CODE_OF_CONDUCT.md).
526 |
527 |
528 |
529 |
530 | ## Contributors ✨
531 |
532 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
533 |
534 |
535 |
536 |
537 |
544 |
545 |
546 |
547 |
548 |
549 |
550 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
551 |
552 |
553 |
554 | ## License
555 |
556 | MIT
557 |
--------------------------------------------------------------------------------
/packages/jasmine-single/jasmine.json:
--------------------------------------------------------------------------------
1 | {
2 | "spec_dir": "packages/jasmine-single/spec",
3 | "spec_files": [
4 | "**/*[sS]pec.ts"
5 | ],
6 | "helpers": [],
7 | "stopSpecOnExpectationFailure": true,
8 | "stopOnSpecFailure": true,
9 | "random": true
10 | }
11 |
--------------------------------------------------------------------------------
/packages/jasmine-single/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hirez_io/jasmine-single",
3 | "version": "1.1.6",
4 | "publishConfig": {
5 | "access": "public",
6 | "registry": "https://registry.npmjs.org"
7 | },
8 | "author": {
9 | "name": "Shai Reznik",
10 | "company": "HiRez.io"
11 | },
12 | "description": "A Jasmine addon that helps you write 'Single-Action Tests' by breaking them into a given / when / then structure.",
13 | "keywords": [
14 | "jasmine",
15 | "jasmine-single",
16 | "gwt",
17 | "Given When Then",
18 | "Microtests",
19 | "Single Action Tests",
20 | "Angular Tests",
21 | "Testing",
22 | "Unit tests",
23 | "JavasScript Unit Tests",
24 | "TypeScript Unit Tests",
25 | "hirez.io"
26 | ],
27 | "homepage": "https://github.com/hirezio/single/tree/main/packages/jasmine-single",
28 | "repository": {
29 | "type": "git",
30 | "url": "https://github.com/hirezio/single",
31 | "directory": "packages/jasmine-single"
32 | },
33 | "license": "MIT",
34 | "source": "temp-src/jasmine-single.ts",
35 | "main": "dist/jasmine-single.js",
36 | "types": "dist/jasmine-single.d.ts",
37 | "scripts": {
38 | "clean": "rimraf dist",
39 | "clean:temp-src": "rimraf temp-src",
40 | "copy:shared-source": "cpy ../../shared/single-core/single-core.ts ./temp-src/ --rename=jasmine-single.ts",
41 | "compile": "microbundle -f iife --tsconfig tsconfig.build.json --compress false",
42 | "build": "run-s clean copy:shared-source compile clean:temp-src",
43 | "test": "echo \n*** Run tests from the root folder\n"
44 | },
45 | "dependencies": {
46 | "@hirez_io/karma-jasmine-single": "^1.0.4"
47 | },
48 | "peerDependencies": {
49 | "jasmine-core": "< 5.x"
50 | },
51 | "devDependencies": {
52 | "@types/jasmine": "3.10.3",
53 | "cpy-cli": "^3.1.1",
54 | "jasmine": "4.0.2",
55 | "microbundle": "^0.15.0",
56 | "nodemon": "2.0.16",
57 | "nyc": "15.1.0",
58 | "source-map-support": "^0.5.21",
59 | "tsconfig-paths": "3.10.1"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/packages/jasmine-single/spec/jasmine-single.spec.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getOnlyOneError,
3 | getMustBeAChildError,
4 | } from '../../../shared/single-core/single-core';
5 |
6 | const root = (1, eval)('this');
7 |
8 | describe('Jasmine Single', () => {
9 | function addTwo(num: number | undefined) {
10 | if (num) {
11 | return num + 2;
12 | }
13 | return undefined;
14 | }
15 |
16 | function addTwoAsync(num: number | undefined): Promise {
17 | if (num) {
18 | return Promise.resolve(num + 2);
19 | }
20 | return Promise.resolve();
21 | }
22 |
23 | describe('A synchronous test', () => {
24 | given('input is set to 1', () => {
25 | const fakeNumber = 1;
26 |
27 | when(`adding 2`, () => {
28 | const actualResult = addTwo(fakeNumber);
29 |
30 | then('result should be 3', () => {
31 | expect(actualResult).toBe(3);
32 | });
33 | });
34 | });
35 |
36 | given('input is set to 1', () => {
37 | const fakeNumber = 1;
38 |
39 | when('adding 2', () => {
40 | const actualResult = addTwo(fakeNumber);
41 |
42 | then('result should be 3', () => {
43 | expect(actualResult).toBe(3);
44 | });
45 | });
46 | });
47 | });
48 |
49 | describe('full description', () => {
50 | it('should combine all 3 descriptions', () => {
51 | const itSpy = spyOn(root, 'it');
52 | given('fake given description', () => {
53 | when(`fake when description`, () => {
54 | then('fake then description', () => {});
55 | });
56 | });
57 | const expectedFullDescription = `
58 | GIVEN fake given description
59 | WHEN fake when description
60 | THEN fake then description`;
61 |
62 | const actualDescription = itSpy.calls.first().args[0];
63 | expect(actualDescription).toEqual(expectedFullDescription);
64 | });
65 | });
66 |
67 | describe('focus and exclude', () => {
68 | it('should be able to focus on 1 given using "fgiven"', () => {
69 | const fitSpy = spyOn(root, 'fit');
70 | fgiven('FAKE DESCRIPTION', function () {});
71 |
72 | expect(fitSpy).toHaveBeenCalled();
73 | });
74 |
75 | it('should be able to focus on 1 given using "given.only"', () => {
76 | const fitSpy = spyOn(root, 'fit');
77 | given.only('FAKE DESCRIPTION', function () {});
78 |
79 | expect(fitSpy).toHaveBeenCalled();
80 | });
81 |
82 | it('should not allow "fgiven" not as a root given', () => {
83 | spyOn(root, 'it').and.callFake((desc: string, fn: Function) => fn());
84 |
85 | given('root given', () => {
86 | try {
87 | fgiven('another given', () => {});
88 | } catch (error: any) {
89 | expect(error.message).toEqual(getOnlyOneError('given'));
90 | }
91 | });
92 | });
93 |
94 | it('should combine all 3 descriptions for fgiven as well', () => {
95 |
96 | const itSpy = spyOn(root, 'fit');
97 |
98 | fgiven('fake given description', () => {
99 | when(`fake when description`, () => {
100 | then('fake then description', () => {});
101 | });
102 | });
103 | const expectedFullDescription = `
104 | GIVEN fake given description
105 | WHEN fake when description
106 | THEN fake then description`;
107 |
108 | const actualDescription = itSpy.calls.first().args[0];
109 | expect(actualDescription).toEqual(expectedFullDescription);
110 | });
111 |
112 |
113 | it('should be able to exclude a given using "xgiven"', () => {
114 | const xitSpy = spyOn(root, 'xit');
115 | xgiven('FAKE DESCRIPTION', function () {});
116 |
117 | expect(xitSpy).toHaveBeenCalled();
118 | });
119 |
120 | it('should be able to exclude a given using "given.skip"', () => {
121 | const xitSpy = spyOn(root, 'xit');
122 | given.skip('FAKE DESCRIPTION', function () {});
123 |
124 | expect(xitSpy).toHaveBeenCalled();
125 | });
126 | });
127 |
128 | describe('Async tests', () => {
129 |
130 | given('a "done" function gets passed', (done) => {
131 | const fakeNumber = 1;
132 |
133 | when('adding 2 asynchronously', () => {
134 | const actualResult = addTwo(fakeNumber);
135 |
136 | setTimeout(() => {
137 | then('result should be 3', () => {
138 | expect(actualResult).toBe(3);
139 | done();
140 | });
141 | }, 0);
142 |
143 | });
144 | });
145 |
146 | it('should handle errors passed to "done"', async () => {
147 | let actualPromiseFromGiven: Promise | undefined = undefined;
148 | spyOn(root, 'it').and.callFake((desc: any, fn: any) => {
149 | actualPromiseFromGiven = fn();
150 | });
151 |
152 | given('given', (done) => {
153 | when('when', () => {
154 | then('then', () => {
155 | done(new Error('FAKE ERROR'));
156 | });
157 | });
158 | });
159 | try {
160 | await actualPromiseFromGiven;
161 |
162 | } catch (error: any) {
163 | expect(error.message).toEqual('FAKE ERROR');
164 | }
165 | });
166 |
167 | it('should handle errors passed to "done.fail()"', async () => {
168 | let actualPromiseFromGiven: Promise | undefined = undefined;
169 | spyOn(root, 'it').and.callFake((desc: any, fn: any) => {
170 | actualPromiseFromGiven = fn();
171 | });
172 |
173 | given('given', (done) => {
174 | when('when', () => {
175 | then('then', () => {
176 | done.fail(new Error('FAKE ERROR'));
177 | });
178 | });
179 | });
180 | try {
181 | await actualPromiseFromGiven;
182 |
183 | } catch (error: any) {
184 | expect(error.message).toEqual('FAKE ERROR');
185 | }
186 | });
187 |
188 | it('should handle promise errors thrown inside "when"', async () => {
189 | let actualPromiseFromGiven: Promise | undefined = undefined;
190 | spyOn(root, 'it').and.callFake((desc: any, fn: any) => {
191 | actualPromiseFromGiven = fn();
192 | });
193 |
194 | given('given', async () => {
195 | when('when', async () => {
196 | await Promise.resolve().then(() => {
197 | throw new Error('FAKE ERROR');
198 | })
199 |
200 | })
201 | });
202 | try {
203 | await actualPromiseFromGiven;
204 |
205 | } catch (error: any) {
206 | expect(error.message).toEqual('FAKE ERROR');
207 | }
208 | });
209 |
210 | it('should handle promise errors thrown inside "then"', async () => {
211 | let actualPromiseFromGiven: Promise | undefined = undefined;
212 | spyOn(root, 'it').and.callFake((desc: any, fn: any) => {
213 | actualPromiseFromGiven = fn();
214 | });
215 |
216 | given('given', async () => {
217 | when('when', async () => {
218 | then('then', async () => {
219 | await Promise.resolve().then(() => {
220 | throw new Error('FAKE ERROR');
221 | })
222 |
223 | })
224 | })
225 | });
226 | try {
227 | await actualPromiseFromGiven;
228 |
229 | } catch (error: any) {
230 | expect(error.message).toEqual('FAKE ERROR');
231 | }
232 | });
233 |
234 |
235 | given('input is set to 1', () => {
236 | const fakeNumber = 1;
237 |
238 | when('adding 2 asynchronously', async () => {
239 | const actualResult = await addTwoAsync(fakeNumber);
240 | const actualResult2 = await addTwoAsync(fakeNumber);
241 |
242 | then('result should be 3', () => {
243 | expect(actualResult).toBe(3);
244 | expect(actualResult2).toBe(3);
245 | });
246 | });
247 | });
248 |
249 | given('input is set to async 1', async () => {
250 | const fakeNumber = await 1;
251 |
252 | when('adding 2 asynchronously', async () => {
253 | const actualResult = await addTwoAsync(fakeNumber);
254 |
255 | then('async result should be 3', async () => {
256 | const expectedResult = await Promise.resolve(3);
257 | expect(actualResult).toBe(expectedResult);
258 | });
259 | });
260 | });
261 | });
262 |
263 | describe('wrong order of functions', () => {
264 | it('should not allow "given" inside of another "given"', () => {
265 | spyOn(root, 'it').and.callFake((desc: string, fn: Function) => fn());
266 |
267 | given('root given', () => {
268 | try {
269 | given('another given', () => {});
270 | } catch (error: any) {
271 | expect(error.message).toEqual(getOnlyOneError('given'));
272 | }
273 | });
274 | });
275 |
276 | it('should not allow "given" inside of another "when"', () => {
277 | spyOn(root, 'it').and.callFake((desc: string, fn: Function) => fn());
278 |
279 | given('root given', () => {
280 | when('fake when', () => {
281 | try {
282 | given('another given', () => {});
283 | } catch (error: any) {
284 | expect(error.message).toEqual(getOnlyOneError('given'));
285 | }
286 | });
287 | });
288 | });
289 |
290 | it('should not allow "given" inside of another "then"', () => {
291 | spyOn(root, 'it').and.callFake((desc: string, fn: Function) => fn());
292 |
293 | given('root given', () => {
294 | when('fake when', () => {
295 | then('fake then', () => {
296 | try {
297 | given('another given', () => {});
298 | } catch (error: any) {
299 | expect(error.message).toEqual(getOnlyOneError('given'));
300 | }
301 | });
302 | });
303 | });
304 | });
305 |
306 | it('should not allow "when" without a "given" parent', () => {
307 | try {
308 | when('fake when', () => {});
309 | fail('The function "when" should have thrown an error');
310 | } catch (error: any) {
311 | expect(error.message).toEqual(getMustBeAChildError('when'));
312 | }
313 | });
314 |
315 | it('should not allow "then" without a "when" parent', () => {
316 | try {
317 | then('fake then', () => {});
318 | fail('The function "then" should have thrown an error');
319 | } catch (error: any) {
320 | expect(error.message).toEqual(getMustBeAChildError('then'));
321 | }
322 | });
323 | it('should not allow "then" straight under a "given"', () => {
324 | spyOn(root, 'it').and.callFake((desc: string, fn: Function) => fn());
325 | given('root given', () => {
326 | try {
327 | then('fake then', () => {});
328 | fail('The function "then" should have thrown an error');
329 | } catch (error: any) {
330 | expect(error.message).toEqual(getMustBeAChildError('then'));
331 | }
332 | });
333 | });
334 |
335 | it('should not allow 2 "when" functions under 1 "given"', () => {
336 | spyOn(root, 'it').and.callFake((desc: string, fn: Function) => fn());
337 | given('root given', () => {
338 | try {
339 | when('first when', () => {});
340 | when('second when', () => {});
341 | fail('The second "when" function should have thrown an error');
342 | } catch (error: any) {
343 | expect(error.message).toEqual(getOnlyOneError('when'));
344 | }
345 | });
346 | });
347 |
348 | it('should not allow 2 "then" functions under 1 "when"', () => {
349 | spyOn(root, 'it').and.callFake((desc: string, fn: Function) => fn());
350 | given('root given', () => {
351 | when('when', () => {
352 | try {
353 | then('first then', () => {});
354 | then('second then', () => {});
355 | fail('The second "then" function should have thrown an error');
356 | } catch (error: any) {
357 | expect(error.message).toEqual(getOnlyOneError('then'));
358 | }
359 | });
360 | });
361 | });
362 | });
363 | });
364 |
--------------------------------------------------------------------------------
/packages/jasmine-single/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "rootDir": "./temp-src"
6 | },
7 | "include": [
8 | "temp-src"
9 | ],
10 | "exclude": ["**/*.spec.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/jasmine-single/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "moduleResolution": "node",
6 | "experimentalDecorators": true,
7 | "emitDecoratorMetadata": true,
8 | "declaration": true, // generate .d.ts files
9 | "declarationMap": true,
10 | "noImplicitAny": true,
11 | "noImplicitThis": false,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "sourceMap": true,
15 | "downlevelIteration": true,
16 | "lib": ["es2015", "es2016", "dom"],
17 | "pretty": true,
18 | "strict": true,
19 | "baseUrl": ".",
20 | "outDir": "dist",
21 | "types": ["jasmine", "node"]
22 | },
23 | "include": ["spec"]
24 | }
25 |
--------------------------------------------------------------------------------
/packages/jest-single/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "single",
3 | "projectOwner": "hirezio",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md",
8 | "../../README.md"
9 | ],
10 | "imageSize": 100,
11 | "commit": true,
12 | "commitConvention": "angular",
13 | "contributors": [
14 | {
15 | "login": "shairez",
16 | "name": "Shai Reznik",
17 | "avatar_url": "https://avatars1.githubusercontent.com/u/1430726?v=4",
18 | "profile": "https://www.hirez.io/?utm_medium=Open_Source&utm_source=Github&utm_campaign=Lead_Generation&utm_content=jest-single--all-contributors-profile-link",
19 | "contributions": [
20 | "code",
21 | "doc",
22 | "ideas",
23 | "infra",
24 | "maintenance",
25 | "mentoring",
26 | "review",
27 | "test"
28 | ]
29 | },
30 | {
31 | "login": "maartentibau",
32 | "name": "Maarten Tibau",
33 | "avatar_url": "https://avatars.githubusercontent.com/u/4103756?v=4",
34 | "profile": "https://www.webtrix.be",
35 | "contributions": [
36 | "doc",
37 | "infra"
38 | ]
39 | },
40 | {
41 | "login": "benjamingr",
42 | "name": "Benjamin Gruenbaum",
43 | "avatar_url": "https://avatars.githubusercontent.com/u/1315533?v=4",
44 | "profile": "https://stackoverflow.com/users/1348195/benjamin-gruenbaum",
45 | "contributions": [
46 | "code",
47 | "ideas",
48 | "maintenance"
49 | ]
50 | }
51 | ],
52 | "contributorsPerLine": 7
53 | }
54 |
--------------------------------------------------------------------------------
/packages/jest-single/.core-version:
--------------------------------------------------------------------------------
1 | 3f24aa9551ab834f07b3464b2043721fb3b55edf
--------------------------------------------------------------------------------
/packages/jest-single/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
--------------------------------------------------------------------------------
/packages/jest-single/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | ## [1.1.8](https://github.com/hirezio/single/compare/@hirez_io/jest-single@1.1.7...@hirez_io/jest-single@1.1.8) (2023-08-25)
7 |
8 | **Note:** Version bump only for package @hirez_io/jest-single
9 |
10 |
11 |
12 |
13 |
14 | ## [1.1.7](https://github.com/hirezio/single/compare/@hirez_io/jest-single@1.1.6...@hirez_io/jest-single@1.1.7) (2023-08-25)
15 |
16 | **Note:** Version bump only for package @hirez_io/jest-single
17 |
18 |
19 |
20 |
21 |
22 | ## [1.1.6](https://github.com/hirezio/single/compare/@hirez_io/jest-single@1.1.5...@hirez_io/jest-single@1.1.6) (2022-07-11)
23 |
24 | **Note:** Version bump only for package @hirez_io/jest-single
25 |
26 |
27 |
28 |
29 |
30 | ## [1.1.5](https://github.com/hirezio/single/compare/@hirez_io/jest-single@1.1.4...@hirez_io/jest-single@1.1.5) (2022-06-04)
31 |
32 |
33 | ### Bug Fixes
34 |
35 | * **global:** fix fgiven description ([dc75bd9](https://github.com/hirezio/single/commit/dc75bd9ff24b6f4f5b50ed734e1ffed99b9c46ae))
36 | * **global:** fix unhandled promise exceptions bug ([06f4934](https://github.com/hirezio/single/commit/06f4934c607aeec06520874a2563e2bd002a1337))
37 |
38 |
39 |
40 |
41 |
42 | ## [1.1.4](https://github.com/hirezio/single/compare/@hirez_io/jest-single@1.1.3...@hirez_io/jest-single@1.1.4) (2022-03-13)
43 |
44 | **Note:** Version bump only for package @hirez_io/jest-single
45 |
46 |
47 |
48 |
49 |
50 | ## [1.1.3](https://github.com/hirezio/single/compare/@hirez_io/jest-single@1.1.2...@hirez_io/jest-single@1.1.3) (2022-03-13)
51 |
52 | **Note:** Version bump only for package @hirez_io/jest-single
53 |
54 |
55 |
56 |
57 |
58 | ## [1.1.2](https://github.com/hirezio/single/compare/@hirez_io/jest-single@1.1.1...@hirez_io/jest-single@1.1.2) (2022-03-02)
59 |
60 | **Note:** Version bump only for package @hirez_io/jest-single
61 |
62 |
63 |
64 |
65 |
66 | ## [1.1.1](https://github.com/hirezio/single/compare/@hirez_io/jest-single@1.1.0...@hirez_io/jest-single@1.1.1) (2021-08-08)
67 |
68 | **Note:** Version bump only for package @hirez_io/jest-single
69 |
70 |
71 |
72 |
73 |
74 | # [1.1.0](https://github.com/hirezio/single/compare/@hirez_io/jest-single@1.0.0...@hirez_io/jest-single@1.1.0) (2021-08-08)
75 |
76 |
77 | ### Features
78 |
79 | * **global:** add support for `done` and update README ([6082c27](https://github.com/hirezio/single/commit/6082c2710153ea0a5288a25457a7a78828a7b48d))
80 |
81 |
82 |
83 |
84 |
85 | # 1.0.0 (2021-07-29)
86 |
87 | **Note:** Version bump only for package @hirez_io/jest-single
88 |
--------------------------------------------------------------------------------
/packages/jest-single/README.md:
--------------------------------------------------------------------------------
1 | # `@hirez_io/jest-single` 📃👌
2 |
3 | A Jest addon that helps you write 'Single-Action Tests' by breaking them into a **"given / when / then"** structure.
4 |
5 | [](https://www.npmjs.org/package/@hirez_io/jest-single)
6 | [](http://npm-stat.com/charts.html?package=@hirez_io/jest-single&from=2017-07-26)
7 | [](https://codecov.io/gh/hirezio/single)
8 | 
9 | [](https://lerna.js.org/)
10 | [](../../CODE_OF_CONDUCT.md)
11 | [](https://opensource.org/licenses/MIT)
12 | [](#contributors-)
13 |
14 |
15 |
23 |
24 |
25 |
26 | # Table of Contents
27 |
28 |
29 |
30 |
31 | - [Installation](#installation)
32 |
33 | - [Configuring Jest](#configuring-jest)
34 |
35 | - [Using TypeScript?](#using-typescript)
36 |
37 | - [What are "single-action" tests?](#what-are-single-action-tests)
38 |
39 | - [Why writing single-action tests is good?](#why-writing-single-action-tests-is-good)
40 |
41 | - [How to write single-action tests?](#how-to-write-single-action-tests)
42 |
43 | - [What's wrong with using `it()` or `test()` for single-action tests?](#whats-wrong-with-using-it-or-test-for-single-action-tests)
44 |
45 | - [Usage](#usage)
46 |
47 | - [▶ The basic testing structure](#%E2%96%B6-the-basic-testing-structure)
48 |
49 | - [▶ Meaningful error messages](#%E2%96%B6-meaningful-error-messages)
50 |
51 | - [▶ `async` / `await` support](#%E2%96%B6-async--await-support)
52 |
53 | - [▶ `done()` function support](#%E2%96%B6-done-function-support)
54 | - [Contributing](#contributing)
55 | - [Code Of Conduct](#code-of-conduct)
56 | - [License](#license)
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | ## Installation
66 |
67 | ```
68 | yarn add -D @hirez_io/jest-single
69 | ```
70 |
71 | or
72 |
73 | ```
74 | npm install -D @hirez_io/jest-single
75 | ```
76 |
77 | ## Configuring Jest
78 |
79 | Add the following line to your jest config:
80 |
81 | ```json
82 | "setupFilesAfterEnv": ["node_modules/@hirez_io/jest-single/dist/jest-single.js"]
83 | ```
84 |
85 | ⚠ **ATTENTION:** If you have configured `rootDir` -
86 |
87 | Make sure you have the right path to `node_modules`.
88 |
89 | For example:
90 |
91 | ```json
92 | "rootDir": "src",
93 | "setupFilesAfterEnv": ["../node_modules/@hirez_io/jest-single/dist/jest-single.js"]
94 | ```
95 |
96 |
97 |
98 |
99 |
100 |
101 | ## Using TypeScript?
102 |
103 |
104 | ⚠ CLICK HERE TO EXPAND
105 |
106 |
107 |
108 | You should add `@hirez_io/jest-single` to your `types` property under `compilerOptions` in your `tsconfig.json` (or `tsconfig.spec.json`) like this:
109 |
110 | ```js
111 | // tsconfig.json or tsconfig.spec.json
112 |
113 | {
114 | ...
115 | "compilerOptions": {
116 | "types": [
117 | "jest",
118 | "@hirez_io/jest-single", // 👈 ADD THIS
119 |
120 | // ...any other types you might have...
121 | ],
122 | ...
123 | }
124 | ...
125 | }
126 | ```
127 |
128 | ⚠ **ATTENTION:** If you have `typeRoots` configured like this -
129 |
130 | ```ts
131 | "compilerOptions": {
132 | "typeRoots": [
133 | "node_modules/@types"
134 | ],
135 | }
136 | ```
137 |
138 | You should add `"node_modules"` like this -
139 |
140 | ```ts
141 | "compilerOptions": {
142 | "typeRoots": [
143 | "node_modules/@types",
144 | "node_modules/@hirez_io" // 👈 ADD THIS
145 | ],
146 | }
147 | ```
148 |
149 | or else it won't find `@hirez_io/jest-single` global types.
150 |
151 | ### ⚠ **VS CODE USERS:**
152 |
153 | Add the above configuration (`types` and/or `typeRoots`) to your `tsconfig.json` specifically or else it would not recognize the global types.
154 |
155 |
156 |
157 |
158 |
159 | ## What are "single-action" tests?
160 |
161 | A single-action test is a test with only one action. (CAPTAIN OBVIOUS! 🦸♂️😅)
162 |
163 | Normally, you setup the environment, you call an action and then you check the output.
164 |
165 | #### What's an action?
166 |
167 | Well... it can be a method call, a button click or anything else our test is checking.
168 |
169 | The big idea here is that it should be only **ONE ACTION PER TEST**.
170 |
171 |
172 |
173 | ## Why writing single-action tests is good?
174 |
175 | Single action tests are more "Test Effective" compared to multi-action tests.
176 |
177 | The benefits of single-action tests:
178 |
179 | ✅ Your tests will **break less often** (making them more effective)
180 |
181 | ✅ Whenever something breaks, you have **only one "action" code to debug**
182 |
183 | ✅ They promote **better coverage** (easier to see which cases are still missing)
184 |
185 | ✅ They give you **better structure** (every part of your test has a clear goal)
186 |
187 |
188 |
189 |
190 | ## How to write single-action tests?
191 |
192 | This library follows the natural "`given`, `when` and `then`" structure (some of you know it as "Arrange, Act, Assert").
193 |
194 | This means every test has only 3 parts to it, no more.
195 |
196 | ```ts
197 | describe('addTwo', () => {
198 |
199 | // This is where you setup your environment / inputs
200 | given('first number is 1', () => {
201 | const firstNum = 1;
202 |
203 | // This is where you call the action under test
204 | when('adding 2 to the first number', () => {
205 | const actualResult = addTwo(firstNum);
206 |
207 | // This is where you check the outcome
208 | then('result should be 3', () => {
209 | expect(actualResult).toEqual(3);
210 | });
211 | });
212 | });
213 |
214 | });
215 |
216 | ```
217 |
218 | It also prints a nicer test description when there's an error:
219 |
220 | ```
221 | CONSOLE OUTPUT:
222 | ~~~~~~~~~~~~~~
223 |
224 | GIVEN first number is 1
225 | WHEN adding 2 to the first number
226 | THEN result should be 3
227 | ```
228 |
229 |
230 |
231 | ## What's wrong with using `it()` or `test()` for single-action tests?
232 |
233 | Did you know that the most common way of writing JavaScript tests dates back to 2005? 😅
234 |
235 | Jasmine, which was created in 2009 was inspired by Ruby's testing framework - RSpec which was created in 2005.
236 |
237 | Jest, which is based on Jasmine added an alias called `test()` which is basically the same thing.
238 |
239 | Originally, RSpec introduced the syntax of "`describe` > `context` > `it`", where `context` was meant to be used as the "setup" part of the test.
240 |
241 | Unfortunately, the `context` wasn't ported to Jasmine so we got used to writing our tests in the "`describe` > `it`" structure... which is more limited.
242 |
243 |
244 |
245 | _Here are a couple of limitations with the common `it()` structure:_
246 |
247 | ### ❌ 1. It promotes partial or awkward descriptions of tests
248 |
249 | The word "it" kinda forces you to begin the description with "should" which leads to focusing specifically on just the "outcome" part of the test (the `then`).
250 |
251 | But if you want to add more context (like what should be the input that causes that outcome) things start to get messy.
252 |
253 | Because there isn't a clear convention, people tend to invent their own on the fly which creates inconsistency.
254 |
255 |
256 | **Example:**
257 | ```ts
258 | it('should do X only when environment is Y and also called by Z But only if...you get the point', ()=> ...)
259 | ```
260 |
261 |
262 |
263 | ### ❌ 2. Nothing prevents you from writing multi-action tests
264 |
265 | This mixes up testing structures and making them harder to understand
266 |
267 | **Example:**
268 | ```ts
269 | it('should transform products', ()=> {
270 |
271 | // SETUP
272 | const fakeProducts = [...];
273 |
274 | // ACTION
275 | const result = classUnderTest.transformProducts(fakeProducts);
276 |
277 | // OUTCOME
278 | const transformedProducts = [...];
279 | expect(result).toEqual(transformedProducts);
280 |
281 | // ACTION
282 | const result2 = classUnderTest.transformProducts();
283 |
284 | // OUTCOME
285 | expect(result2).toEqual( [] );
286 |
287 |
288 | // this 👆 is a multi-action test.
289 |
290 | })
291 | ```
292 |
293 |
294 | ### ❌ 3. Detailed descriptions can get out of date more easily
295 |
296 | The farther the description is from the actual implementation the less likely you'll remember to update it when the test code changes
297 |
298 | **Example:**
299 | ```ts
300 | test('GIVEN valid products and metadata returned successfully WHEN destroying the products THEN they should get decorated', ()=> {
301 |
302 | const fakeProducts = [...];
303 | const fakeMetadata = [...];
304 | mySpy.getMetadata.mockReturnValue(fakeMetadata);
305 |
306 | const result = classUnderTest.transformProducts(fakeProducts);
307 |
308 | const decoratedProducts = [...];
309 | expect(result).toEqual(decoratedProducts);
310 |
311 | })
312 | ```
313 |
314 |
315 | #### Did you spot the typo? 👆😅
316 |
317 |
318 | (it should be _"transforming"_ instead of _"destroying"_)
319 |
320 |
321 |
322 | Compare that to -
323 |
324 | ```ts
325 |
326 | given('valid products and metadata returned successfully', () => {
327 | const fakeProducts = [...];
328 | const fakeMetadata = [...];
329 | mySpy.getMetadata.mockReturnValue(fakeMetadata);
330 |
331 | // 👇 --> easier to spot as it's closer to the implementation
332 | when('destroying the products', () => {
333 | const result = classUnderTest.transformProducts(fakeProducts);
334 |
335 | then('they should get decorated', () => {
336 | const decoratedProducts = [...];
337 | expect(result).toEqual(decoratedProducts);
338 | });
339 | });
340 | });
341 |
342 |
343 | ```
344 |
345 |
346 |
347 |
348 |
349 | ## Usage
350 |
351 | ### ▶ The basic testing structure
352 |
353 | The basic structure is a nesting of these 3 functions:
354 |
355 | ```ts
356 | given(description, () => {
357 | when(description, () => {
358 | then(description, () => {
359 |
360 | })
361 | })
362 | })
363 |
364 |
365 | ```
366 |
367 | **EXAMPLE:**
368 | ```ts
369 | describe('addTwo', () => {
370 |
371 | // This is where you setup your environment / inputs
372 | given('first number is 1', () => {
373 | const firstNum = 1;
374 |
375 | // This is where you call the action under test
376 | when('adding 2 to the first number', () => {
377 | const actualResult = addTwo(firstNum);
378 |
379 | // This is where you check the outcome
380 | then('result should be 3', () => {
381 | expect(actualResult).toEqual(3);
382 | });
383 | });
384 | });
385 |
386 | });
387 |
388 | ```
389 |
390 | Under the hood it creates a regular `it()` test with a combination of all the descriptions:
391 |
392 | ```
393 | CONSOLE OUTPUT:
394 | ~~~~~~~~~~~~~~
395 |
396 | GIVEN first number is 1
397 | WHEN adding 2 to the first number
398 | THEN result should be 3
399 | ```
400 |
401 |
402 |
403 | ### ▶ Meaningful error messages
404 |
405 | This library will throw an error if you deviate from the `given > when > then` structure.
406 |
407 | So you won't be tempted to accidentally turn your single-action test into a multi-action one.
408 |
409 | ```ts
410 | describe('addTwo', () => {
411 |
412 | // 👉 ATTENTION: You cannot start with a "when()" or a "then()"
413 | // the test MUST start with a "given()"
414 |
415 |
416 | given('first number is 1', () => {
417 | const firstNum = 1;
418 |
419 | // 👉 ATTENTION: You cannot add here a "then()" function directly
420 | // or another "given()" function
421 |
422 | when('adding 2 to the first number', () => {
423 | const actualResult = addTwo(firstNum);
424 |
425 | // 👉 ATTENTION: You cannot add here a "given()" function
426 | // or another "when()" function
427 |
428 | then('result should be 3', () => {
429 | expect(actualResult).toEqual(3);
430 |
431 |
432 | // 👉 ATTENTION: You cannot add here a "given()" function
433 | // or a "when()" function or another "then()"
434 | });
435 | });
436 | });
437 |
438 | });
439 |
440 | ```
441 |
442 |
443 | ### ▶ `async` / `await` support
444 |
445 | **Example:**
446 |
447 | ```ts
448 | describe('addTwo', () => {
449 |
450 | given('first number is 1', () => {
451 | const firstNum = 1;
452 | // 👇
453 | when('adding 2 to the first number', async () => {
454 | const actualResult = await addTwo(firstNum);
455 |
456 | then('result should be 3', () => {
457 | expect(actualResult).toEqual(3);
458 | });
459 | });
460 | });
461 |
462 | });
463 |
464 |
465 | ```
466 |
467 | ### ▶ `done()` function support
468 |
469 | The `given` function supports the (old) async callback way of using a `done()` function to signal when the test is completed.
470 |
471 | ```ts
472 | describe('addTwo', () => {
473 | // 👇
474 | given('first number is 1', (done) => {
475 | const firstNum = 1;
476 |
477 | when('adding 2 to the first number', () => {
478 | const actualResult = addTwo(firstNum, function callback() {
479 |
480 | then('result should be 3', () => {
481 | expect(actualResult).toEqual(3);
482 | done();
483 | });
484 |
485 | });
486 | });
487 |
488 | });
489 |
490 | });
491 |
492 |
493 | ```
494 |
495 | ℹ It also supports `done(error)` or `done.fail(error)` for throwing async errors.
496 |
497 |
498 |
499 | ## Contributing
500 |
501 | Want to contribute? Yayy! 🎉
502 |
503 | Please read and follow our [Contributing Guidelines](../../CONTRIBUTING.md) to learn what are the right steps to take before contributing your time, effort and code.
504 |
505 | Thanks 🙏
506 |
507 |
508 |
509 | ## Code Of Conduct
510 |
511 | Be kind to each other and please read our [code of conduct](../../CODE_OF_CONDUCT.md).
512 |
513 |
514 |
515 |
516 | ## Contributors ✨
517 |
518 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
519 |
520 |
521 |
522 |
523 |
530 |
531 |
532 |
533 |
534 |
535 |
536 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
537 |
538 |
539 |
540 | ## License
541 |
542 | MIT
543 |
--------------------------------------------------------------------------------
/packages/jest-single/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | rootDir: '../../',
4 | roots: ['/shared/single-core', '/packages/jest-single/spec'],
5 | verbose: true,
6 | resetMocks: true,
7 | testEnvironment: 'node',
8 | transform: {
9 | '^.+\\.[tj]s?$': ['ts-jest', {
10 | tsconfig: '/packages/jest-single/tsconfig.json'
11 | }]
12 | },
13 | collectCoverage: true,
14 | coverageDirectory: 'packages/jest-single/coverage',
15 | coverageThreshold: {
16 | global: {
17 | branches: 100,
18 | functions: 90,
19 | lines: 90,
20 | statements: 90,
21 | },
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/packages/jest-single/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hirez_io/jest-single",
3 | "version": "1.1.8",
4 | "publishConfig": {
5 | "access": "public",
6 | "registry": "https://registry.npmjs.org"
7 | },
8 | "author": {
9 | "name": "Shai Reznik",
10 | "company": "HiRez.io"
11 | },
12 | "description": "A jest addon that helps you write 'Single-Action Tests' by breaking them into a given / when / then structure.",
13 | "keywords": [
14 | "jest",
15 | "jest-single",
16 | "gwt",
17 | "Given When Then",
18 | "Microtests",
19 | "Testing",
20 | "Unit tests",
21 | "JavasScript Unit Tests",
22 | "TypeScript Unit Tests",
23 | "hirez.io"
24 | ],
25 | "homepage": "https://github.com/hirezio/single/tree/main/packages/jest-single",
26 | "repository": {
27 | "type": "git",
28 | "url": "https://github.com/hirezio/single",
29 | "directory": "packages/jest-single"
30 | },
31 | "license": "MIT",
32 | "main": "dist/jest-single.js",
33 | "types": "dist/jest-single.d.ts",
34 | "scripts": {
35 | "clean": "rimraf dist",
36 | "clean:temp-src": "rimraf temp-src",
37 | "copy:shared-source": "cpy ../../shared/single-core/single-core.ts ./temp-src/ --rename=jest-single.ts",
38 | "compile": "tsc -p tsconfig.build.json",
39 | "build": "run-s clean copy:shared-source compile clean:temp-src",
40 | "test": "echo \n*** Run tests from the root folder\n"
41 | },
42 | "peerDependencies": {
43 | "jest": "< 30.x"
44 | },
45 | "devDependencies": {
46 | "@types/jest": "29.5.3",
47 | "cpy-cli": "^3.1.1",
48 | "jest": "29.6.3",
49 | "microbundle": "^0.15.0",
50 | "ts-jest": "29.1.1"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/packages/jest-single/spec/jest-single.spec.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getOnlyOneError,
3 | getMustBeAChildError
4 | } from '../../../shared/single-core/single-core';
5 |
6 | const root = (1, eval)('this');
7 |
8 | describe('Jest Single', () => {
9 | function addTwo(num: number | undefined) {
10 | if (num) {
11 | return num + 2;
12 | }
13 | return undefined;
14 | }
15 |
16 | function addTwoAsync(num: number | undefined): Promise {
17 | if (num) {
18 | return Promise.resolve(num + 2);
19 | }
20 | return Promise.resolve();
21 | }
22 |
23 | describe('A synchronous test', () => {
24 | given('input is set to 1', () => {
25 | const fakeNumber = 1;
26 |
27 | when(`adding 2`, () => {
28 | const actualResult = addTwo(fakeNumber);
29 |
30 | then('result should be 3', () => {
31 | expect(actualResult).toBe(3);
32 | });
33 | });
34 | });
35 |
36 | given('input is set to 1', () => {
37 | const fakeNumber = 1;
38 |
39 | when('adding 2', () => {
40 | const actualResult = addTwo(fakeNumber);
41 |
42 | then('result should be 3', () => {
43 | expect(actualResult).toBe(3);
44 | });
45 | });
46 | });
47 | });
48 |
49 | describe('full description', () => {
50 | it('should combine all 3 descriptions', () => {
51 | const itSpy = jest.spyOn(root, 'it').mockImplementation();
52 | given('fake given description', () => {
53 | when(`fake when description`, () => {
54 | then('fake then description', () => {});
55 | });
56 | });
57 | const expectedFullDescription = `
58 | GIVEN fake given description
59 | WHEN fake when description
60 | THEN fake then description`;
61 |
62 | const actualDescription = itSpy.mock.calls[0][0];
63 | expect(actualDescription).toEqual(expectedFullDescription);
64 | });
65 | });
66 |
67 | describe('focus and exclude', () => {
68 | it('should be able to focus on 1 given using "fgiven"', () => {
69 | const fitSpy = jest.spyOn(root, 'fit').mockImplementation();
70 | fgiven('FAKE DESCRIPTION', function () {});
71 |
72 | expect(fitSpy).toHaveBeenCalled();
73 | });
74 |
75 | it('should be able to focus on 1 given using "given.only"', () => {
76 | const fitSpy = jest.spyOn(root, 'fit').mockImplementation();;
77 | given.only('FAKE DESCRIPTION', function () {});
78 |
79 | expect(fitSpy).toHaveBeenCalled();
80 | });
81 |
82 | it('should not allow "fgiven" not as a root given', () => {
83 | jest.spyOn(root, 'it').mockImplementation((desc: any, fn: any) => fn());
84 |
85 | given('root given', () => {
86 | try {
87 | fgiven('another given', () => {});
88 | } catch (error: any) {
89 | expect(error.message).toEqual(getOnlyOneError('given'));
90 | }
91 | });
92 | });
93 |
94 | it('should combine all 3 descriptions for fgiven as well', () => {
95 | const itSpy = jest.spyOn(root, 'fit').mockImplementation();
96 | fgiven('fake given description', () => {
97 | when(`fake when description`, () => {
98 | then('fake then description', () => {});
99 | });
100 | });
101 | const expectedFullDescription = `
102 | GIVEN fake given description
103 | WHEN fake when description
104 | THEN fake then description`;
105 |
106 | const actualDescription = itSpy.mock.calls[0][0];
107 | expect(actualDescription).toEqual(expectedFullDescription);
108 | });
109 |
110 | it('should be able to exclude a given using "xgiven"', () => {
111 | const xitSpy = jest.spyOn(root, 'xit').mockImplementation();
112 | xgiven('FAKE DESCRIPTION', function () {});
113 |
114 | expect(xitSpy).toHaveBeenCalled();
115 | });
116 |
117 | it('should be able to exclude a given using "given.skip"', () => {
118 | const xitSpy = jest.spyOn(root, 'xit').mockImplementation();
119 | given.skip('FAKE DESCRIPTION', function () {});
120 |
121 | expect(xitSpy).toHaveBeenCalled();
122 | });
123 | });
124 |
125 | describe('Async tests', () => {
126 |
127 | given('a "done" function gets passed', (done) => {
128 | const fakeNumber = 1;
129 |
130 | when('adding 2 asynchronously', () => {
131 | const actualResult = addTwo(fakeNumber);
132 |
133 | setTimeout(() => {
134 | then('result should be 3', () => {
135 | expect(actualResult).toBe(3);
136 | done();
137 | });
138 | }, 0);
139 |
140 | });
141 | });
142 |
143 | it('should handle errors passed to "done"', async () => {
144 | let actualPromiseFromGiven: Promise | undefined = undefined;
145 | jest.spyOn(root, 'it').mockImplementation((desc: any, fn: any) => {
146 | actualPromiseFromGiven = fn();
147 | });
148 |
149 | given('given', (done) => {
150 | when('when', () => {
151 | then('then', () => {
152 | done(new Error('FAKE ERROR'));
153 | });
154 | });
155 | });
156 | try {
157 | await actualPromiseFromGiven;
158 |
159 | } catch (error: any) {
160 | expect(error.message).toEqual('FAKE ERROR');
161 | }
162 | });
163 |
164 | it('should handle errors passed to "done.fail()"', async () => {
165 | let actualPromiseFromGiven: Promise | undefined = undefined;
166 | jest.spyOn(root, 'it').mockImplementation((desc: any, fn: any) => {
167 | actualPromiseFromGiven = fn();
168 | });
169 |
170 | given('given', (done) => {
171 | when('when', () => {
172 | then('then', () => {
173 | done.fail(new Error('FAKE ERROR'));
174 | });
175 | });
176 | });
177 | try {
178 | await actualPromiseFromGiven;
179 |
180 | } catch (error: any) {
181 | expect(error.message).toEqual('FAKE ERROR');
182 | }
183 | });
184 |
185 | it('should handle promise errors thrown inside "when"', async () => {
186 | let actualPromiseFromGiven: Promise | undefined = undefined;
187 | jest.spyOn(root, 'it').mockImplementation((desc: any, fn: any) => {
188 | actualPromiseFromGiven = fn();
189 | });
190 |
191 | given('given', async () => {
192 | when('when', async () => {
193 | await Promise.resolve().then(() => {
194 | throw new Error('FAKE ERROR');
195 | })
196 |
197 | })
198 | });
199 | try {
200 | await actualPromiseFromGiven;
201 |
202 | } catch (error: any) {
203 | expect(error.message).toEqual('FAKE ERROR');
204 | }
205 | });
206 |
207 | it('should handle promise errors thrown inside "then"', async () => {
208 | let actualPromiseFromGiven: Promise | undefined = undefined;
209 | jest.spyOn(root, 'it').mockImplementation((desc: any, fn: any) => {
210 | actualPromiseFromGiven = fn();
211 | });
212 |
213 | given('given', async () => {
214 | when('when', async () => {
215 | then('then', async () => {
216 | await Promise.resolve().then(() => {
217 | throw new Error('FAKE ERROR');
218 | })
219 |
220 | })
221 | })
222 | });
223 | try {
224 | await actualPromiseFromGiven;
225 |
226 | } catch (error: any) {
227 | expect(error.message).toEqual('FAKE ERROR');
228 | }
229 | });
230 |
231 | given('input is set to 1', () => {
232 | const fakeNumber = 1;
233 |
234 | when('adding 2 asynchronously', async () => {
235 | const actualResult = await addTwoAsync(fakeNumber);
236 | const actualResult2 = await addTwoAsync(fakeNumber);
237 |
238 | then('result should be 3', () => {
239 | expect(actualResult).toBe(3);
240 | expect(actualResult2).toBe(3);
241 | });
242 | });
243 | });
244 |
245 | given('input is set to async 1', async () => {
246 | const fakeNumber = await 1;
247 |
248 | when('adding 2 asynchronously', async () => {
249 | const actualResult = await addTwoAsync(fakeNumber);
250 |
251 | then('async result should be 3', async () => {
252 | const expectedResult = await Promise.resolve(3);
253 | expect(actualResult).toBe(expectedResult);
254 | });
255 | });
256 | });
257 |
258 | });
259 |
260 | describe('wrong order of functions', () => {
261 | it('should not allow "given" inside of another "given"', () => {
262 | jest.spyOn(root, 'it').mockImplementation((desc: any, fn: any) => fn());
263 |
264 | given('root given', () => {
265 | try {
266 | given('another given', () => {});
267 | } catch (error: any) {
268 | expect(error.message).toEqual(getOnlyOneError('given'));
269 | }
270 | });
271 | });
272 |
273 | it('should not allow "given" inside of another "when"', () => {
274 | jest.spyOn(root, 'it').mockImplementation((desc: any, fn: any) => fn());
275 |
276 | given('root given', () => {
277 | when('fake when', () => {
278 | try {
279 | given('another given', () => {});
280 | } catch (error: any) {
281 | expect(error.message).toEqual(getOnlyOneError('given'));
282 | }
283 | });
284 | });
285 | });
286 |
287 | it('should not allow "given" inside of another "then"', () => {
288 | jest.spyOn(root, 'it').mockImplementation((desc: any, fn: any) => fn());
289 |
290 | given('root given', () => {
291 | when('fake when', () => {
292 | then('fake then', () => {
293 | try {
294 | given('another given', () => {});
295 | } catch (error: any) {
296 | expect(error.message).toEqual(getOnlyOneError('given'));
297 | }
298 | });
299 | });
300 | });
301 | });
302 |
303 | it('should not allow "when" without a "given" parent', () => {
304 | try {
305 | when('fake when', () => {});
306 | fail('The function "when" should have thrown an error');
307 | } catch (error: any) {
308 | expect(error.message).toEqual(getMustBeAChildError('when'));
309 | }
310 | });
311 |
312 | it('should not allow "then" without a "when" parent', () => {
313 | try {
314 | then('fake then', () => {});
315 | fail('The function "then" should have thrown an error');
316 | } catch (error: any) {
317 | expect(error.message).toEqual(getMustBeAChildError('then'));
318 | }
319 | });
320 | it('should not allow "then" straight under a "given"', () => {
321 | jest.spyOn(root, 'it').mockImplementation((desc: any, fn: any) => fn());
322 | given('root given', () => {
323 | try {
324 | then('fake then', () => {});
325 | fail('The function "then" should have thrown an error');
326 | } catch (error: any) {
327 | expect(error.message).toEqual(getMustBeAChildError('then'));
328 | }
329 | });
330 | });
331 |
332 | it('should not allow 2 "when" functions under 1 "given"', () => {
333 | jest.spyOn(root, 'it').mockImplementation((desc: any, fn: any) => fn());
334 | given('root given', () => {
335 | try {
336 | when('first when', () => {});
337 | when('second when', () => {});
338 | fail('The second "when" function should have thrown an error');
339 | } catch (error: any) {
340 | expect(error.message).toEqual(getOnlyOneError('when'));
341 | }
342 | });
343 | });
344 |
345 | it('should not allow 2 "then" functions under 1 "when"', () => {
346 | jest.spyOn(root, 'it').mockImplementation((desc: any, fn: any) => fn());
347 | given('root given', () => {
348 | when('when', () => {
349 | try {
350 | then('first then', () => {});
351 | then('second then', () => {});
352 | fail('The second "then" function should have thrown an error');
353 | } catch (error: any) {
354 | expect(error.message).toEqual(getOnlyOneError('then'));
355 | }
356 | });
357 | });
358 | });
359 |
360 | });
361 |
362 |
363 | });
364 |
--------------------------------------------------------------------------------
/packages/jest-single/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "rootDir": "./temp-src"
5 | },
6 | "include": [
7 | "temp-src"
8 | ],
9 | "exclude": ["**/*.spec.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/jest-single/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "moduleResolution": "node",
6 | "experimentalDecorators": true,
7 | "emitDecoratorMetadata": true,
8 | "declaration": true, // generate .d.ts files
9 | "declarationMap": true,
10 | "noImplicitAny": true,
11 | "noImplicitThis": false,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "sourceMap": true,
15 | "downlevelIteration": true,
16 | "lib": ["es2015", "es2016", "dom"],
17 | "pretty": true,
18 | "strict": true,
19 | "baseUrl": ".",
20 | "outDir": "dist",
21 | "types": ["jest", "node"],
22 | "typeRoots": [
23 | "../../node_modules/@hirez_io/jest-single",
24 | "../../node_modules/@types"
25 | ]
26 | },
27 | "include": ["spec"],
28 | }
29 |
--------------------------------------------------------------------------------
/packages/karma-jasmine-single/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "single",
3 | "projectOwner": "hirezio",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md",
8 | "../../README.md"
9 | ],
10 | "imageSize": 100,
11 | "commit": true,
12 | "commitConvention": "angular",
13 | "contributors": [
14 | {
15 | "login": "shairez",
16 | "name": "Shai Reznik",
17 | "avatar_url": "https://avatars1.githubusercontent.com/u/1430726?v=4",
18 | "profile": "https://www.hirez.io/?utm_medium=Open_Source&utm_source=Github&utm_campaign=Lead_Generation&utm_content=karma-jasmine-single--all-contributors-profile-link",
19 | "contributions": [
20 | "code",
21 | "doc",
22 | "ideas",
23 | "infra",
24 | "maintenance",
25 | "mentoring",
26 | "review",
27 | "test"
28 | ]
29 | }
30 | ],
31 | "contributorsPerLine": 7
32 | }
33 |
--------------------------------------------------------------------------------
/packages/karma-jasmine-single/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | .nyc_output
--------------------------------------------------------------------------------
/packages/karma-jasmine-single/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | ## [1.0.4](https://github.com/hirezio/single/compare/@hirez_io/karma-jasmine-single@1.0.3...@hirez_io/karma-jasmine-single@1.0.4) (2023-08-25)
7 |
8 | **Note:** Version bump only for package @hirez_io/karma-jasmine-single
9 |
10 |
11 |
12 |
13 |
14 | ## [1.0.3](https://github.com/hirezio/single/compare/@hirez_io/karma-jasmine-single@1.0.2...@hirez_io/karma-jasmine-single@1.0.3) (2022-03-13)
15 |
16 | **Note:** Version bump only for package @hirez_io/karma-jasmine-single
17 |
18 |
19 |
20 |
21 |
22 | ## [1.0.2](https://github.com/hirezio/single/compare/@hirez_io/karma-jasmine-single@1.0.1...@hirez_io/karma-jasmine-single@1.0.2) (2022-03-13)
23 |
24 | **Note:** Version bump only for package @hirez_io/karma-jasmine-single
25 |
26 |
27 |
28 |
29 |
30 | ## [1.0.1](https://github.com/hirezio/single/compare/@hirez_io/karma-jasmine-single@1.0.0...@hirez_io/karma-jasmine-single@1.0.1) (2021-08-08)
31 |
32 | **Note:** Version bump only for package @hirez_io/karma-jasmine-single
33 |
34 |
35 |
36 |
37 |
38 | # 1.0.0 (2021-07-29)
39 |
40 | **Note:** Version bump only for package @hirez_io/karma-jasmine-single
41 |
--------------------------------------------------------------------------------
/packages/karma-jasmine-single/README.md:
--------------------------------------------------------------------------------
1 | # `@hirez_io/karma jasmine-single` ⚒
2 |
3 | A karma plugin for loading [@hirez_io/jasmine-single](https://github.com/hirezio/single/tree/main/packages/jasmine-single)
4 |
5 | [](https://www.npmjs.org/package/@hirez_io/karma-jasmine-single)
6 | [](http://npm-stat.com/charts.html?package=@hirez_io/karma-jasmine-single&from=2017-07-26)
7 | 
8 | [](https://lerna.js.org/)
9 | [](../../CODE_OF_CONDUCT.md)
10 | [](https://opensource.org/licenses/MIT)
11 | [](#contributors-)
12 |
13 |
14 |
22 |
23 | ## Installation
24 |
25 | ```
26 | yarn add -D @hirez_io/karma-jasmine-single
27 | ```
28 |
29 | or
30 |
31 | ```
32 | npm install -D @hirez_io/karma-jasmine-single
33 | ```
34 |
35 | This plugin was inspired by [karma-jasmine-single](https://github.com/kirisu/karma-jasmine-single)) which loads the original "jasmine-single".
36 |
37 | I rewrote it to save you the hassle of loading @hirez_io/jasmine-single's script files yourself. 😎
38 |
39 | ## Configuration
40 |
41 | Here's how to modify your `karma.conf.js`:
42 |
43 | ```js
44 | // karma.conf.js
45 |
46 | module.exports = function(config) {
47 | config.set({
48 |
49 | plugins: [
50 | require('karma-jasmine'),
51 | require('@hirez_io/karma-jasmine-single'), // <-- ADD THIS
52 | require('karma-chrome-launcher')
53 | // other plugins you might have...
54 | ],
55 |
56 | frameworks: [
57 | '@hirez_io/jasmine-single', // <-- ADD THIS
58 | 'jasmine',
59 | // other frameworks...
60 | ],
61 |
62 | // ...
63 | ```
64 |
65 | Want to contribute? Yayy! 🎉
66 |
67 | Please read and follow our [Contributing Guidelines](../../CONTRIBUTING.md) to learn what are the right steps to take before contributing your time, effort and code.
68 |
69 | Thanks 🙏
70 |
71 |
72 |
73 | ## Code Of Conduct
74 |
75 | Be kind to each other and please read our [code of conduct](../../CODE_OF_CONDUCT.md).
76 |
77 |
78 |
79 |
80 | ## Contributors ✨
81 |
82 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
83 |
84 |
85 |
86 |
87 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
99 |
100 |
101 |
102 | ## License
103 |
104 | MIT
105 |
--------------------------------------------------------------------------------
/packages/karma-jasmine-single/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Tue Aug 11 2020 14:21:54 GMT+0300 (Israel Daylight Time)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | plugins: [
8 | require('karma-jasmine'),
9 | require('@hirez_io/karma-jasmine-single'),
10 | require('karma-chrome-launcher')
11 | ],
12 |
13 | // base path that will be used to resolve all patterns (eg. files, exclude)
14 | basePath: '',
15 |
16 |
17 | // frameworks to use
18 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
19 | frameworks: ['@hirez_io/jasmine-single', 'jasmine'],
20 |
21 | // list of files / patterns to load in the browser
22 | files: [
23 | './lib/karma-jasmine-single.spec.js'
24 | ],
25 |
26 | // test results reporter to use
27 | // possible values: 'dots', 'progress'
28 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
29 | reporters: ['progress'],
30 |
31 |
32 | // web server port
33 | port: 9876,
34 |
35 |
36 | // enable / disable colors in the output (reporters and logs)
37 | colors: true,
38 |
39 |
40 | // level of logging
41 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
42 | logLevel: config.LOG_INFO,
43 |
44 |
45 | // enable / disable watching file and executing tests whenever any file changes
46 | autoWatch: false,
47 |
48 |
49 | // start these browsers
50 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
51 | browsers: ['ChromeHeadless'],
52 | // browsers: ['Chrome'],
53 |
54 |
55 | // Continuous Integration mode
56 | // if true, Karma captures browsers, runs the tests and exits
57 | singleRun: true,
58 |
59 | // Concurrency level
60 | // how many browser should be started simultaneous
61 | concurrency: Infinity
62 | })
63 | }
64 |
--------------------------------------------------------------------------------
/packages/karma-jasmine-single/lib/karma-jasmine-single.js:
--------------------------------------------------------------------------------
1 | var createPattern = function (file) {
2 | return {
3 | pattern: file,
4 | included: true,
5 | served: true,
6 | watched: false,
7 | };
8 | };
9 |
10 | var initJasmineSingle = function (files) {
11 | const jasmineSingleFile = createPattern(require.resolve('@hirez_io/jasmine-single'));
12 |
13 | // Find "karma-jasmine" last file (adapter.js) to make sure
14 | // "jasmine" is loaded before "jasmine-single"
15 | const karmaJasmineAdapterFileIndex = files.findIndex((file) => {
16 | return file.pattern.indexOf('adapter.js') !== -1;
17 | });
18 |
19 | if (karmaJasmineAdapterFileIndex !== -1) {
20 | files.splice(karmaJasmineAdapterFileIndex + 1, 0, jasmineSingleFile);
21 | } else {
22 | files.unshift(jasmineSingleFile);
23 | }
24 | };
25 |
26 | initJasmineSingle.$inject = ['config.files'];
27 |
28 | module.exports = {
29 | 'framework:@hirez_io/jasmine-single': ['factory', initJasmineSingle],
30 | 'framework:@hirez_io/karma-jasmine-single': ['factory', initJasmineSingle],
31 | };
32 |
--------------------------------------------------------------------------------
/packages/karma-jasmine-single/lib/karma-jasmine-single.spec.js:
--------------------------------------------------------------------------------
1 | describe('Karma Jasmine Single', function(){
2 |
3 | let num;
4 |
5 | Given(function () {
6 | num = 1;
7 | });
8 |
9 | When(function(){
10 | num++;
11 | });
12 |
13 | Then(function(){
14 | expect(num).toBe(2);
15 | });
16 |
17 | });
--------------------------------------------------------------------------------
/packages/karma-jasmine-single/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hirez_io/karma-jasmine-single",
3 | "version": "1.0.4",
4 | "publishConfig": {
5 | "access": "public",
6 | "registry": "https://registry.npmjs.org"
7 | },
8 | "author": {
9 | "name": "Shai Reznik",
10 | "company": "HiRez.io"
11 | },
12 | "description": "A karma plugin for loading @hirez_io/jasmine-single",
13 | "keywords": [
14 | "jasmine",
15 | "jasmine-single",
16 | "jasmine-karma",
17 | "karma",
18 | "gwt",
19 | "Given When Then",
20 | "Microtests",
21 | "Testing",
22 | "Unit tests",
23 | "JavasScript Unit Tests",
24 | "TypeScript Unit Tests",
25 | "hirez.io"
26 | ],
27 | "homepage": "https://github.com/hirezio/single/tree/main/packages/karma-jasmine-single",
28 | "repository": {
29 | "type": "git",
30 | "url": "https://github.com/hirezio/single",
31 | "directory": "packages/karma-jasmine-single"
32 | },
33 | "license": "MIT",
34 | "main": "lib/karma-jasmine-single.js",
35 | "scripts": {
36 | "build": "echo build is not needed for the karma plugin",
37 | "test": "karma start"
38 | },
39 | "bugs": {
40 | "url": "https://github.com/shairez/single/issues"
41 | },
42 | "devDependencies": {
43 | "karma": "6.3.17",
44 | "karma-chrome-launcher": "3.1.1",
45 | "karma-jasmine": "4.0.1"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/shared/single-core/single-core.ts:
--------------------------------------------------------------------------------
1 | export function getOnlyOneError(funcName: string): string {
2 | return `There must be only 1 "${funcName}" per test`;
3 | }
4 |
5 | export function getMustBeAChildError(funcName: string): string {
6 | let parentName = 'given';
7 | if (funcName === 'then') {
8 | parentName = 'when';
9 | }
10 | return `"${funcName}" must be a child of the function "${parentName}"`;
11 | }
12 |
13 | export const WHEN_IS_ASYNC_ERROR = `
14 | ====================================
15 | You wrapped the "when" callback with "async".
16 | Please wrap the "given" callback with async as well and run "when" with await like this:
17 |
18 | 👇
19 | given('description', async () => {
20 | 👇
21 | await when('description', async () => {
22 |
23 | ... your "await" code ...
24 |
25 | });
26 | })
27 | ====================================
28 | `
29 |
30 | declare global {
31 | const given: GivenFunction;
32 | const fgiven: TestFunction;
33 | const xgiven: TestFunction;
34 | const when: TestFunction;
35 | const then: TestFunction;
36 | }
37 |
38 | export interface TestFunction{
39 | (description: string, callback: TestCallback): void
40 | }
41 |
42 | export interface GivenFunction extends TestFunction {
43 | only: TestFunction;
44 | skip: TestFunction;
45 | }
46 |
47 | export interface DoneFn {
48 | (...args: Error[]): void;
49 | fail: (...args: Error[]) => void;
50 | }
51 |
52 | export interface TestCallback {
53 | (done: DoneFn, ...args:any[]): any;
54 | }
55 |
56 | const root = (1, eval)('this');
57 |
58 | let currentRunningFunction: 'given' | 'when' | 'then' | undefined;
59 | let numberOfWhens: number;
60 | let numberOfThens: number;
61 | let doneFunctionIsSet: boolean = false;
62 | let innerPromiseQueue: Promise[];
63 |
64 | beforeEach(function () {
65 | currentRunningFunction = undefined;
66 | numberOfWhens = 0;
67 | numberOfThens = 0;
68 | doneFunctionIsSet = false;
69 | innerPromiseQueue = [];
70 | });
71 |
72 | root.given = function given(givenDescription: string, callback: (...args:any[])=>any) {
73 | if (currentRunningFunction !== undefined) {
74 | throw new Error(getOnlyOneError('given'));
75 | }
76 |
77 | const fullDescription = getFullDescription(givenDescription, callback);
78 | it(fullDescription, getGivenCallbackWrapper(callback));
79 | };
80 |
81 | root.given.only = root.fgiven = function given(givenDescription: string, callback: (...args:any[])=>any) {
82 | if (currentRunningFunction !== undefined) {
83 | throw new Error(getOnlyOneError('given'));
84 | }
85 | const fullDescription = getFullDescription(givenDescription, callback);
86 | fit(fullDescription, getGivenCallbackWrapper(callback));
87 | };
88 |
89 | function getFullDescription(givenDescription: string, callback: (...args:any[])=>any): string{
90 | const functionContent = callback.toString();
91 | const regex = /when\(["'`](.*?)["'`],.*then\(["'`](.*?)["'`],/mgs;
92 |
93 | const match = regex.exec(functionContent);
94 |
95 | let fullDescription = `
96 | GIVEN ${givenDescription}`;
97 |
98 | if (match) {
99 | /* istanbul ignore next */
100 | if (match[1]) {
101 | fullDescription += `
102 | WHEN ${match[1]}`;
103 | }
104 | /* istanbul ignore next */
105 | if (match[2]) {
106 | fullDescription += `
107 | THEN ${match[2]}`;
108 | }
109 | }
110 |
111 | return fullDescription;
112 | }
113 |
114 | function getGivenCallbackWrapper(callback: (...args:any[])=>any) {
115 | return async function givenCallbackWrapper() {
116 |
117 | currentRunningFunction = 'given';
118 |
119 | await promisify(callback);
120 |
121 | if (innerPromiseQueue.length > 0) {
122 | for (const promise of innerPromiseQueue) {
123 |
124 | await promise;
125 |
126 | }
127 | }
128 |
129 | }
130 | }
131 |
132 | root.given.skip = root.xgiven = function given(description: string, callback: any) {
133 | xit(description, callback);
134 | };
135 |
136 |
137 | root.when = function when(description: string, callback: (...args:any[])=>any) {
138 | numberOfWhens++;
139 | if (numberOfWhens > 1) {
140 | throw new Error(getOnlyOneError('when'));
141 | }
142 |
143 | if (currentRunningFunction !== 'given') {
144 | throw new Error(getMustBeAChildError('when'));
145 | }
146 |
147 | const oldContext = currentRunningFunction;
148 | currentRunningFunction = 'when';
149 |
150 | const potentialPromise = callback();
151 | if (potentialPromise) {
152 | const then = potentialPromise.then;
153 | /* istanbul ignore else */
154 | if (typeof then === 'function') {
155 | const whenPromise = then.call(potentialPromise, () => {
156 | currentRunningFunction = oldContext;
157 | })
158 |
159 | innerPromiseQueue.push(whenPromise);
160 |
161 | } else {
162 | /* istanbul ignore next */
163 | currentRunningFunction = oldContext;
164 | }
165 | } else {
166 | currentRunningFunction = oldContext;
167 | }
168 | };
169 |
170 | root.then = function then(description: string, callback: (...args:any[])=>any) {
171 | numberOfThens++;
172 | if (numberOfThens > 1) {
173 | throw new Error(getOnlyOneError('then'));
174 | }
175 |
176 | if (!doneFunctionIsSet && currentRunningFunction !== 'when') {
177 | throw new Error(getMustBeAChildError('then'));
178 | }
179 |
180 | const oldContext = currentRunningFunction;
181 | currentRunningFunction = 'then';
182 |
183 | const potentialPromise = callback();
184 | if (potentialPromise) {
185 | const then = potentialPromise.then;
186 | /* istanbul ignore else */
187 | if (typeof then === 'function') {
188 | const thenPromise = then.call(potentialPromise, () => {
189 | currentRunningFunction = oldContext;
190 | });
191 | innerPromiseQueue.push(thenPromise);
192 | } else {
193 | /* istanbul ignore next */
194 | currentRunningFunction = oldContext;
195 | }
196 | } else {
197 | currentRunningFunction = oldContext;
198 | }
199 | };
200 |
201 | async function promisify(fn: TestCallback): Promise {
202 | if (doesFunctionHaveParams(fn)) {
203 | doneFunctionIsSet = true;
204 | return new Promise((resolve, reject) => {
205 | function next(err: Error) {
206 | if (err) {
207 | reject(err);
208 | return;
209 | }
210 | resolve(undefined as any);
211 | }
212 | next.fail = function nextFail(err: Error) {
213 | reject(err);
214 | };
215 |
216 | fn.call(this, next);
217 | });
218 | }
219 | return await (fn as () => any).call(this);
220 | }
221 |
222 | function doesFunctionHaveParams(fn: (...args: any[]) => any) {
223 | return fn.length > 0;
224 | }
225 |
--------------------------------------------------------------------------------
/update-core-hash.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 |
3 | const fs = require('fs').promises;
4 | const execa = require('execa');
5 |
6 | (async () => {
7 | try {
8 | const coreFileName = 'shared/single-core/single-core.ts';
9 | const jestCacheOfHashFileName = 'packages/jest-single/.core-version';
10 | const jasmineCacheOfHashFileName = 'packages/jasmine-single/.core-version';
11 |
12 | const {stdout} = await execa('git', ['hash-object', coreFileName]);
13 | const latestCoreHash = stdout;
14 |
15 | const jestCachedHash = await fs.readFile(jestCacheOfHashFileName, 'utf8');
16 | const jasmineCachedHash = await fs.readFile(jasmineCacheOfHashFileName, 'utf8');
17 |
18 | if (jestCachedHash === latestCoreHash && jasmineCachedHash === latestCoreHash) {
19 | console.log('No changes in single-core');
20 | return;
21 | }
22 |
23 | await fs.writeFile(jestCacheOfHashFileName, latestCoreHash);
24 | await fs.writeFile(jasmineCacheOfHashFileName, latestCoreHash);
25 |
26 | console.log(
27 | 'Updated single-core hash to help lerna know that it needs to bump versions'
28 | );
29 |
30 | // await execa('git', ['add', jestCacheOfHashFileName, jasmineCacheOfHashFileName]);
31 |
32 | // const { stdout } = await execa('git', ['commit', '--amend', '--no-edit']);
33 | console.log(stdout);
34 | } catch (error) {
35 | console.error(error);
36 | }
37 | })();
38 |
--------------------------------------------------------------------------------