GithubHelp home page GithubHelp logo

benv's Introduction

benv

Stub a browser environment and test your client-side code in node.js.

Example

Example using mocha and should.

Given some client-side code

./client/app.js

$(function() {
  $('body').html('Wat!?');
});

Setup, declare global dependencies, and test in node.js.

./test/client/app.js

var benv = require('benv');

beforeEach(function(done) {
  benv.setup(function() {
    benv.expose({
      $: benv.require('../client/vendor/zepto.js', 'Zepto')
    });
    done();
  });
});

afterEach(function() {
  benv.teardown();
});

describe('app.js', function() {
  it('renders Wat', function() {
    require('../client/app.js');
    $('body').html().should.include('Wat!?');
  });
});

Why

Unit testing client side code in a browser is slow and hard to setup with CI. Wouldn't it be nice if we could just run it along-side our server-side tests? Benv is a library of test helpers that make it easy to require your client-side code in node.js and test it like any other node module.

See this blog post for details & inspiration.

API

benv.setup(callback, options)

Exposes a stubbed browser API into the node.js global namespace so the current process can act like a browser environment. The optional options are passed to jsdom.env. In particular, if your code makes ajax requests then you must pass {url:'http://hostname:port'} to avoid CORS errors.

benv.expose(globals)

Pass in a hash of common global client-side dependencies. For instance you may have a Backbone app that has a global App namespace and uses jQuery. This should be run after benv.setup b/c a lot of libraries assume the window object is already global.

benv.expose({
  _: require('underscore'),
  jQuery: require('jquery'),
  $: require('jquery'),
  Backbone: require('backbone'),
  App: {}
})
```

### benv.teardown(clearDOM = false)

Clean up the globals exposed by `setup` and `expose` so other tests can run without being harmed.

Use `benv.teardown(true)` to remove references to `window`, `document`, and other DOM globals. This isn't enabled by default because a lot of libraries cache references to DOM globals and don't work so nicely when trying to clear globals and re-require these libs.

### benv.require(filename, globalVarName)

For non-commonjs wrapped libraries, benv.require will export the global variable that is generally attached to window. For instance [zepto](https://github.com/madrobby/zepto) doesn't adopt any module pattern but it does create a global `Zepto` variable.

e.g.

````javascript
var $ = benv.require('./client/vendor/zepto.js', 'Zepto');

benv.render(filename, data, callback)

Renders the html in a body tag of a template. Pass in the template's filename along with any data passed into the template. Benv is backed by jsdom and benv.render will remove any script tags so as to not accidentally run external javascript.

e.g.

benv.render('./views/artwork.jade', {
  artwork: new Artwork({ title: 'Andy Warhol, Flowers' })
}, function() {
  $('body').html().should.include('Andy Warhol, Flowers');
});

Currently only supports .jade and .pug templates, but please contribute others :)

benv.requireWithJadeify(filename, varNames)

For those using jadeify when requiring client-side code that uses jadeify it will throw an error because require('template.jade') isn't valid node code.

If you defer your jade requires to run time e.g. var artworkTemplate = function() { return require('foo.jade').apply(this, arguments); } and use benv.requireWithJadeify('../client/artwork.js', ['artworkTemplate']) you can avoid this error and test the jadeified templates in node again.

benv.requireWithPugify(filename, varNames)

For those using pugify when requiring client-side code that uses pugify it will throw an error because require('template.pug') isn't valid node code.

If you defer your pug requires to run time e.g. var artworkTemplate = function() { return require('foo.pug').apply(this, arguments); } and use benv.requireWithPugify('../client/artwork.js', ['artworkTemplate']) you can avoid this error and test the pugified templates in node again.

Contributing

Please fork the project and submit a pull request with tests. Install node modules npm install and run tests with npm test.

License

MIT

benv's People

Contributors

alechenninger avatar aulisius avatar craigspaeth avatar kanaabe avatar rwaldin avatar sweir27 avatar vbraun 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

Watchers

 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

benv's Issues

Need to set URL as JSdom started to check CORS

I just updated to the latest benv and all XMLHttpRequests error out because jsdom now checks CORS, but benv doesn't supply the URL to jsdom.env. Since the origin then mismatches everything is a CORS violation. The fix is to supply jsdom with the URL to check against:

module.exports.setup = function(callback) {
  if (typeof window != 'undefined') return callback && callback();
  jsdom.env({
    url: 'http://localhost:8080',       // <-------- add URL here
    html: "<html><body></body></html>",
    done: function(errs, w) {
      global.window = w;
      domGlobals.forEach(function(varName) {
        global[varName] = w[varName] || function(){};
      });
      if (callback) callback();
    }
  })
}

Seems that we need a way to supply additional parameters to jsdom.env in the setup() call. Optional second argument perhaps?

jQuery still holds the previous window object

Strange issue which I tried to fork and resolve. Benv.teardown(false) cleans up the dom when using jQuery. I think this is because jQuery holds a reference to the window object when requiring it (preferredDoc = window.document, which is a self invoking function which only gets called once inside jQuery's source).

So I think when you delete the window document in the teardown function, jQuery still holds reference to the old document.

You can prove this by appending results to the dom, calling teardown, then in a new test console.log(document.getElementsByTagName('body')[0].innerHTML, $('body').html());. You will see that they retrieve different results. jQuery still shows the elements appended to the dom previously.

Cannot install benv on node 4.2 (jsdom update required)

Hello,

It seems jsdom 3.x cannot be installed on node 4.x anymore due to contextify dependency which require new nan.

npm install [email protected] 

> [email protected] install /tmp/.node_mod_tmp/42/[email protected]/node_modules/benv/node_modules/jsdom/node_modules/contextify
> node-gyp rebuild

make: Entering directory `/tmp/.node_mod_tmp/42/[email protected]/node_modules/benv/node_modules/jsdom/node_modules/contextify/build'
  CXX(target) Release/obj.target/contextify/src/contextify.o
In file included from ../src/contextify.cc:3:0:
../node_modules/nan/nan.h:261:25: error: redefinition of ‘template<class T> v8::Local<T> _NanEnsureLocal(v8::Local<T>)’
 NAN_INLINE v8::Local<T> _NanEnsureLocal(v8::Local<T> val) {
                         ^
../node_modules/nan/nan.h:256:25: error: ‘template<class T> v8::Local<T> _NanEnsureLocal(v8::Handle<T>)’ previously declared here
 NAN_INLINE v8::Local<T> _NanEnsureLocal(v8::Handle<T> val) {
                         ^
../node_modules/nan/nan.h:661:13: error: ‘node::smalloc’ has not been declared
     , node::smalloc::FreeCallback callback
             ^
../node_modules/nan/nan.h:661:35: error: expected ‘,’ or ‘...’ before ‘callback’
     , node::smalloc::FreeCallback callback
                                   ^
../node_modules/nan/nan.h: In function ‘v8::Local<v8::Object> NanNewBufferHandle(char*, size_t, int)’:
../node_modules/nan/nan.h:665:50: error: ‘callback’ was not declared in this scope
         v8::Isolate::GetCurrent(), data, length, callback, hint);
                                                  ^
../node_modules/nan/nan.h:665:60: error: ‘hint’ was not declared in this scope
         v8::Isolate::GetCurrent(), data, length, callback, hint);
                                                            ^
../node_modules/nan/nan.h: In function ‘v8::Local<v8::Object> NanNewBufferHandle(const char*, uint32_t)’:
../node_modules/nan/nan.h:672:67: error: call of overloaded ‘New(v8::Isolate*, const char*&, uint32_t&)’ is ambiguous
     return node::Buffer::New(v8::Isolate::GetCurrent(), data, size);
                                                                   ^
../node_modules/nan/nan.h:672:67: note: candidates are:
In file included from ../node_modules/nan/nan.h:25:0,
                 from ../src/contextify.cc:3:
/home/anthony/.node-gyp/4.2.0/include/node/node_buffer.h:31:40: note: v8::MaybeLocal<v8::Object> node::Buffer::New(v8::Isolate*, v8::Local<v8::String>, node::encoding) <near match>
 NODE_EXTERN v8::MaybeLocal<v8::Object> New(v8::Isolate* isolate,
                                        ^
/home/anthony/.node-gyp/4.2.0/include/node/node_buffer.h:31:40: note:   no known conversion for argument 3 from ‘uint32_t {aka unsigned int}’ to ‘node::encoding’
/home/anthony/.node-gyp/4.2.0/include/node/node_buffer.h:43:40: note: v8::MaybeLocal<v8::Object> node::Buffer::New(v8::Isolate*, char*, size_t) <near match>
 NODE_EXTERN v8::MaybeLocal<v8::Object> New(v8::Isolate* isolate,
                                        ^
/home/anthony/.node-gyp/4.2.0/include/node/node_buffer.h:43:40: note:   no known conversion for argument 2 from ‘const char*’ to ‘char*’
In file included from ../src/contextify.cc:3:0:
../node_modules/nan/nan.h: In function ‘v8::Local<v8::Object> NanNewBufferHandle(uint32_t)’:
../node_modules/nan/nan.h:676:61: error: could not convert ‘node::Buffer::New(v8::Isolate::GetCurrent(), ((size_t)size))’ from ‘v8::MaybeLocal<v8::Object>’ to ‘v8::Local<v8::Object>’
     return node::Buffer::New(v8::Isolate::GetCurrent(), size);
                                                             ^
../node_modules/nan/nan.h: In function ‘v8::Local<v8::Object> NanBufferUse(char*, uint32_t)’:
../node_modules/nan/nan.h:683:12: error: ‘Use’ is not a member of ‘node::Buffer’
     return node::Buffer::Use(v8::Isolate::GetCurrent(), data, size);
            ^
make: *** [Release/obj.target/contextify/src/contextify.o] Error 1
make: Leaving directory `/tmp/.node_mod_tmp/42/[email protected]/node_modules/benv/node_modules/jsdom/node_modules/contextify/build'
gyp ERR! build error 
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/var/lib/nave/global/4.2.0/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:270:23)
gyp ERR! stack     at emitTwo (events.js:87:13)
gyp ERR! stack     at ChildProcess.emit (events.js:172:7)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:200:12)
gyp ERR! System Linux 3.13.0-43-generic
gyp ERR! command "/var/lib/nave/global/4.2.0/bin/node" "/var/lib/nave/global/4.2.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /tmp/.node_mod_tmp/42/[email protected]/node_modules/benv/node_modules/jsdom/node_modules/contextify
gyp ERR! node -v v4.2.0
gyp ERR! node-gyp -v v3.0.3
gyp ERR! not ok 
npm ERR! Linux 3.13.0-43-generic
npm ERR! argv "/var/lib/nave/global/4.2.0/bin/node" "/var/lib/nave/global/4.2.0/bin/npm" "install" "[email protected]"
npm ERR! node v4.2.0
npm ERR! npm  v2.14.7
npm ERR! code ELIFECYCLE

npm ERR! [email protected] install: `node-gyp rebuild`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] install script 'node-gyp rebuild'.
npm ERR! This is most likely a problem with the contextify package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     node-gyp rebuild
npm ERR! You can get their info via:
npm ERR!     npm owner ls contextify
npm ERR! There is likely additional logging output above.

npm ERR! Please include the following file with any support request:
npm ERR!     /tmp/.node_mod_tmp/42/[email protected]/npm-debug.log

Node not defined

As in: https://developer.mozilla.org/en-US/docs/Web/API/Node

I found this while running AngularJS inside benv. At (this line)[https://github.com/angular/angular.js/blob/1537651c8c5f26f1c75576b881e04a3cf818447f/src/jqLite.js#L259], an error is thrown due to Node not being defined.

This is with version 2.1.0 of benv. Running tests on older versions of node so I can't upgrade just yet.

requireWithJadeify might not need to specify variables

We might be able to just use benv.require and determine what we need to set . Through parsing the file and determining the variables that require .jade files. I wonder if there's a way to leverage the browserify API to do this.

Libraries will cache references to window or document

This will cause setup/teardown issues b/c we create a new DOM but Zepto caches the reference to the old window and now $('body') referrers to something different than we expct. Would be nice if benv.require would be able to work around this, or that benv setup only ever uses one jsdom instance.

Bubble up jade errors better

Right now if you, say pass in an incomplete stub, jade might choke and it just shows a stack of anonymous function calls stopping at benv.render.

Consider "global locals" helper

It's often the case that many tests depend on the same global locals being set via something like app.locals.foo = 'bar'. It can be a PITA to have to set these across various tests every time.

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.