Power-assert with wallaby.js? #754

Closed
studds opened this Issue Aug 22, 2016 · 14 comments

Projects

None yet

4 participants

@studds
studds commented Aug 22, 2016 edited

Issue description or question

Power-assert provides descriptive assertion messages through standard assert interface. To do this, power-assert transform test code to look for calls to assert and enhance them. I'm wondering how to get this working with wallaby.js?

It's pretty simple to add power-assert to the compiler pipeline including sourcemap (an example taking the result from ts-node and passing it through power-assert: https://github.com/tracecomms/espower-ts-node). I'm not sure how ranges would need to be created in a custom compiler.

Is power-assert support something that could be added to wallaby.js?

Wallaby.js configuration file

module.exports = function () {
    return {
        files: [
            'src/**/*.ts',
            { pattern: 'src/**/*.unit.ts', ignore: true }
        ],

        tests: [
            'src/**/*.unit.ts'
        ],
        testFramework: 'mocha',
        env: {
            type: 'node'
            // More options are described here
            // http://wallabyjs.com/docs/integration/node.html
        }
    };
};

Code editor or IDE name and version

WebStorm v11
Atom v1.x

OS name and version

OSX

@ArtemGovorov
Member

For JavaScript projects + babel it just works by adding the babel-preset-power-assert preset to the .babelrc file (or directly passing to the compiler's options).

For TypeScript projects, the easiest way to use power-assert is to add a babel preprocessor to perform the transformation after TypeScript compilation (so that the compiler will compile .ts to .js and then preprocessor will add power-assert stuff to .js test files).

I have put together a sample project with your config and everything else that you may use as an example.

Note how power-assert produces a verbose error message that is helpful when displayed in the Wallaby (or any other) multiline console, but the same error message displayed inline may not be that helpful for obvious reasons.

So I have also included a small hack to make power-assert to report the original error message as well. It's optional, only required to make inline messages look better.

screen shot 2016-08-23 at 2 34 36 pm

@studds
studds commented Aug 23, 2016

Wow, @ArtemGovorov, thank you for the fast response - that's great.

Depending on a different compiler chain during dev vs build / prod seems like a suboptimal outcome. Do you think there's any chance that this will be support with typescript directly, rather than via babel?

@ArtemGovorov
Member
ArtemGovorov commented Aug 23, 2016 edited

@studds Sure, wallaby preprocessor can do anything as long as it returns an object with the transformed code and (optionally, but required for the correct stack mapping) the source map.

I have updated the sample to use espower-source in the preprocessor instead of babel. I have also taken the source map extraction function from your project.

@studds
studds commented Aug 23, 2016

@ArtemGovorov wow, amazing! Thanks so much!

@tomitrescak
tomitrescak commented Sep 5, 2016 edited

Hi, I'm still having issues with this. I had to configure my app via Babel as I am using react. The power asset seems to work, yet I'm still getting undefined in some message parts. Also, I am getting really weird inline message.

screen shot 2016-09-06 at 9 22 50 am

And a message, please note the unknown:

screen shot 2016-09-06 at 9 27 57 am

This is my config (rather large)

module.exports = function(wallaby) {
  // var load = require;

  return {
    files: [
      "src/lib/models/*.ts*",
      "src/lib/helpers/*.ts*",
      "src/client/configs/*.ts*",
      "src/client/utils/*.ts*",
      "src/client/modules/**/components/*.ts*",
      "src/client/modules/**/actions/*.ts",
      "src/client/modules/**/containers/*.ts",
      "src/client/modules/**/libs/*.ts",
      "src/client/modules/**/models/*.ts",
      //"src/client/modules/**/stories/*.ts*",
      "src/typings/**/*.d.ts",
      { pattern: 'src/**/_stories/*.ts*', ignore: true }
    ],
    tests: [
      "src/client/**/stories/*.ts*"
    ],
    compilers: {
      "**/*.ts*": wallaby.compilers.typeScript({module: "es6", target: "es6", jsx: "preserve"})
    },
    preprocessors: {
      "**/*.js*": file => require("babel-core").transform(file.content.replace('(\'assert\')', '(\'power-assert\')'), {
        sourceMap: true,
        presets: ["es2015", "stage-2", "react", "babel-preset-power-assert"],
        plugins: ["jsx-control-statements"]
      })
    },
    env: {
      type: "node"
    },
    testFramework: "mocha",
    setup: function() {

      // setup power asssert
      var Module = require('module').Module;
      if (!Module._originalRequire) {
        const modulePrototype = Module.prototype;
        Module._originalRequire = modulePrototype.require;
        modulePrototype.require = function (filePath) {
          if (filePath === 'empower-core') {
            var originalEmpowerCore = Module._originalRequire.call(this, filePath);
            var newEmpowerCore = function () {
              var originalOnError = arguments[1].onError;
              arguments[1].onError = function (errorEvent) {
                errorEvent.originalMessage = errorEvent.error.message + '\n';
                return originalOnError.apply(this, arguments);
              };
              return originalEmpowerCore.apply(this, arguments);
            };
            newEmpowerCore.defaultOptions = originalEmpowerCore.defaultOptions;
            return newEmpowerCore;
          }
          return Module._originalRequire.call(this, filePath);
        };
      }

      // facade for stories
      var mocha = require('mocha');

      var chai = require('chai');
      var chaiEnzyme = require('chai-enzyme');

      chai.use(chaiEnzyme());

      var storiesOf = function storiesOf() {
        console.log('executing ...')
        var api = {};
        api.addDecorator = () => {
          return api;
        };
        api.add = (name, func)=> { 
          func();
          return api; 
        };
        api.addWithInfo = (name, description, func)=> { 
          func();
          return api; 
        };
        return api;
      };
      var action = () => {};

      var linkTo = () => {};

      var specs = (spec) => {
        spec();
      };

      // handle jquery
      var jquery = require('jquery');
      global.$ = jquery;
      global.jQuery = jquery;

      global.storiesOf = storiesOf;
      global.action = action;
      global.linkTo = linkTo;
      global.specs = specs;
      global.describe = mocha.describe;
      global.it = mocha.it;

      global.React = require("react");

      // Taken from https://github.com/airbnb/enzyme/blob/master/docs/guides/jsdom.md
      var jsdom = require('jsdom').jsdom;

      var exposedProperties = ['window', 'navigator', 'document'];

      global.document = jsdom('');
      global.window = document.defaultView;
      Object.keys(document.defaultView).forEach((property) => {
        if (typeof global[property] === 'undefined') {
          exposedProperties.push(property);
          global[property] = document.defaultView[property];
        }
      });

      global.navigator = {
        userAgent: 'node.js'
      };
    }
  };
};

I'm not sure if I have all the dependencies, this is package.json

{
  "name": "nwb-react-tutorial",
  "version": "1.0.0",
  "description": "An implementation of the React tutorial using nwb middleware",
  "private": true,
  "scripts": {
    "forever": "./node_modules/.bin/forever -a -l /app/logs/f.log -f start --pidFile /app/logs/pids.log server.js",
    "compile": "tsc -p app/server",
    "build": "nwb build-react-app",
    "clean": "nwb clean-app",
    "start": "./node_modules/.bin/forever -a -l /app/logs/f.log -f --pidFile /app/logs/pids.log server.js",
    "dev": "EXPRESS_PORT=3000 nodemon --watch src/server server.js",
    "startClient": "nwb serve-react-app --reload",
    "test": "nwb test",
    "test:coverage": "nwb test --coverage",
    "test:watch": "nwb test --server",
    "storybook": "start-storybook -p 9001 -s ./public"
  },
  "dependencies": {
    "apollo-module-date": "^1.0.1",
    "apollo-modules": ">0.0.1",
    "apollo-server": "^0.2.4",
    "bcrypt-nodejs": "0.0.3",
    "body-parser": "^1.15.2",
    "connect-history-api-fallback": "^1.3.0",
    "cors": "^2.8.0",
    "dataloader": "^1.2.0",
    "express": "^4.14.0",
    "forever": "^0.15.2",
    "graphql": "^0.6.2",
    "graphql-tools": "^0.6.4",
    "json-loader": "^0.5.4",
    "jsonwebtoken": "^7.1.9",
    "meteor-random": "0.0.3",
    "meteor-sha256": "^1.0.0",
    "mongodb": "^2.2.5",
    "morgan": "^1.7.0",
    "nodemailer": "^2.5.0",
    "remove": "^0.1.5",
    "typescript": "rc"
  },
  "devDependencies": {
    "@kadira/react-storybook-addon-info": "^3.2.1",
    "@kadira/storybook": "^2.11.0",
    "apollo-authentication-semantic-ui": ">=0.0.2",
    "apollo-client": "^0.4.11",
    "apollo-mantra": "^1.1.0",
    "babel-preset-power-assert": "^1.0.0",
    "babel-preset-stage-2": "^6.13.0",
    "bcrypt-nodejs": "0.0.3",
    "chai": "^3.5.0",
    "chai-enzyme": "^0.5.1",
    "date-format-lite": "^0.9.1",
    "enzyme": "^2.4.1",
    "graphql-tag": "^0.1.11",
    "i18n-client": "0.0.6",
    "jquery": "^3.1.0",
    "jss": "^5.4.0",
    "jss-nested": "^2.1.0",
    "jss-vendor-prefixer": "^3.0.0",
    "jsx-control-statements": "^3.1.2",
    "local-storage": "^1.4.2",
    "marked": "^0.3.6",
    "meteor-random": "0.0.3",
    "mocha": "^3.0.2",
    "moment": "^2.14.1",
    "morgan": "^1.7.0",
    "nwb": "0.12.x",
    "power-assert": "^1.4.1",
    "raw-loader": "^0.5.1",
    "react": "^15.3.1",
    "react-addons-test-utils": "^15.3.1",
    "react-addons-update": "^15.3.0",
    "react-apollo": "^0.4.7",
    "react-dom": "^15.3.0",
    "react-functional": "^2.0.0",
    "react-helmet": "^3.1.0",
    "react-hot-loader": "^3.0.0-beta.2",
    "react-redux": "^4.4.5",
    "react-router": "^2.6.1",
    "react-router-redux": "^4.0.5",
    "react-s-alert": "^1.1.4",
    "redux-form": "^6.0.1",
    "redux-form-semantic-ui": "../../packages/redux-form-semantic-ui",
    "redux-thunk": "^2.1.0",
    "semantic-ui-css": "^2.2.4",
    "semanticui-react": "^0.1.53",
    "storybook-addon-specifications": "^1.0.15",
    "style-loader": "^0.13.1",
    "sweetalert2": "^4.1.9"
  },
  "author": "Tomi Trescak <tomi.trescak@gmail.com>",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": ""
  }
}
@ArtemGovorov
Member

@tomitrescak Could you please share a small sample project where I could reproduce the issue?

@tomitrescak

I have disected your setup function and the problem is that the onError function never gets executed :/

// setup power asssert
      var Module = require('module').Module;
      if (!Module._originalRequire) {
        const modulePrototype = Module.prototype;
        Module._originalRequire = modulePrototype.require;
        modulePrototype.require = function (filePath) {
          if (filePath === 'empower-core') {
            console.log('GET EXECUTED!');
            var originalEmpowerCore = Module._originalRequire.call(this, filePath);
            var newEmpowerCore = function () {
              var originalOnError = arguments[1].onError;
              arguments[1].onError = function (errorEvent) {
                console.log('NEVER GETS EXECUTED!: ' + errorEvent.error.message);
                errorEvent.originalMessage = errorEvent.error.message + '\n';
                return originalOnError.apply(this, arguments);
              };
              console.log('GET EXECUTED');
              return originalEmpowerCore.apply(this, arguments);
            };
            newEmpowerCore.defaultOptions = originalEmpowerCore.defaultOptions;
            return newEmpowerCore;
          }
          return Module._originalRequire.call(this, filePath);
        };
      } 
@tomitrescak

It actually does get executed and I see correct message in my console, only inline is showing this long message. But maybe it's by design ?

screen shot 2016-09-06 at 9 56 13 am

@ArtemGovorov
Member
ArtemGovorov commented Sep 6, 2016 edited

It actually does get executed and I see correct message in my console, only inline is showing this long message.

Wallaby has always been showing whatever error message an assertion library would provide. So it is expected. However, with the setup function I tried to hack power-assert a bit to just display a better inline message starting with some meaningful info (because it's obviously not possible to display the full nice multiline message inline). I can't yet reproduce it, will have a look into why it doesn't work (as it was hack, perhaps it was broken by their internal changes).

@tomitrescak

Don't worry about it. I understand now what you tried to do and it works as expected. Thanks!

@ArtemGovorov
Member

@tomitrescak Oh, cool. So did you manage to make the shorter/nicer inline messages work?

@ArtemGovorov
Member

Sorry, they are not actually shorter, but are starting with the meaningful part :)

@geekflyer
geekflyer commented Oct 9, 2016 edited

@ArtemGovorov Is there a way to get wallaby + typescript + ava + power-assert working?

The wallaby blog post only mentions the required setup when using babel. I tried out different variations including your example above when using ava (instead of mocha) but I can't see any power assert messages.

@ArtemGovorov
Member
ArtemGovorov commented Oct 10, 2016 edited

@geekflyer When ava module is installed, it also installs all required babel modules as its dependencies, so you may just use it like described in the blog post:

module.exports = function (wallaby) {
  return {

    ...

    testFramework: 'ava',

    preprocessors: {
      '**/*.js': wallaby.compilers.babel({
        plugins: [
          require('babel-plugin-espower/create')(
            require('babel-core'), {
              embedAst: true,
              patterns:require('ava/lib/enhance-assert').PATTERNS
            })
        ]
      })
    }
  };
};

Note that I have added the embedAst: true (in the blog post as well). It's a new option required for power-assert to work correctly with ava.

I have also published the new version of wallaby.js core 1.0.302 that embeds the setup function for better inline messages for ava runner. So all you need is the config above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment