Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Power-assert with wallaby.js? #754

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

Power-assert with wallaby.js? #754

studds opened this issue Aug 22, 2016 · 14 comments

Comments

@studds
Copy link

studds commented Aug 22, 2016

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
Copy link
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
Copy link
Author

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
Copy link
Member

ArtemGovorov commented Aug 23, 2016

@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
Copy link
Author

studds commented Aug 23, 2016

@ArtemGovorov wow, amazing! Thanks so much!

@tomitrescak
Copy link

tomitrescak commented Sep 5, 2016

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
Copy link
Member

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

@tomitrescak
Copy link

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
Copy link

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
Copy link
Member

ArtemGovorov commented Sep 6, 2016

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
Copy link

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

@ArtemGovorov
Copy link
Member

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

@ArtemGovorov
Copy link
Member

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

@geekflyer
Copy link

geekflyer commented Oct 9, 2016

@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
Copy link
Member

ArtemGovorov commented Oct 10, 2016

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants