GithubHelp home page GithubHelp logo

riteway's Introduction

Riteway

Simple, readable, helpful unit tests.

  • Readable
  • Isolated/Integrated
  • Thorough
  • Explicit

Riteway forces you to write Readable, Isolated, and Explicit tests, because that's the only way you can use the API. It also makes it easier to be thorough by making test assertions so simple that you'll want to write more of them.

There are 5 questions every unit test must answer. Riteway forces you to answer them.

  1. What is the unit under test (module, function, class, whatever)?
  2. What should it do? (Prose description)
  3. What was the actual output?
  4. What was the expected output?
  5. How do you reproduce the failure?

Installing

npm install --save-dev riteway

Then add an npm command in your package.json:

"test": "riteway test/**/*-test.js",

Now you can run your tests with npm test. Riteway also supports full TAPE-compatible usage syntax, so you can have an advanced entry that looks like:

"test": "nyc riteway test/**/*-rt.js | tap-nirvana",

In this case, we're using nyc, which generates test coverage reports. The output is piped through an advanced TAP formatter, tap-nirvana that adds color coding, source line identification and advanced diff capabilities.

Troubleshooting

If you get an error like:

SyntaxError: Unexpected identifier
    at new Script (vm.js:79:7)
    at createScript (vm.js:251:10)
    at Object.runInThisContext (vm.js:303:10)
...

The problem is likely that you need a .babelrc configured with support for esm (standard JavaScript modules) and/or React. If you need React support, that might look something like:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": [
          "last 2 versions",
          "safari >= 7"
        ]
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      { "corejs": 2 }
    ]
  ]
}

To install babel devDependencies:

npm install --save-dev @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/register @babel/runtime-corejs2

And if you're using react:

npm install --save-dev @babel/preset-react

You can then update your test script in package.json to use babel:

"test": "node -r @babel/register source/test"

If you structure your folders by type like this:

├──todos
│  ├── component
│  ├── reducer
│  └── test
└──user
   ├── component
   ├── reducer
   └── test

Update your test script to find all files with your custom ending:

"test": "riteway -r @babel/register 'src/**/*.test.js' | tap-nirvana",

Usage with ESM Modules

If you want to use ESM modules instead of compiling, you'll need to import from the esm folder:

import { describe } from 'riteway/esm/riteway.js';
import { match } from 'riteway/esm/match.js';

// Note: If you're using a compiler for JSX, you might want to handle
// ESM modules with it, too, so you shouldn't need the ESM modules, but
// if you want to use them anyway:
import { render } from 'riteway/esm/render.js';

The script to run your tests should be run using node instead of riteway, like this:

"test": "node test/index-test.js",

Since the tests are being run using node, it will not be possible to use globs to select the test files. You're encouraged to create a single entry test file which imports all test files from your project.

Usage with SWC

SWC is a fast, Rust based compiler that is the new default compiler in Next.js 12+.

If you'd like to use it, there are some additional configuration steps:

  1. Configure SWC to recognize React and JSX.
  2. Configure your project to use absolute import paths.
  3. Configure SWC to recognize .module.css files or .css files in your Next.js project.
  4. Configure SWC to run styled-jsx plugins, if you use it.

Here is how you can compile your code with SWC and run Riteway tests.

Install @swc/core and @swc/register:

npm install --save-dev @swc/core @swc/register

or

yarn add --dev @swc/core @swc/register

Add a "test" script to your package.json:

"test": "node -r @swc/register src/test.js",

Create an .swcrc file with the options you need. Hint: Try the SWC Playground for help generating valid SWC configurations. Example with css modules, absolute path, and React support:

{
  "jsc": {
    "baseUrl": "./src",
    "paths": {
      "*.css": ["utils/identity-object-proxy.js"],
      "utils/*": ["utils/*"]
    },
    "parser": {
      "jsx": true,
      "syntax": "ecmascript"
    },
    "transform": {
      "react": {
        "runtime": "automatic"
      }
    },
    "experimental": {
      "plugins": [
        ["@swc/plugin-styled-jsx", {}]
      ]
    }
  },
  "module": {
    "type": "commonjs"
  }
}

The "baseUrl" setting combined with "utils/*": ["utils/*"] is an example if you're using an absolute import from src/utils, e.g. utils/pipe. You'll need to add this for every folder for which you're using absolute imports.

The "parser" and "transform" settings tell SWC how to handle JSX and files that use React without importing it.

The "module" settings allows you to use modern syntax like import.

"*.css": ["utils/identity-object-proxy.js"], tells SWC to replace all absolute CSS imports with an identity object proxy. (Note: relative imports like import './styles.css will NOT be replaced, and you need to convert them to absolute imports.) An identity object proxy is an object that returns each key stringified when accessed. For example obj.foo returns "foo".

You will need to create the identity object proxy yourself. Here is an example:

const identityObjectProxy = new Proxy(
  {},
  {
    get: function getter(target, key) {
      if (key === '__esModule') {
        return false;
      }

      return key;
    },
  },
);

export default identityObjectProxy;

Example Usage

import { describe, Try } from 'riteway';

// a function to test
const sum = (...args) => {
  if (args.some(v => Number.isNaN(v))) throw new TypeError('NaN');
  return args.reduce((acc, n) => acc + n, 0);
};

describe('sum()', async assert => {
  const should = 'return the correct sum';

  assert({
    given: 'no arguments',
    should: 'return 0',
    actual: sum(),
    expected: 0
  });

  assert({
    given: 'zero',
    should,
    actual: sum(2, 0),
    expected: 2
  });

  assert({
    given: 'negative numbers',
    should,
    actual: sum(1, -4),
    expected: -3
  });

  assert({
    given: 'NaN',
    should: 'throw',
    actual: Try(sum, 1, NaN).toString(),
    expected: 'TypeError: NaN'
  });  
});

Testing React Components

import React from 'react';
import render from 'riteway/render-component';
import { describe } from 'riteway';

describe('renderComponent', async assert => {
  const $ = render(<div className="foo">testing</div>);

  assert({
    given: 'some jsx',
    should: 'render markup',
    actual: $('.foo').html().trim(),
    expected: 'testing'
  });
});

Riteway makes it easier than ever to test pure React components using the riteway/render-component module. A pure component is a component which, given the same inputs, always renders the same output.

I don't recommend unit testing stateful components, or components with side-effects. Write functional tests for those, instead, because you'll need tests which describe the complete end-to-end flow, from user input, to back-end-services, and back to the UI. Those tests frequently duplicate any testing effort you would spend unit-testing stateful UI behaviors. You'd need to do a lot of mocking to properly unit test those kinds of components anyway, and that mocking may cover up problems with too much coupling in your component. See "Mocking is a Code Smell" for details.

A great alternative is to encapsulate side-effects and state management in container components, and then pass state into pure components as props. Unit test the pure components and use functional tests to ensure that the complete UX flow works in real browsers from the user's perspective.

Isolating React Unit Tests

When you unit test React components you frequently have to render your components many times. Often, you want different props for some tests.

Riteway makes it easy to isolate your tests while keeping them readable by using factory functions in conjunction with block scope.

import ClickCounter from '../click-counter/click-counter-component';

describe('ClickCounter component', async assert => {
  const createCounter = clickCount =>
    render(<ClickCounter clicks={ clickCount } />)
  ;

  {
    const count = 3;
    const $ = createCounter(count);
    assert({
      given: 'a click count',
      should: 'render the correct number of clicks.',
      actual: parseInt($('.clicks-count').html().trim(), 10),
      expected: count
    });
  }

  {
    const count = 5;
    const $ = createCounter(count);
    assert({
      given: 'a click count',
      should: 'render the correct number of clicks.',
      actual: parseInt($('.clicks-count').html().trim(), 10),
      expected: count
    });
  }
});

Output

Riteway produces standard TAP output, so it's easy to integrate with just about any test formatter and reporting tool. (TAP is a well established standard with hundreds (thousands?) of integrations).

TAP version 13
# sum()
ok 1 Given no arguments: should return 0
ok 2 Given zero: should return the correct sum
ok 3 Given negative numbers: should return the correct sum
ok 4 Given NaN: should throw

1..4
# tests 4
# pass  4

# ok

Prefer colorful output? No problem. The standard TAP output has you covered. You can run it through any TAP formatter you like:

npm install -g tap-color
npm test | tap-color

Colorized output

API

describe

describe = (unit: String, cb: TestFunction) => Void

Describe takes a prose description of the unit under test (function, module, whatever), and a callback function (cb: TestFunction). The callback function should be an async function so that the test can automatically complete when it reaches the end. Riteway assumes that all tests are asynchronous. Async functions automatically return a promise in JavaScript, so Riteway knows when to end each test.

describe.only

describe.only = (unit: String, cb: TestFunction) => Void

Like Describe, but don't run any other tests in the test suite. See test.only

describe.skip

describe.skip = (unit: String, cb: TestFunction) => Void

Skip running this test. See test.skip

TestFunction

TestFunction = assert => Promise<void>

The TestFunction is a user-defined function which takes assert() and must return a promise. If you supply an async function, it will return a promise automatically. If you don't, you'll need to explicitly return a promise.

Failure to resolve the TestFunction promise will cause an error telling you that your test exited without ending. Usually, the fix is to add async to your TestFunction signature, e.g.:

describe('sum()', async assert => {
  /* test goes here */
});

assert

assert = ({
  given = Any,
  should = '',
  actual: Any,
  expected: Any
} = {}) => Void, throws

The assert function is the function you call to make your assertions. It takes prose descriptions for given and should (which should be strings), and invokes the test harness to evaluate the pass/fail status of the test. Unless you're using a custom test harness, assertion failures will cause a test failure report and an error exit status.

Note that assert uses a deep equality check to compare the actual and expected values. Rarely, you may need another kind of check. In those cases, pass a JavaScript expression for the actual value.

createStream

createStream = ({ objectMode: Boolean }) => NodeStream

Create a stream of output, bypassing the default output stream that writes messages to console.log(). By default the stream will be a text stream of TAP output, but you can get an object stream instead by setting opts.objectMode to true.

import { describe, createStream } from 'riteway';

createStream({ objectMode: true }).on('data', function (row) {
    console.log(JSON.stringify(row))
});

describe('foo', async assert => {
  /* your tests here */
});

countKeys

Given an object, return a count of the object's own properties.

countKeys = (Object) => Number

This function can be handy when you're adding new state to an object keyed by ID, and you want to ensure that the correct number of keys were added to the object.

Render Component

First, import render from riteway/render-component:

import render from 'riteway/render-component';
render = (jsx) => CheerioObject

Take a JSX object and return a Cheerio object, a partial implementation of the jQuery core API which makes selecting from your rendered JSX markup just like selecting with jQuery or the querySelectorAll API.

Example

describe('MyComponent', async assert => {
  const $ = render(<MyComponent />);

  assert({
    given: 'no params',
    should: 'render something with the my-component class',
    actual: $('.my-component').length,
    expected: 1
  });
});

Match

First, import match from riteway/match:

import match from 'riteway/match';
match = text => pattern => String

Take some text to search and return a function which takes a pattern and returns the matched text, if found, or an empty string. The pattern can be a string or regular expression.

Example

Imagine you have a React component you need to test. The component takes some text and renders it in some div contents. You need to make sure that the passed text is getting rendered.

const MyComponent = ({text}) => <div className="contents">{text}</div>;

You can use match to create a new function that will test to see if your search text contains anything matching the pattern you passed in. Writing tests this way allows you to see clear expected and actual values, so you can expect the specific text you're expecting to find:

describe('MyComponent', async assert => {
  const text = 'Test for whatever you like!';
  const $ = render(<MyComponent text={ text }/>);
  const contains = match($('.contents').html());

  assert({
    given: 'some text to display',
    should: 'render the text.',
    actual: contains(text),
    expected: text
  });
});

Vitest

Vitest is a Vite plugin through which you can run Riteway tests. It's a great way to get started with Riteway, because it's easy to set up and it's fast.

Installing

First you will need to install Vitest. You will also need to install Riteway into your project if you have not already done so. You can use any package manager you like:

pnpm add --save-dev vitest
npm install --save-dev vitest
yarn add --save-dev vitest

Usage

First, import assert from riteway/vitest and describe from vitest:

import { assert } from 'riteway/vitest';
import { describe } from "vitest";

Then, as simple as that, you should be able to use the Vitest runner to test. You can trigger the Vitest directly or add a script to your package.json. Running npm vitest would do the trick to see a basic test setup. See here for additional details on setting up a Vitest configuration.

// a function to test
const sum = (...args) => {
  if (args.some(v => Number.isNaN(v))) throw new TypeError('NaN');
  return args.reduce((acc, n) => acc + n, 0);
};

describe('sum()', () => {
  const should = 'return the correct sum';

  assert({
    given: 'no arguments',
    should: 'return 0',
    actual: sum(),
    expected: 0
  });

  assert({
    given: 'two numbers',
    should: 'return the correct sum',
    actual: sum(2, 0),
    expected: 2
  });
});

riteway's People

Contributors

3dos avatar brunomoutinho avatar dependabot[bot] avatar ericelliott avatar geireann avatar gipphe avatar grushetsky avatar inadarei avatar janhesters avatar jwicks31 avatar kennylavender avatar lautarodragan avatar lusarz avatar maxyzli avatar puiutucutu avatar renovate-bot avatar renovate[bot] avatar serdec avatar sfiquet avatar thijsgadiot avatar tmueller avatar warrenv avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

riteway's Issues

RITEway with CRA: ReferenceError: describe is not defined

I'm trying to use RITEway with create-react-app.

I made a new project, installed RITEway via yarn add --dev and added "test": "riteway **/*.test.js | tap-color", to my package.json. (tap-color is installed globally on my mac.)

I wanted to write a header using TDD. I wrote a component that should fail all tests:

import React from 'react';

export function Header() {
  return null;
}

export default Header;

And wrote the corresponding tests for that:

import React from 'react';
import { describe } from 'riteway';
import render from 'riteway/render-component';

import Header from './';

describe('header', async assert => {
  const $ = render(<Header />);

  assert({
    given: 'just rendering',
    should: "display the string 'rifky'",
    actual: $('header')
      .html()
      .trim(),
    expected: 'rifky'
  });
});

But when I run the test via yarn test I get the following error:

/path/to/react/app/rifky/node_modules/is-color-stop/test/index.test.js:6
describe('is-color-stop', function () {
^

ReferenceError: describe is not defined
    at Object.<anonymous> (/path/to/react/app/rifky/node_modules/is-color-stop/test/index.test.js:6:1)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)
    at /path/to/react/app/rifky/node_modules/tape/bin/tape:39:9
    at Array.forEach (<anonymous>)

Am I missing some configuration? I followed the README.md 🤔

Needs to support multiple test files.

Current implementation only supports a single test file. In large projects it's typical to organize various tests in multiple file and then indicate a pattern of files in package.json. Typiclaly, you will find an entry such as:

"test" :  "test/**/*-test.js",

Riteway needs to support the same.

TestFunction must return Promise<void>

In index.d.ts, TestFunction currently has the return type Promise<void>, while the docs say the TestFunction can return either Promise or void. Should the return type instead be Promise<void> | void?

In TypeScript, this is an invalid TestFunction:

describe('foo()', (assert) => {
  assert({
    given: 'some case',
    should: 'return something',
    actual: 'value',
    expected: 'value',
  });
});

It must return a Promise<void>:

describe('foo()', (assert) => {
  assert({
    given: 'some case',
    should: 'return something',
    actual: 'value',
    expected: 'value',
  });

  return Promise.resolve();
});

or use async:

describe('foo()', async (assert) => {
  assert({
    given: 'some case',
    should: 'return something',
    actual: 'value',
    expected: 'value',
  });
});

Unwanted details logging during catching an error

Hey Eric,

recently something changed and now terminal shows errors details when you catching errors.

  assert({
    given: 'noRequestId',
    should: 'throw "Missing parameter: requestId"',
    actual: (await newTransfer().catch(e => e)).message,
    expected: 'Missing parameter: requestId'
  })

Tests are passed but the output looks messy:

Error: Missing parameter: requestId
        at newTransfer (/Users/guest/src/xApi/transfers/transfers.js:19:30)
        at fn (/Users/guest/src/xApi/utils/index.js:23:23)
        at tryCatch (/Users/guest/node_modules/regenerator-runtime/runtime.js:45:40)
        at Generator.invoke [as _invoke] (/Users/guest/node_modules/regenerator-runtime/runtime.js:271:22)
        at Generator.prototype.<computed> [as next] (/Users/guest/node_modules/regenerator-runtime/runtime.js:97:21)
        at asyncGeneratorStep (/Users/guest/node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:5:24)
        at _next (/Users/guest/node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:27:9)
        at /Users/guest/node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:34:7
        at new Promise (<anonymous>)
        at new F (/Users/guest/node_modules/core-js/library/modules/_export.js:36:28)    
        
        ✔  Given noRequestId: should throw "Missing parameter: requestId"

Is it possible to suppress that?
Thank you.

Async tests leaking into others

I believe this is the same issue reported in tape.

I have a test that needs to wait for a promise to resolve to do the assertion. As per my understanding, tests run async. That's why the next test doesn't wait for the previous to finish.

All tests pass, however the output is incorrect.

image

Is there any way to wait for a test to finish?

Issue with arrays/objects

Currently, the deep equality isn't verifying types, so arrays and objects are treated as "equal" even though they are not. Here's an example:

const { Try, describe } = require('riteway');

describe('array-object-fail',async (test) => {
  test({
    given: 'an array and an object',
    should: 'not be equal',
    expected: {},
    actual: []
  });

  test({
    given: 'another example',
    should: 'fail',
    expected: { 0: 'foo' },
    actual: ['foo'],
  });
});

Suggestion to add a basic command to work with Typescript

Hello,
thank you for the lib! Great job!

I'd be interested in trying it out, but I don't know how to make it work with TypeScript.
I'm not so familiar with babel, but I guess it can be done using different script switches in package.json.

It'd be great if you could update the docs, to include just bare-bones info on how to get started with this lib and TS.

Thank you in advance!

Intelligible stacktraces on TypeErrors?

Sometimes the stacktraces when using riteway is rather unintelligible, especially when encountering TypeErrors. Example:

not ok 1 TypeError: Cannot read property '0' of undefined
  ---
    operator: fail
    stack: |-
      Error: TypeError: Cannot read property '0' of undefined
          at Test.assert [as _assert] (<app_path>/node_modules/tape/lib/test.js:260:54)
          at Test.bound [as _assert] (<app_path>/node_modules/tape/lib/test.js:84:32)
          at Test.fail (<app_path>/node_modules/tape/lib/test.js:354:10)
          at Test.bound [as fail] (<app_path>/node_modules/tape/lib/test.js:84:32)
          at onError (<app_path>/node_modules/tape/lib/test.js:114:18)
          at processTicksAndRejections (internal/process/task_queues.js:93:5)

Is there a way to get more intelligible stacktraces?

I'd naturally like the testing framework to refer specifically to the line in the test file and the line in the file under test where the error happened.

Cannot log after tests are done.

Hi,

First of all, thank you for making this library, i was really going to go for jest-cucumber, but then I found your library and felt it was simple, complete and followed the practice of giving a reason for test cases and also the code and hence am trying to get this incorporated in my company.


My below test, throws an error::
Cannot log after tests are done. Did you forget to wait for something async in your test?

Not sure where to put the 'await' call or do I really need one?

`
import React from 'react';
import { describe } from 'riteway';
import render from 'riteway/render-component';

describe('Hello component', async assert => {

const $ = render(<div className={'greeting'} >Hello</div>);
assert({
    given: 'No Params',
    should: 'Render a greeting',
    actual: $('.greeting').length,
    expected: 1
});

});

`
Regards
Arsh

How to select test-id?

I want to migrate some of my unit tests from React Testing Library to RITEway.

I'm stuck with a particular one for a list of contacts. Each row in the list of contacts has a data-testid attribute like this const testId = `contact-${key}`;. Where key can be firstName, lastName etc.

<div data-testid={testId}>
  {contact[key]}
</div>

The tests look like this:

describe('ContactList', () => {
  describe('given a list of contacts', () => {
    const props = { contacts };

    it('displays the last name of all contacts', () => {
      const { getAllByTestId } = render(<ContactList {...props} />);

      const given = getAllByTestId('contact-lastName');
      const actual = pluck('textContent')(given);
      const expected = pluck('lastName')(contacts);
      expect(actual).toEqual(expected);
    });
  });
});

Where contacts is an array of objects (contacts). pluck is from ramda. As you can see, I already mimicked the given, actual and expected pattern of RITEway.

How can I query for test ids with RITEway? And can you query for multiple like the getAll selector?

Cannot find module 'react'

Always is react dependency necessary?

I started a project from scratch without React and Riteway show me this error

Cannot find module 'react'

How to properly assert values are NOT equal?

For now, I test values are actually different with a boolean assertion like the following:

assert({
  given: 'something',
  should: 'be different',
  actual: value !== otherValue,
  expected: true
})

But I feel this kind of assertion is sketchy and when the test fails, I get a report saying I receive false instead of true without any insight about the actual values.

Do I write this test "correctly"? Is there another way or even shouldn't I have to test for different values?

I ran across this case by testing an array shuffling function. Here I'd like to test the array has actually been shuffled.

Issue with using date format strings

I'm testing a formatting library, which formats values from Excel formats. When I'm trying to test the date component, I keep getting this error:

error fired SyntaxError: missing ) after argument list
        operator: deepEqual
        diff: "10.09.2019" => "09/Di/yyyy"

The function that I'm trying to test looks like this:

formatLocaleDate: (date, locale, format) => {
        let f = format || 'L';
        return moment(date, self.dateInputFormats, locale).format(f);
    }

My test function looks like this:

 assert({
        given: '2019-09-10T00:00:00.000-07:00',
         'return correct date for the locale.',
        actual: formatLocaleDate('2019-09-10T00:00:00.000-07:00', 'de', 'MM/dd/yyyy'),
        expected: '10.09.2019'
    });

Given:

  • self.dateInputFormats is an array of moment constants.

If I remove the format param ('MM/dd/yyyy') the test works just fine. If I have it in there, you can see that it creates some kind of weird value: "09/Di/yyyy"

Any idea how to get past this or why it's trying to do something with the format, which is just an input string? Are the some escape sequences that I need to know about?
I need to be able of pass all kinds template strings: #,##0.00, #,##0 and the like.

How to search for all test files recursively if you group files by feature?

In README.md you write that the test command should be

"test": "riteway test/**/*-test.js",

As I understand it the regex test/**/*-test.js looks for all files in test/ and it's subdirectories recursively with the ending -test.js and executes them.

Furtermore, when using RITEway with React the README.md says the test command should be:

"test": "node -r @babel/register -r @babel/polyfill source/test"

which works if you group files by type.

I read your book "Composing Software" (which is great by the way) in which you say it's good to sort your files by feature like this:

├──todos
│  ├── component
│  ├── reducer
│  └── test
└──user
   ├── component
   ├── reducer
   └── test

and I structured my folders like that. Therefore I tried to change the test command to this:

"test": "node -r @babel/register -r @babel/polyfill src/**/*.test.js | tap-color",

The problem is that this test command matches no file.

$ node -r @babel/register -r @babel/polyfill src/**/*.test.js | tap-color
internal/modules/cjs/loader.js:583
    throw err;
    ^

Error: Cannot find module '/Users/jan/dev/learning-riteway/src/**/*.test.js'

How do you write your test command if you grouped files by feature?

Edit:

I tested a bit. If I do ls src/**/*.test.js no files are found. If I do ls src/**/* only the files in folders and subfolders are found (including the ones that end on .test.js), but the src/ folders direct files (src/App.test.js) are left out. Extra weird that src/**/* finds .test.js files, but src/**/*.test.js does not.

Edit 2:

"test": "node -r @babel/register -r @babel/polyfill $(find src -type f -name '*.test.js') | tap-color",

works in that it finds all files when using ls and the test runs. But only the test of the first file matching this pattern runs.

nyc coverage shows 0%

i have been trying to use riteway, in one of my sideprojects. When trying to get it working with instanbul coverage report it shows 0% coverage.

I currently have only a single test.js file , it imports a class from another js file, and runs tests on it.

Output:


    ----------|---------|----------|---------|---------|-------------------
    File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
    ----------|---------|----------|---------|---------|-------------------
    All files |       0 |        0 |       0 |       0 |
    ----------|---------|----------|---------|---------|-------------------

  passed: 2,  failed: 0  of 2 tests  (4.1s)

package.json

   "test": "riteway -r @babel/register -r @babel/polyfill src/**/*-test.js",
    "coverage": "nyc npm run test  | tap-nirvana",

.babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": [
          "last 2 versions",
          "safari >= 7"
        ]
      }
    ],
  ],
  "plugins": ["istanbul"]
}

.nycrc

{
    "extends": "@istanbuljs/nyc-config-babel"
}

How to deal with CSS?

I need to test a display component in React which uses CSS.

It imports it like this:

import './Cart.css';

This throws the error:

(function (exports, require, module, __filename, __dirname) { .Cart {
                                                              ^

I think this has something to do with Babel or Webpack. At least Jest is solving this by mocking all modules that end with .css. How can we solve this with RITEway?

Expose tape's fail function

It would be nice to use fail in some cases for integration and functional tests.

Example:

  • without try/catch an error below would cause the node to crash resulting in none of the remaining tests to be ran.
  • Using try/catch without fail would require compromises like using let instead of const outside the try statement.
import { describe } from 'tape'
import { addEntity, getEntity } from './my-module'

describe('addEntity', async should => {
   const { assert, fail } = should()
  {
    const myEntity  = { id: 'baz', foo: 'bar' }
    try {
      const response = await addEntity(myEntity)
      const storedEntity = await getEntity(response.id)
      assert({
        given: 'an entity',
        should: 'read the same entity from the api',
        actual: storedEntity,
        expected: myEntity
      })
    } catch(error) {
      fail('failed to add and read entity', { myEntity, error })
    }
  }
})

Here is my original use case poetapp/node#429, currently just letting it crash because the try/catch was odd without fail being available.

Examples folder

So, I've been using RITEway a lot over the last two weeks and I love it, especially for TDD 👌🏻 But there where some edge cases where I wasted up to a couple of hours figuring out the selectors. I think others could save that time.

I'm wondering whether you would be open for an examples/ folder? If you are okay with it, I would include the following cases (for which I already have the code, except for the regex one):

1, Select multiple elements in a React component with the same class (useful for lists, tables).
2, Select input's vaules in a React component
3, Test whether selection is a certain tag (e.g. <button /> or <select /> etc.) in React Component.
4. Add context and dummy required props for propTypes in a React component. This would be in the createComponent factory function. E.g.:

// in the component
Counter.propTypes = {
  onClick: PropTypes.func.isRequired
}

// ... in the test
const createCounter = clickCount =>
  render(
    <ClickCounter clicks={ clickCount } onClick={() => {}} />
  )
;
  1. Regex.
  2. The reducer from your "Unit Testing React Components" article and adding tests for selectors.

I still have 3 questions about these:

  1. Do you even recommend to use propTypes? If not, why not?
  2. You said you would use regex like this: /search text/.test(renderedOutput). I couldn't figure out how you would use this. Could you give a more concrete example?
  3. Is it correct that you would test selectors like this:
const getCount = state => state.counter;

assert({
 given: 'a click count and a click action',
 should: 'add a click to the count',
 actual: getCount({ counter: reducer(3, click()) }),
 expected: 4
});

If you could answer these questions and like the idea of an examples/ folder, I will make a PR asap 😊Also would you like to see another case?

Using Try with render seems to stop execution of other tests

I have created a useStore consumer hook to access context in my TypeScript React app following the pattern recommended by Kent C. Dodds:

export const useStore = () => {
  const context = useContext(StoreContext);
  if (context === undefined) {
    throw new Error('useStore must be used within a StoreProvider');
  }
  return context;
};

I want to test that the error is thrown when context is undefined, so I've written the following test using render and Try:

import React, { ReactElement } from 'react';
import { describe, Try } from 'riteway';
import render from 'riteway/render-component';
import { useStore } from './Store.provider';

const ContextlessComponent = (): ReactElement => {
  const { state } = useStore();
  return <>{state.toString()}</>;
};

describe('Store/Store.provider/useStore()', async (assert) => {
  assert({
    given: 'useStore() is called without a StoreProvider',
    should: 'throw an error',
    actual: Try(render, <ContextlessComponent />).message,
    expected: 'useStore must be used within a StoreProvider',
  });
});

The test passes, but no further tests get run after this one.

RITEway doesn't work out of the box with Create-React-App

I'm trying to use RITEway with create-react-app and there seems to be a babel issue.

Steps to reproduce:

  1. I made a new project.
  2. I installed RITEway via yarn add --dev and added "test": "riteway **/*.test.js | tap-color", to my package.json. (tap-color is installed globally on my mac.) This replaced the former "test": "react-scripts test",.
  3. I wrote the tests:

I wanted to write a header using TDD. I wrote a component that should fail all tests:

import React from 'react';

export function Header() {
  return null;
}

export default Header;

And wrote the corresponding tests for that:

import React from 'react';
import { describe } from 'riteway';
import render from 'riteway/render-component';

import Header from './';

describe('header', async assert => {
  const $ = render(<Header />);

  assert({
    given: 'just rendering',
    should: "display the string 'rifky'",
    actual: $('header')
      .html()
      .trim(),
    expected: 'rifky'
  });
});

But when I run the test via yarn test I get the following error:

(function (exports, require, module, __filename, __dirname) { import React from 'react';
                                                                     ^^^^^

SyntaxError: Unexpected identifier
    at new Script (vm.js:79:7)
    at createScript (vm.js:251:10)
    at Object.runInThisContext (vm.js:303:10)
    at Module._compile (internal/modules/cjs/loader.js:657:28)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)

Both my own written test and the default test in App.test.js that comes with CRA is failing because of this error.

I guess this has to do with babel. But what am I missing? Why do the RITEway tests not know about React or import? If this is an issue with my setup, could we add to the README.md how to fix this for future readers?

error when using render react component functionality with latest react version

Recently came across an issue using riteway from the context of a next.js app. Below is a stack-trace showing the specifics.

npm run test

> [email protected] test
> riteway -r @babel/register 'source/index.test.js' | tap-nirvana


 renderComponent
Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
    ✖  TypeError: Cannot read properties of null (reading 'useRef')
    ----------------------------------------------------------------
        operator: deepEqual
        diff:
        source: at _callee$ (/nextjs-app/source/features/LandingPage/component.test.js:6:13)




passed: 0,  failed: 1  of 1 tests  (558ms)

I wasn't making use of hooks yet, so I could rule that out and move on to checking the versions my project had installed. This led me seeing that riteway had a react-dom version that conflicted with my project.

npm ls react-dom
[email protected] /nextjs-app
├─┬ [email protected]
│ └── [email protected] deduped
├── [email protected]
└─┬ [email protected]
  └── [email protected]

Run describe block isolated

Hello, I was wondering is there a way to isolate the describe block from each other or clean after?
My case is that first I test a register() function that attaches an object to the global variable and then I have instance () function that is trying to access that object. The goal is to test the two functions separately so that instance() should not see the object before registe() is called.
Is there are way to achieve this with RITEway?

How to run tests without npm

Hi,

I don't have npm installed on staging and prod machines. I'd like to run my tests and am trying to figure out how to do that.

I suppose I could create a file that searches my *test.js and require them, but this feels a bit heavyhanded.

Can you help me see how to run my tests in say a lib folder such as helloWorld.test.js without npm?

Thanks!

ERR_REQUIRE_ESM when running tests from `npm run test`

Steps to reproduce:

  1. Initialize a new project;
  2. Add "type": "module" to package.json;
  3. Install riteway as a dev dependency;
  4. Add "test": "riteway test/**/*-test.js" to the "scripts" key in package.json;
  5. Create a simple test file like:
import { describe } from "riteway/esm/index.js";

describe("foo", async (assert) => {
  assert({
    given: "foo",
    should: "bar",
    expected: true,
    actual: false, // make the test fail 😂
  });
});
  1. Run the test with npm run test.

After running these steps, the scripts fails and throws the ERR_REQUIRE_ESM error.

The reason for that is because bin/riteway uses require and that is not allowed when running an ESM module.

It is possible to fix this (sort of) by updating the test command to node test/foo-test.js, but it makes it impossible to use the **/* expansion (though it is possible to use *-test.js, for example).

Would it be possible to have a built version of the script which would use import statements instead of require? Is it just a matter of running a different build step?

Example of `Try` in documentation implies that `Try` tests the error when it actually just returns the error

Comment

When looking at the provided example showing how to use the Try function, it gave me the impression that somewhere down the line, the assert function will use the expected property to match the supplied error both against the error prototype and the message when this is not actually the case.

The example is taken from https://github.com/ericelliott/riteway#example-usage .

// a function to test
const sum = (...args) => {
  if (args.some(v => Number.isNaN(v))) throw new TypeError('NaN');
  return args.reduce((acc, n) => acc + n, 0);
};

[...]

assert({
  given: 'NaN',
  should: 'throw',
  actual: Try(sum, 1, NaN),
  expected: new TypeError('NaN') // in question
});

In fact, the Try fn is a safety wrapper for handling errors arising in promises and does not perform any equality checks.

const Try = (fn = noop, ...args) => {
  try {
    return catchPromise(fn(...args));
  } catch (err) {
    return err;
  }
};

Additionally, one can even use Try in any of the following ways and the tests will all still pass. This is due to the behaviour of deepEqual() from tape which itself uses the deep-equal project from https://github.com/substack/node-deep-equal. Maybe this is the intended behavior but I got confused.

assert({
  given: 'NaN',
  should: 'throw',
  actual: Try(sum, 1, NaN),
  expected: new TypeError()
});

assert({
  given: 'NaN',
  should: 'throw',
  actual: Try(sum, 1, NaN),
  expected: new TypeError
});

assert({
  given: 'NaN',
  should: 'throw',
  actual: Try(sum, 1, NaN),
  expected: new TypeError("Has a different error message")
});

Suggestion

1 - Change the error message for the TypeError

The NaN string is confusing with the given: 'NaN' used in the assert as well, so just change the error message thrown to make this clearer.

// a function to test
const sum = (...args) => {
  if (args.some(v => Number.isNaN(v))) throw new TypeError('Not a number');
  return args.reduce((acc, n) => acc + n, 0);
};

[...]

  assert({
    given: 'NaN',
    should: 'throw',
    actual: Try(sum, 1, NaN),
    expected: new TypeError('Not a number')
  });
});

2 - Show examples of how to test an Error

I would add these examples to show how to explicitly test an error's type and its message.

assert({
  given: 'NaN',
  should: 'throw a TypeError',
  actual: Try(sum, 1, NaN) instanceof TypeError,
  expected: true
});

assert({
  given: 'NaN',
  should: 'throw a TypeError with correct message',
  actual: Try(sum, 1, NaN).toString(),
  expected: 'TypeError: NaN'
});

// merged
assert({
  given: 'NaN',
  should: 'throw',
  actual: 
    Try(sum, 1, NaN) instanceof TypeError && 
    Try(sum, 1, NaN).toString() === 'TypeError: NaN',
  expected: true
});

Alternatively, one could wrap the sum fn call in a try catch and run the assert in the catch itself.

Nextjs/Babel 7.10

i had a few problems running the tests in a nextjs project.

using the @babel/polyfill module with the .babelrc config described in the README.md file, causes the next app to crash.

a solution that worked for me is describet at https://stackoverflow.com/questions/33527653/babel-6-regeneratorruntime-is-not-defined/53736090#53736090

in short, if you have the same issue do the following:

npm install --save-dev @babel/core @babel/preset-env @babel/register @babel/preset-react

npm install --save @babel/runtime 
npm install --save-dev @babel/plugin-transform-runtime

Your test script in package.json:

"test": "riteway -r @babel/register 'src/**/*-test.js' | tap-nirvana ",

and then in your .babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": [
          "last 2 versions",
          "safari >= 7"
        ]
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": [
    [
      "@babel/transform-runtime"
    ]
  ]
}

Do I need a babel runner?

This is probably a novice question. Here is my project structure:

├─ src
├── helper.js
├─ tests
├── helper.test.js
├─ package.json
├─ .babelrc

My package.json

{
  "name": "riteway",
  "version": "1.0.0",
  "main": "index.js",
  "author": "Test",
  "license": "MIT",
  "devDependencies": {
    "@babel/cli": "7.2.0",
    "@babel/core": "7.2.0",
    "babel-preset-env": "1.7.0",
    "riteway": "4.0.1"
  },
  "scripts": {
    "test": "riteway test/**/*.test.js"
  },
  "dependencies": {}
}

Here is my test file

import { describe } from 'riteway';
 
describe('sum()', async assert => {
 
  assert({
    given: 'no arguments',
    should: 'return 0',
    actual: 0,
    expected: 0
  });
});

When running npm run test I get the following error:

 riteway babel test/helpers.test.js
/Users/danielcs/Desktop/riteway/test/helpers.test.js:1
(function (exports, require, module, __filename, __dirname) { import { describe } from 'riteway';

I know for a fact that I need babel to compile the code and make use of modules. My question is how exactly do I accomplish this?

For tape there is a babel runner. Do I need to write my own runner e.g. babel-riteway-runner, or use a bundler like webpack?

Thanks

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Rate-Limited

These updates are currently rate-limited. Click on a checkbox below to force their creation now.

  • Update dependency cheerio to v1.0.0
  • Update dependency typescript to v5.5.4
  • Update dependency updtr to v4.1.0
  • Update dependency eslint to v9
  • Update dependency vitest to v2
  • 🔐 Create all rate-limited PRs at once 🔐

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

npm
package.json
  • cheerio 1.0.0-rc.12
  • esm 3.2.25
  • react-dom 18.2.0
  • tape ^5.7.4
  • vitest ^1.2.1
  • @babel/core ^7.23.9
  • @babel/plugin-transform-runtime ^7.23.9
  • @babel/preset-env ^7.23.9
  • @babel/preset-react ^7.23.3
  • @babel/register ^7.23.7
  • @babel/runtime-corejs2 ^7.23.9
  • @types/cheerio ^0.22.35
  • @types/node ^20.11.6
  • @types/react ^18.2.48
  • eslint ^8.56.0
  • eslint-plugin-react ^7.33.2
  • react 18.2.0
  • release-it ^17.0.3
  • tap-nirvana 1.1.0
  • typescript ^5.3.3
  • updtr 4.0.0
  • watch ^0.13.0
travis
.travis.yml
  • node 18

  • Check this box to trigger a request for Renovate to run again on this repository

How to run with nodemon or chokidar?

Just in case someone needs this - this will keep running riteway as changes are made to the source or test files. Couldn't get it to work with nodemon but chokidar-cli works - it uses chokidar to monitor files and run a command after any changes.

Install chokidar-cli - https://github.com/kimmobrunfeldt/chokidar-cli

npm install --save-dev chokidar-cli

Update the test script in package.json - the --command option specifies the command to run after any of the specified files change, and --initial tells chokidar to run the command once on start -

{
  "name": "ta",
  "version": "0.1.0",
  "scripts": {
    "test": "chokidar \"src/**/*.js\" --initial --command \"riteway -r esm src/**/*.test.js | tap-nirvana\""
  },
  "license": "ISC",
  "devDependencies": {
    "chokidar-cli": "^2.1.0",
    "esm": "^3.2.25",
    "riteway": "^6.1.2",
    "tap-nirvana": "^1.1.0"
  }
}

Then just

npm test

Question: testing isolation in React

First of all, I want to thank you for this great concept which enforces those best practices.

I have a question regarding testing isolation in React component.
We often have the case to test several things when the component rendered with the same props.

Given this example (review component that has the author name and content) - which test implementation would you recommend?

const createComp = () => render(<Review author="author" rating={2} review="review" />);
  {
    const $ = createComp();

    assert({
      given: 'author',
      should: 'render author name',
      actual: $(`.${styles.author}`).text(),
      expected: 'author',
    });
}
{
    const $ = createComp();
    assert({
      given: 'review text',
      should: 'render review text',
      actual: $(`.${styles.content}`).text(),
      expected: '“review”',
    });
  }
  {
    const $ = render(<Review author="author" rating={2} review="review" />);

    assert({
      given: 'author',
      should: 'render author name',
      actual: $(`.${styles.author}`).text(),
      expected: 'author',
    });

    assert({
      given: 'review text',
      should: 'render review text',
      actual: $(`.${styles.content}`).text(),
      expected: '“review”',
    });
  }

Using RITEway in an EcmaScript module environment?

I cannot get past errors in my attempts to use RITEway in an EcmaScript modules project (not CommonJS).

This demonstrates the problem:
https://github.com/StaticNoiseLog/riteway-with-esm
The line "type": "module", in package.json tells Node that these are EcmaScript modules.
The code in question is math.test.js.

First I tried the import syntax (that works for "Tape"):
import { describe } from 'riteway'
-> SyntaxError: Named export 'describe' not found.

Then the workaround suggested by Node:
import pkg from 'riteway'
const { describe } = pkg
-> TypeError: describe is not a function
I suspected that this might be because I use a style without semicolons. But adding semicolons everywhere did not help.

I am not really familiar with JavaScript and its ecosystem, but as far as I understand it should be possible to use CommonJS modules in EcmaScript modules.
I wonder why I cannot get this to work with RITEway.
Is it possible to use RITEway with EcmaScript modules without Babel or other tooling that I would like to avoid?
Would it make sense to publish RITEway as an EcmaScript module or is that not a good idea?

"Error [ERR_PACKAGE_PATH_NOT_EXPORTED]" after upgrade to Node 12.17.0

Basic installation as described in README. package.json has:

  "scripts": {
    "test": "nyc riteway -r esm test/**/*.test.js | tap-nirvana",
  },
  "devDependencies": {
    "esm": "^3.2.25",
    "nyc": "^15.1.0",
    "riteway": "^6.2.0",
    "tap-nirvana": "^1.1.0",
  }

Works perfectly in Node versions up to 12.16.3. With Node versions 12.17.0 and more recent, the following error is thrown:

> riteway -r esm test/**/*.test.js

internal/modules/cjs/loader.js:491
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(basePath, mappingKey);
^

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './bin/tape' is not defined by "exports" in /path/to/modules/node_modules/tape/package.json
at applyExports (internal/modules/cjs/loader.js:491:9)
at resolveExports (internal/modules/cjs/loader.js:507:23)
at Function.Module._findPath (internal/modules/cjs/loader.js:635:31)
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:953:27)
at Function.Module._load (internal/modules/cjs/loader.js:842:27)
at Module.require (internal/modules/cjs/loader.js:1026:19)
at require (internal/modules/cjs/helpers.js:72:18)
at Object. (/path/to/modules/node_modules/riteway/bin/riteway:3:1)
at Module._compile (internal/modules/cjs/loader.js:1138:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10) {
code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}

Riteway does not recognise absolute paths to imports

I have the following in my Webpack config:

resolve: {
  modules: [
    path.resolve(__dirname, 'src'),
    'node_modules',
    path.resolve('node_modules')
  ]
}

However, when I run tests, it doesn't seem to recognise paths pointing to directories in src.

For example, if I have

import {method_to_test} from 'utils';

I get the following error:

Error: Cannot find module 'utils/method_to_test'

To get it to work, I seem to have to use a relative path:

import {method_to_test} from '../../utils';

Absolute paths in imported modules are also not properly resolved.

UnhandledPromiseRejectionWarning instead of catching `throw new Error()`

Hey Eric,
thanks for super neat and laconic testing framework!

I have a question about testing async functions.

Code

export const foo = async (fn, event, cb) => {
  if (!cb) {
    throw new Error('No callback provided!')
  } else {
    try {
      const res = await fn()
      cb(null, res)
    } catch (e) {
      cb(e)
    }
  }
}

Test

describe('foo()', async assert => {
  assert({
    given: 'no callback',
    should: 'throw specific error',
    actual: Try(await responseFormatter(fn, event)),
    expected: Error()
  })
})

Result

foo()
    ✔  Given no function to call: should throw specific error
(node:38228) UnhandledPromiseRejectionWarning: Error: No callback provided!
    at _callee$ (/hmm/src/utils/index.js:3:11)
    at tryCatch (/hmm/node_modules/regenerator-runtime/runtime.js:45:40)
    at Generator.invoke [as _invoke] (/hmm/node_modules/regenerator-runtime/runtime.js:271:22)
    at Generator.prototype.<computed> [as next] (/hmm/node_modules/regenerator-runtime/runtime.js:97:21)
    at asyncGeneratorStep (/hmm/node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:5:24)
    at _next (/hmm/node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:27:9)
    at /hmm/node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:34:7
    at new Promise (<anonymous>)
    at new F (/hmm/node_modules/core-js/library/modules/_export.js:36:28)
    at /hmm/node_modules/@babel/runtime-corejs2/helpers/asyncToGenerator.js:23:12
(node:38228) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:38228) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.


  passed: 1,  failed: 0  of 1 tests  (949ms)

Confusion

it caught the error, but the error was thrown on await fn() call, not on throw new Error('No callback provided!')

Question

How to catch the Error that I've thrown?

Thanks!

Type argument for Assertion

Currently, Assertion's type signature is as follows:

interface Assertion {
    readonly given: any
    readonly should: string
    readonly actual: any
    readonly expected: any
}

given that actual and expected will never deep equal each other if they are of differing types[1], so the type signature of Assertion could be tightened to:

interface Assertion<T> {
    readonly given: any
    readonly should: string
    readonly actual: T
    readonly expected: T
}

Simplest case of assert and Assertion, using type arguments:

interface Assertion<T> {
  readonly given: any;
  readonly should: string;
  readonly actual: T;
  readonly expected: T;
}

function assert<T>(v: Assertion<T>): void {
  // assert all the things!
}

assert({
  given: 'some condition',
  should: 'return something',
  actual: 'foo',
  expected: 'foo',
});
// passes

assert({
  given: 'some other case',
  should: 'return something else',
  actual: 'bar',
  expected: 'quack',
});
// fails, as you'd expect

assert({
  given: 'a third case',
  should: 'probably return something, but we\'ll never know',
  actual: 'foobar',
  expected: 123,
});
// TSC error: [ts] Type 'number' is not assignable to type 'string'. [2322]
// Type errors are nice.

function fn(v: number): number | string {
  return v % 2 === 0 ? v | String(v);
}
assert({
  given: 'a string case',
  should: 'return a string',
  actual: fn(1),
  expected: '1',
});
assert({
  given: 'a number case',
  should: 'return a number',
  actual: fn(2),
  expected: 2,
});
// both are valid, since `number` and `string` can both be applied to `number | string`.

You could even go as far as to make given a T | string as well, but given is only used for describing the test in question, so that would seem somewhat unnecessary and limiting.

Through type inference, the majority of existing uses of assert will not fail.

This is a bit of a personal thought though, as I personally see strict type safety as a valuable tool.

Thoughts?


[1]: Well, technically, they can, if we're talking about type aliases or overlapping interfaces, but even TypeScript itself is aware of this, and will allow one type to be applied to another if the first type is a subset of, or identical to, the other type. Example:

interface Foo {
  foobar: string;
}

interface Bar {
  foobar: string;
}

const a: Foo = { foobar: 'a' };

function fn(val: Bar): Foo {
  return val;
}

console.log(fn(a));
//=> { foobar: 'a' }

testing click

I would like to test e.g. when i click the button, is the counter updated. Is this possible? ( I am refering to your clickcounter example)

Minor syntax error in README

"test": "riteway -r @babel/register 'src/**/*.test.js' | tap-nirvana",
I think should be:
"test": "riteway -r @babel/register src/**/*.test.js | tap-nirvana",
omitting the inside quotation marks surrounding src/**/*.test.js

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.