wallabyjs / public

Repository for Wallaby.js questions and issues
http://wallabyjs.com
757 stars 45 forks source link

Power-assert with wallaby.js? #754

Closed studds closed 8 years ago

studds commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

@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 commented 8 years ago

@ArtemGovorov wow, amazing! Thanks so much!

tomitrescak commented 8 years ago

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 commented 8 years ago

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

tomitrescak commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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

ArtemGovorov commented 8 years ago

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

ArtemGovorov commented 8 years ago

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

geekflyer commented 7 years ago

@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 commented 7 years ago

@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',

    compilers: {
      '**/*.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.