ScottHamper/Cookies

using the latest jsDom returns undefined for Cookies definition

kwhitaker opened this issue · 6 comments

It looks like the later versions of jsDom aren't playing nicely with Cookies. Per your example, I've tried the following:

import {jsdom} from 'jsdom'

let Cookies
if (process.env.NODE_ENV === 'test') {
  Cookies = require('cookies-js')(jsdom().defaultView)
} else {
  Cookies = require('cookies-js')
}
export default Cookies

Cookies.method() work as expected in production and development, but Cookies is undefined when I run my unit tests. Is there something I'm missing?

Thanks

Hey Kevin,

Can you see if Cookies.js throws an error when executing:

require('cookies-js')(jsdom().defaultView)

I have a feeling the issue is related to what the value of jsdom().defaultView is. If it does not have a document object property, Cookies.js will throw the following error:

"Cookies.js requires a window with a document object"

This will result in your Cookies variable being undefined. Outside of exceptions being thrown, or require suddenly not working, Cookies should never be undefined.

@ScottHamper It doesn't throw an error, and this is what console.log(jsdom().defaultValue) returns: Document { location: [Getter/Setter] }.

I added a logging statement to cookies.js to verify, and it returns the same thing.

Ah... it looks like I found the issue. I'm using http://airbnb.io/enzyme/docs/guides/jsdom.html, and this code:

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'
};

seems to play hell with what cookies expects. Commenting it out solves the problem with cookies, but opens up other issues. Off-hand, can you think of a modification I could make to satisfy both?

So it looks like since your global object already has a window property (from using jsdom), Cookies.js ends up automatically executing its factory function using that window property, and then exports the result.

Basically, Cookies.js checks if there's a window property on the global object. If there isn't, it exports a factory function that will accept a window object and return the Cookies API bound to that window. If there already is a window property on the global object, Cookies.js won't export the factory function - it will auto-execute it using the global window, and then export the Cookies API directly.

This export behavior is inconsistent depending on the environment Cookies.js is run in, which I dislike, but it was a concession so that using Cookies.js in the browser didn't require the developer to explicitly pass in the window object (the goal was to reduce boilerplate code in the default/majority scenario of using the library in the browser).

In other words, looking at the code in your original post, try removing the conditional and always execute the following, regardless of environment:

Cookies = require('cookies-js')

Since you're running the copy/pasted code in your post above to make the node environment global object directly emulate the browser global, there's no need to do anything special/conditionally for Cookies.js to work. Just use it as if it were still running in a browser.

If I ever get around to rewriting the library (to update to ES6 or TypeScript), I will likely make the export behavior consistent regardless of environment.

That did it. Thanks so much.

Great! Glad you got it working.