npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

forgiven

v0.0.8

Published

Extensible given/when/then and more for test frameworks

Downloads

13

Readme

Forgiven

Build Status Coverage Status

Extensible given/when/then and more for test frameworks

Usage

install forgiven and a UI factory (forgiven-mocha in this case) as a development dependency.

npm install --save-dev mocha forgiven forgiven-mocha

Then initialize a new given function, with the UI factory

import {
  create,
} from 'forgiven';
import {
  mocha,
} from 'forgiven-mocha';

global.given = create(mocha);

The given function can then be used to create concise test chains in place of the verbose describe/beforeEach/it/afterEach format.

import {
  greet,
} from '../src/greet';

let greeting;
let person;

describe('greet', () => {
  given(() => greeting = 'hello')
  .and(() => person = 'fred')
  .then(() => greet(greeting, person).should.eql('hello fred'))
  .end();
});

Note that the chain can consist of multiple setup steps followed by multiple test steps. Test steps start after the first then after which only test steps are allowed.

The chain must be ended with a call to end which will actually render the chain so far (as an aside, multiple calls to end will render the tests multiple times... don't bother doing that).

Chains can also be reused to keep your tests DRY, but rememeber to only end them once after completely defining them.

const chain = given(() => greeting = 'hello')
.and(() => person = 'fred');

chain
.then(() => greet(greeting, person).should.eql('hello fred'));

chain
.or.with(() => greeting = 'bonjour')
.then(() => greet(greeting, person).should.eql('bonjour fred'));

chain
.or.with(() => greeting = 'ola')
.then(() => greet(greeting, person).should.eql('ola fred'));

chain.end();

You may prefer to use the fork method for reusing chains in order to layout your shared chains more clearly.

given(() => greeting = 'hello')
.and(() => person = 'fred')
.fork((chain) => {
  chain
  .then(() => greet(greeting, person).should.eql('hello fred'));

  chain
  .or.with(() => greeting = 'bonjour')
  .then(() => greet(greeting, person).should.eql('bonjour fred'));

  chain
  .or.with(() => greeting = 'ola')
  .then(() => greet(greeting, person).should.eql('ola fred'));
})
.end();

Another bonus to using fork is that if you attempt to end a chain inside a fork then an error will be thrown.

So we have only seen the most concise form of setup and test definition, however the full function signatures for setup steps are

setup(description, beforeEach, afterEach);
setup(beforeEach, afterEach);
setup(beforeEach);
setup({description, beforeEach, afterEach});

When no description is given then the body of the beforeEach callback will be used as the description.

The full function signatures for test steps are

test(description, test);
test(test);
test({description, test});

When no description is given then the body of the test callback will be used as the description.

You will have noticed that various grammatical constructions can be made. There are in fact a fixed list of words that can be used for setup steps and another list for test steps. The choice of wording only affects the generated test reports as they are prepended to the descriptions. The words can also be chained indefinitely (even if it doesn't make sense) and they will all be prefixed to test and setup descriptions. The only limitation is that the setup chain must begin with given and the test phase must begin with then.

The valid words for setup steps are

export const SETUP_CONJUNCTIONS = [
  'given',
  'when',
  'where',
  'while',
  'with',
  'and',
  'or',
  'after',
  'once',
];

The valid words for test steps are

export const TEST_CONJUNCTIONS = [
  'then',
  'and',
];

Pending Setups and Tests

Any setup or test step can be marked as pending (skipped) using the following modifiers.

export const PENDING_MODIFIERS = [
  'skip',
  'pending',
  'eventually',
];

eg.

given(() => greeting = 'hello')
.and(() => person = 'fred')
.then.eventually(() => greet(greeting, person).should.eql('hello fred'))
.end();

It is then up to the UI factory to handle the pending flag if it supports it.

Exclusive Setups and Tests

Any setup or test step can be marked as exclusive (other tests will be skipped) using the following modifiers.

export const ONLY_MODIFIERS = [
  'only',
  'just',
  'exclusive',
];

eg.

given(() => greeting = 'hello')
.and(() => person = 'fred')
.then.just(() => greet(greeting, person).should.eql('hello fred'))
.end();

It is then up to the UI factory to handle the only flag if it supports it.

Plugins

Plugins can be defined and used to extend the setup steps so that complex behaviour can be expressed in a concise and natural manner. They are registered with the create method.

import {
  create,
} from 'forgiven';
import {
  mocha,
} from 'forgiven-mocha';
import {
  promise,
} from 'forgiven-promise';

global.given = create(mocha, {
  promise: promise,
});

The following plugins are currently available

Each plugin is registered with a name that can be used with setup steps using a determiner.

export const DETERMINERS = [
  'the',
  'a',
  'an',
];

For example

const context = {};

given.a.promise.as(context, 'promise').from(() => Promise.reject(new Error('FAIL')))
.then(() => context.promise.should.be.rejectedWith('FAIL'))
.end();

Creating a plugin is easy (well it depends how complicated you want to make it). A plugin is defined as a function that takes a setup step function as its only parameter. The function then returns an object or function to assign to the registered plugin name. It's important that something inside the plugin calls the setup function and eventually returns the return value so that the chain can be continued.

function myPlugin(setup) {
  return (params) => {
    return setup({
      description: params.description,
      beforeEach: params.beforeEach,
      afterEach: params.afterEach,
    });
  };
}

UI Factories

UI factories can be created to support various test frameworks depending on their features. Currently the following UI factories exist.

A UI factory is defined as a function that returns a function that calls the initial setup and handles setup and test callbacks. Any of the above factories provide an example but as a skeleton, the following should provide guidance.

// To define...
//
//  doSetupWithModifiers
//  doBeforeEach
//  doAfterEach
//  doTest
//

function setup({
  description,
  beforeEach,
  afterEach,
  pending,
  only,
}, callback) {
  // add the setup phase with modifiers
  // for pending and only if supported
  doSetupWithModifiers(description, pending, only, () => {
    // call the beforeEach/afterEach
    // functions if specified
    doBeforeEach(beforeEach);
    doAfterEach(afterEach);

    // insert child setups and tests
    callback(
      setup,
      test,
    );
  });
}

function test({description, pending, only, test}) {
  // add a test with modifiers if supported
  doTest(description, pending, only, test);
}

export function uiFactory(params, callback) {
  return () => {
    setup(params, callback);
  };
}

Contributing

Run tests and build before pushing/opening a pull request.

  • npm test - lint and test
  • npm start - watch and build, etc with alarmist
  • npm run build - run tests then build
  • npm run watch - watch for changes and run build
  • npm run ci - run build and submit coverage to coveralls