10

I have a very simple React mixin which uses jQuery to trigger an event

MyMixin = {
  trackStructEvent: function () {
    args = Array.prototype.slice.call(arguments);
    $('body').trigger('myEvent', args);
  }
module.exports = MyMixin

This is imported into the main site as part of a new set of components using browserify. As the main site holding these components will always include jQuery, I don't want to require jQuery with browserify, as it will be duplicated.

This isn't an issue in terms of behaviour - however it causes problems when running jest to unit test the components using this mixin throwing the error.

ReferenceError: $ is not defined

I know I can fix this by including jQuery through browserify, but that will load 2 copies into my site.

Is there any way in jest to tell my react component that jQuery already exists on the window and not to worry about it?

Sam Ternent
  • 281
  • 1
  • 4
  • 14
  • Did you try `var $ = window.jQuery`? – David Hellsing Nov 28 '14 at 13:19
  • 1
    Another way is to use the `external` option in browserify – David Hellsing Nov 28 '14 at 13:22
  • @David - using `external` seemed to work ok in my component, but I couldn't get it to play nice with jest. For now I have added a check that `$` is defined before calling it. I won't put it as an answer as it's not a real solution to the problem. It just let me safely pass my jests – Sam Ternent Nov 28 '14 at 16:33
  • I would also be interested in an answer for this question, for me it's React itself which is loaded already. maybe global.React = require('react/addons') at the top of your tests will do... – Mischa Molhoek Feb 11 '15 at 09:57
  • 1
    or in your case global.$ = require('jquery') :) – Mischa Molhoek Feb 11 '15 at 10:03
  • @Mischa - as it's within the module defining globally still doesn't let JEST know it's available in all modules. The issue here also is that would load in an additional copy of jQuery into the build file, as well as the one loaded in the script tag. I think Davids solution to use the `external` option in browserify would be the correct way to go about it. However we're looking to replace jQuery with some smaller modules and polyfils so haven't invested too much time here to get it working – Sam Ternent Feb 11 '15 at 10:48

3 Answers3

7

I have a similar problem but I think where i've got to so far solves your problem. You need to use require inside your react files to define jQuery under $

var $ = require('jquery');

MyMixin = {
   trackStructEvent: function () {
      args = Array.prototype.slice.call(arguments);
      $('body').trigger('myEvent', args);
   }
module.exports = MyMixin

You then need to tell jest not to mock jquery

jest.dontMock('jquery')

Then your jest unit tests should pass (assuming they aren't verifying things that jQuery is doing - this is where my tests are falling over).

You also need to tell browserify that jQuery is external then the result will be that all your references to $ have been defined, the jquery library is loaded for testing but is not included in your browserify bundle. (use browserify-shim)

Community
  • 1
  • 1
brommersman
  • 116
  • 1
  • 4
  • 2
    We've ended up removing jQuery from our site now we have a full react set up, so no need for DOM manipulation. This does look like it would solve the problems we were having though, and as it solved your issue it makes sense to be the accepted answer. thanks! – Sam Ternent Jul 01 '15 at 14:20
2

What I'm going with is:

if (process.env.NODE_ENV === 'test') window.$ = require('jquery');

I hope this helps someone!

UPDATE:

I've begun to import $ from 'jquery' in all React files that use jQuery. This removes the dependency on window.$ being the type of jQuery I expect (I have run into problems with other libraries messing with window.$. At first, I was concerned about importing jQuery everywhere, because I was afraid that this would increase my bundle size. But the way bundling works, all these imports will point to a singleton, so bundle size would not increase.

Zachary Ryan Smith
  • 2,688
  • 1
  • 20
  • 30
2

For my part, I am not interested in testing the jQuery functionality in my components. In addition, I have not included jQuery in my bundle because it is used and included all over the website my app is on. So I have, for example, done the following:

// SubmitRender.js

// ...import statements...

var SubmitRender = React.createClass({
  componentWillMount: function () {
    $('form').on('submit', (e)=>{
      this.handleSubmit(e);
    });
  },

  // ...more code...
});
export default SubmitRender;

.

// SubmitRender.spec.js

// ...import statements...

describe('SubmitRender', () => {
  beforeAll(() => {
    // mocking jQuery $()
    global.window.$ = jest.fn(() => {return {on: jest.fn()}});
  });

  it('renders without error', () => {
    expect( () => render(<SubmitRender />) ).not.toThrow();
  });
});

I know my component will not mount without calling the $ function. So I just mock out that function with jest.fn in a beforeAll(). Now, I also know that my jQuery function inside my actual component is chained. So I know that I also need to account for jQuery's .on(), so I make sure to return an object with an on function. There is no further chaining, so that is where I can stop.

If I had a more complex set of chained functions, I could do something like this:

beforeAll(() => {
  // mocking jQuery $()
  var fakeQuery = jest.fn(() => {
    return {
      on: fakeQuery,
      off: fakeQuery,
      click: fakeQuery
    }
  })
  global.window.$ = fakeQuery;
});
Don
  • 746
  • 6
  • 18