diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 0b60675..f004510 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -2,7 +2,7 @@ name: CD Build on: push: - branches: [ main ] + branches: [ main, next, beta, alpha ] jobs: build: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 568fd7c..ac423e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI build on: pull_request: - branches: [main] + branches: [ main, next, beta, alpha ] jobs: build: diff --git a/.releaserc.json b/.releaserc.json index 52382c6..1f45671 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -1,5 +1,18 @@ { - "branches": "main", + "branches": [ + "+([0-9])?(.{+([0-9]),x}).x", + "main", + "next", + "next-major", + { + "name": "beta", + "prerelease": true + }, + { + "name": "alpha", + "prerelease": true + } + ], "repositoryUrl": "https://github.com/herbsjs/aloe", "debug": "true", "plugins": [ diff --git a/CHANGELOG.md b/CHANGELOG.md index 230a2fa..b69cf3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +# [2.3.0-beta.2](https://github.com/herbsjs/aloe/compare/v2.3.0-beta.1...v2.3.0-beta.2) (2023-07-27) + + +### Bug Fixes + +* **lint:** fix lint error ([6fc20d0](https://github.com/herbsjs/aloe/commit/6fc20d059e71c50aec81a545f47ee506d5a6d6f3)) +* **prettystack:** fix prettyStack when error is not a Error instance ([d73ff5f](https://github.com/herbsjs/aloe/commit/d73ff5fb1dc434e770546bc6b7bf849425fce52d)) + +# [2.3.0-beta.1](https://github.com/herbsjs/aloe/compare/v2.2.2-beta.3...v2.3.0-beta.1) (2023-05-12) + + +### Features + +* **scenario:** only - run only scenarios marked as .only ([8ce67a0](https://github.com/herbsjs/aloe/commit/8ce67a023220cfe8679c43a7885d97072499cb46)), closes [#7](https://github.com/herbsjs/aloe/issues/7) + +## [2.2.2-beta.3](https://github.com/herbsjs/aloe/compare/v2.2.2-beta.2...v2.2.2-beta.3) (2023-03-29) + + +### Bug Fixes + +* **package.json:** fix export main ([08e01ed](https://github.com/herbsjs/aloe/commit/08e01edc5575d74a6ee1499a594ed156d93c8bcb)) + +## [2.2.2-beta.2](https://github.com/herbsjs/aloe/compare/v2.2.2-beta.1...v2.2.2-beta.2) (2023-03-29) + + +### Bug Fixes + +* **depencency:** buchu version ([751ff23](https://github.com/herbsjs/aloe/commit/751ff235e81a7742af6595afd5c67dd22a5bc9ce)) + +## [2.2.2-beta.1](https://github.com/herbsjs/aloe/compare/v2.2.1...v2.2.2-beta.1) (2023-03-29) + + +### Bug Fixes + +* **touch:** ping ([4b55fe9](https://github.com/herbsjs/aloe/commit/4b55fe9929b978a647ac89ba498fa3ff428e6208)) + ## [2.2.1](https://github.com/herbsjs/aloe/compare/v2.2.0...v2.2.1) (2023-03-20) diff --git a/README.md b/README.md index e091183..cc751ef 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,9 @@ const taskCountSpec = spec({ }) ``` -It is possible to have many `given` and `check` on the same scenario. +It is possible to have many `given` and `check` on the same scenario. + + ```javascript const billingSpec = spec({ @@ -62,6 +64,17 @@ const billingSpec = spec({ }), }) ``` +It's also possible to run `only` a specific scenario. + +```javascript +const taskCountSpec = spec({ + 'Change count for the task': scenario.only({ + 'Given a valid task': given({ task: 'do it', count: 0 }), + 'When increase count': when((ctx) => (ctx.count++)), + 'Must have a increased count': check((ctx) => { assert.ok(ctx.count === 1) }), + }), +}) +``` ### Use Case Spec @@ -174,7 +187,7 @@ const createUserSpec = spec({ }), }) ``` - + ### Runner The runner is responsible for executing the scenarios and showing the results. @@ -201,7 +214,7 @@ await runner() // specsPath can be especified - [ ] doc on web site - [ ] CLI: doc `spec` command - [ ] skip -- [ ] only +- [X] only - [ ] todo / pending - [ ] nested scenarios - [ ] tests for runner diff --git a/package-lock.json b/package-lock.json index 0a8d93a..07b396e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@herbsjs/aloe", - "version": "2.2.1", + "version": "2.3.0-beta.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@herbsjs/aloe", - "version": "2.2.1", + "version": "2.3.0-beta.2", "license": "MIT", "dependencies": { "chalk": "^4.1.2", @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.18.2", - "@herbsjs/buchu": "^1.6.2", + "@herbsjs/buchu": "^2.1.1", "@semantic-release/changelog": "^6.0.1", "@semantic-release/commit-analyzer": "^9.0.2", "@semantic-release/git": "^10.0.1", @@ -35,7 +35,7 @@ "node": ">= 16.x" }, "peerDependencies": { - "@herbsjs/buchu": "2.1.0" + "@herbsjs/buchu": "^2.1.1" }, "peerDependenciesMeta": { "@herbsjs/buchu": { @@ -626,40 +626,40 @@ } }, "node_modules/@herbsjs/buchu": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@herbsjs/buchu/-/buchu-1.6.2.tgz", - "integrity": "sha512-5lwFFS9LJH2/JLd+xdlpop1RFr+Ad0S1neNenvrdpQjlm75bOj7l/v0rxnwcujDasHhc1oUyzm+TEGAJoPqrkg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@herbsjs/buchu/-/buchu-2.1.1.tgz", + "integrity": "sha512-8aZ6sMmLh2qBczdlNljIkznckmz9o+wZfzB2+/vufLXBBZB4J+2rlvZ/2L8UChSRODHn2iwzuymBlix3TzImZQ==", "dev": true, "dependencies": { - "@herbsjs/suma": "^1.3.1" + "@herbsjs/suma": "^1.4.0" }, "engines": { - "node": ">= 14.x" + "node": ">= 16.x" }, "optionalDependencies": { - "@herbsjs/gotu": "^1.2.0" + "@herbsjs/gotu": "^1.3.0" } }, "node_modules/@herbsjs/gotu": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@herbsjs/gotu/-/gotu-1.2.0.tgz", - "integrity": "sha512-hOwEuEapqzQOd01YdfXQAqTYJCtwRwUrz/CGHOjF5At1tf8Ojnh0jK5j66LH1yKOXF2YGkBtVMp3/V+iSoEPOQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@herbsjs/gotu/-/gotu-1.3.0.tgz", + "integrity": "sha512-FrTUQoUn043SuYbhEKP/oFmHsQJtREx4CLDtKS+0MJIZwbnLQQpvmXWHXxQwVBDHYDDH5RNyttEeezJvbr7ZeQ==", "dev": true, "optional": true, "dependencies": { - "@herbsjs/suma": "^1.3.1" + "@herbsjs/suma": "^1.4.0" }, "engines": { - "node": ">= 14.x" + "node": ">= 16.x" } }, "node_modules/@herbsjs/suma": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@herbsjs/suma/-/suma-1.3.1.tgz", - "integrity": "sha512-OGRqse3k1peitBGMJEiyGhESozmCujo3PMkupRIOUFogj3YicFNl2UCmADnse+lRPKfN6QQGx7e6A5Ey9pxA2g==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@herbsjs/suma/-/suma-1.4.0.tgz", + "integrity": "sha512-te2yTDV9IjidpKoAZHLmGdS5yktVOOCya5QuM8ohVbScaTCzQU2oGC7B4iOuIiRX5Oic+mrhX/cAT/J4Rk8Ctg==", "dev": true, "engines": { - "node": ">= 14.x" + "node": ">= 16.x" } }, "node_modules/@humanwhocodes/config-array": { diff --git a/package.json b/package.json index d30fd5a..def2faa 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "@herbsjs/aloe", - "version": "2.2.1", + "version": "2.3.0-beta.2", "description": "Scenario description and test runner for Herbs", "main": "./src/aloe.js", "exports": { + ".": "./src/aloe.js", "./runner": "./src/runner/runner.js" }, "scripts": { @@ -38,7 +39,7 @@ "license": "MIT", "homepage": "https://github.com/herbsjs/aloe#readme", "peerDependencies": { - "@herbsjs/buchu": "2.1.0" + "@herbsjs/buchu": "^2.1.1" }, "peerDependenciesMeta": { "@herbsjs/buchu": { @@ -47,7 +48,7 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.18.2", - "@herbsjs/buchu": "^1.6.2", + "@herbsjs/buchu": "^2.1.1", "@semantic-release/changelog": "^6.0.1", "@semantic-release/commit-analyzer": "^9.0.2", "@semantic-release/git": "^10.0.1", diff --git a/src/runner/prettyStack.js b/src/runner/prettyStack.js index 053521b..2ed46f7 100644 --- a/src/runner/prettyStack.js +++ b/src/runner/prettyStack.js @@ -12,6 +12,12 @@ const themes = { function prettyStack({ error, theme = themes.herbs }) { + if (!(error instanceof Error)) { + /* eslint-disable no-console */ + console.info(`\n `, chalk.ansi256(theme['main'])('Error:'), chalk.ansi256(theme['message'])(chalk.bgAnsi256(theme['backgroundMessage'])(error)), `\n`) + return + } + // Replace the error message with a colorized version let messageStack = error.stack.replace(error.message, chalk.ansi256(theme['message'])(chalk.bgAnsi256(theme['backgroundMessage'])(error.message))) diff --git a/src/runner/runner.js b/src/runner/runner.js index 04e026a..b78ea68 100644 --- a/src/runner/runner.js +++ b/src/runner/runner.js @@ -63,9 +63,9 @@ async function runner({ specs, herbarium, specsPath, dependencies = {} }) { return orderedGroup } - async function showScenarios(spec, previousGroup, groupName, errorCount, successCount) { const ret = await spec.run() + if (ret === state.ignored) return { previousGroup, errorCount, successCount } const color = ret !== failed ? white : red if (previousGroup !== groupName) { if (groupName === undefinedGroup) @@ -79,6 +79,8 @@ async function runner({ specs, herbarium, specsPath, dependencies = {} }) { for (const scenario of spec.scenarios) { + if (scenario.state === state.ignored) continue + function countSamples(scenario) { let count = 0 for (const sample of scenario.samples) { @@ -147,6 +149,21 @@ async function runner({ specs, herbarium, specsPath, dependencies = {} }) { return { errorCount, successCount } } + function prepareForOnly() { + // build all the specs + specs.forEach(meta => meta.spec.build()) + + // if there is one or more specs with only, mark all the others as ignored + const hasOnly = Array.from(specs).some(([_, meta]) => meta.spec.only) + if (hasOnly) { + specs.forEach(meta => { + if (!meta.spec.only) meta.spec.ignore = true + }) + } + } + + prepareForOnly() + const usecasesGroup = groupSteps(specs) for (const groupName in usecasesGroup) { diff --git a/src/runningState.js b/src/runningState.js index cbff8c4..ca38ddc 100644 --- a/src/runningState.js +++ b/src/runningState.js @@ -3,4 +3,5 @@ module.exports.state = Object.freeze({ done: 'done', passed: 'passed', failed: 'failed', + ignored: 'ignored', }) diff --git a/src/scenario.js b/src/scenario.js index 948b12d..5f46d49 100644 --- a/src/scenario.js +++ b/src/scenario.js @@ -114,12 +114,14 @@ class SamplesExecution { class Scenario { - constructor(description, body) { + constructor(description, body, options = {}) { this.type = 'scenario' this.state = state.ready this.description = description + this.ignore = false this._auditTrail = { type: this.type, state: this.state, description: this.description } this._body = body + this.only = options.only } async run() { @@ -138,6 +140,12 @@ class Scenario { this.samples = intialized.filter(s => s.isSamples) addBuiltinSample() + if(this.ignore) { + this.state = state.ignored + this._auditTrail.state = this.state + return this.state + } + for (const samples of this.samples) { const execution = new SamplesExecution(this, samples) samples.execution = execution @@ -186,8 +194,11 @@ class Scenario { } } -const scenario = (body) => ({ - create: (description) => { return new Scenario(description, body) } +const scenario = (body, options) => ({ + create: (description) => { return new Scenario(description, body, options) } }) +scenario.only = (body, options) => scenario(body, Object.assign({}, options, { only: true })) + + module.exports = { scenario } diff --git a/src/spec.js b/src/spec.js index 006892f..ff0981e 100644 --- a/src/spec.js +++ b/src/spec.js @@ -5,17 +5,20 @@ class Spec { constructor(body) { this.type = 'spec' this.state = state.ready + this.ignore = false this._auditTrail = { type: this.type, state: this.state } this._body = body } async run() { - // if (this._hasRun) - // return Err('Cannot run use case more than once. Try to instantiate a new object before run this use case.') - // this._hasRun = true this.build() + if(this.ignore) { + this.state = state.ignored + return this.state + } + // scenario for (const scenario of this.scenarios) { await scenario.run() @@ -29,11 +32,7 @@ class Spec { } build() { - const description = (entry) => { - const [description, scenario] = entry - scenario.description = description - return scenario - } + if (this._hasBuild) return const addUsecase = (scenario) => { if (this.usecase) scenario.usecase = this.usecase return scenario @@ -41,12 +40,16 @@ class Spec { this.usecase = this._body.usecase const entries = Object.entries(this._body) const intialized = entries.map(([k, v]) => v.create ? v.create(k) : {}) + this.only = intialized.find((scenario) => scenario.only) + if (this.only) intialized.forEach((scenario) => { + if (!scenario.only) scenario.ignore = true + }) + this.scenarios = intialized .filter(s => s.isScenario) .map(addUsecase) - // run flag - // this._hasRun = false + this._hasBuild = true } doc() { @@ -59,10 +62,6 @@ class Spec { return doc } - // get auditTrail() { - // return this._mainStep.auditTrail - // } - get isSpec() { return true } diff --git a/test/scenario.test.js b/test/scenario.test.js index 416f829..4294289 100644 --- a/test/scenario.test.js +++ b/test/scenario.test.js @@ -103,7 +103,7 @@ describe('A scenario', () => { //then // - firts, it should not throw a exception, then: - assert.ok(ret, state.passed) + assert.equal(ret, state.passed) assert.strictEqual(instance.description, 'A scenario') assert.strictEqual(instance.info, 'A simple scenario') assert.strictEqual(instance.samples[0].description, '') @@ -186,7 +186,7 @@ describe('A scenario', () => { //then // - firts, it should not throw a exception, then: - assert.ok(ret, state.failed) + assert.equal(ret, state.failed) assert.strictEqual(instance.description, 'A failed scenario') assert.strictEqual(instance.info, 'A simple scenario') assert.strictEqual(instance.samples[0].description, '') @@ -379,7 +379,7 @@ describe('A scenario', () => { //then // - firts, it should not throw a exception, then: - assert.ok(ret, state.passed) + assert.equal(ret, state.passed) assert.strictEqual(instance.samples.length, 2) assert.strictEqual(instance.samples[0].state, state.done) for (const s of [0, 1]) { @@ -395,4 +395,61 @@ describe('A scenario', () => { }) }) + + context('with only', () => { + + const givenTheScenarioWithOnly = () => { + return scenario.only({ + 'Given a input': given(() => { }), + 'When running': when(() => { }), + 'Check output': check(() => { }), + }) + } + + it('should run', async () => { + //given + const factory = givenTheScenarioWithOnly() + const instance = factory.create('A scenario') + + //when + const ret = await instance.run() + + //then + // - firts, it should not throw a exception, then: + assert.equal(ret, state.passed) + assert.strictEqual(instance.description, 'A scenario') + assert.strictEqual(instance.samples[0].description, '') + assert.strictEqual(instance.samples[0].builtin, true) + assert.strictEqual(instance.samples[0].execution.scenarios[0].stage, 'check') + }) + }) + + context('with ignore flag', () => { + + const givenTheScenarioWithIgnore = () => { + return scenario({ + 'Given a input': given(() => { }), + 'When running': when(() => { }), + 'Check output': check(() => { }), + }) + } + + it('should run', async () => { + //given + const factory = givenTheScenarioWithIgnore() + const instance = factory.create('A scenario') + instance.ignore = true + + //when + const ret = await instance.run() + + //then + // - firts, it should not throw a exception, then: + assert.equal(ret, state.ignored) + assert.strictEqual(instance.description, 'A scenario') + assert.strictEqual(instance.samples[0].description, '') + assert.strictEqual(instance.samples[0].builtin, true) + assert.strictEqual(instance.samples[0].state, state.ready) + }) + }) }) diff --git a/test/spec.test.js b/test/spec.test.js index a1bce2c..469dd5e 100644 --- a/test/spec.test.js +++ b/test/spec.test.js @@ -294,4 +294,88 @@ describe('A spec', () => { }) it('for an entity') + + context('with scenarios with only', async () => { + const givenTheGenericSpecWithOnly = () => { + const ASpec = spec({ + 'Scenario 1': scenario({ + info: 'A simple scenario', + 'Given a input': given(() => ({ + id: 'a', + })), + 'When running': when((ctx) => { + ctx.id = 'b' + }), + 'Check another output': check((ctx) => { + assert.ok(ctx.id === 'b') + }), + }), + 'Scenario 2': scenario.only({ + info: 'A simple scenario', + 'Given a input': given(() => ({ + id: 'a', + })), + 'When running': when((ctx) => { + ctx.id = 'b' + }), + 'Check another output': check((ctx) => { + assert.ok(ctx.id === 'b') + }), + }), + }) + + return ASpec + } + + it('should run', async () => { + //given + const instance = givenTheGenericSpecWithOnly() + //when + const ret = await instance.run() + //then + // - firts, it should not throw a exception, then: + assert.strictEqual(ret, state.passed) + assert.strictEqual(instance.scenarios[0].state, state.ignored) + assert.strictEqual(instance.scenarios[1].state, state.passed) + }) + + it('should audit after run') + + }) + + context('with ignore flag', () => { + const givenTheGenericSpecWithIgnore = () => { + const ASpec = spec({ + 'Scenario 1': scenario({ + info: 'A simple scenario', + ignore: true, + 'Given a input': given(() => ({ + id: 'a', + })), + 'When running': when((ctx) => { + ctx.id = 'b' + }), + 'Check another output': check((ctx) => { + assert.ok(ctx.id === 'b') + }), + }), + }) + + return ASpec + } + + it('should run', async () => { + //given + const instance = givenTheGenericSpecWithIgnore() + instance.ignore = true + //when + const ret = await instance.run() + //then + // - firts, it should not throw a exception, then: + assert.strictEqual(ret, state.ignored) + assert.strictEqual(instance.scenarios[0].state, state.ready) + }) + + it('should audit after run') + }) })