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
localstorage doesn't work within jsdom #1137
Comments
These are unfortunately quite hard without ES2015 proxies :(. We could get getItem/setItem/etc. working, but having property modifications apply correctly is not really possible. |
It would be nice to have at least an in-memory experience i.e. localStorage having behavior similar to sessionStorage |
No, because we can't emulate the API well enough. For simple use-cases (i.e. only using getItem/setItem) it might work, but as soon as properties are accessed directly, our implementation will break down. |
@Sebmaster: I wrote a simple shim for the storage interface for use in my unit tests for a separate package. https://github.com/mnahkies/node-storage-shim It's currently a transient solution, and forbids the setting of keys that clash with the method names of the storage interface. It also doesn't currently implement the event firing portions of the spec. Real localStorage does allow the setting of these keys using setItem, but using property access does blow away the function definitions, and once set you can't access those keys using property access either. However aside from these limitations I think it's a pretty faithful implementation: Could you elaborate on what aspects of the API can't be emulated well enough? As far as I can tell the main thing that isn't currently possible to emulate is the storage event firing in response to property setting. I would be happy to have a go at integrating it with jsdom if you thought it was complete enough with the mentioned caveat's. |
localStorage supports setting any property name directly on the interface as in localStorage.myKey = "myVal";
localStorage.getItem('myKey') // 'myVal'
localStorage.setItem('otherKey', 'val')
localStorage.otherKey // 'val' which we could emulate somewhat by always creating a getter if setItem is called, but we'll not be able to support the other way around (setting arbitary properties on the object) without proxies. |
Right, I guess the main issue with regards to setting keys by property access is that we can't coerce the values to strings correctly, as the real interface does. |
Yes that is a very important aspect of local storage. We need ES6 Proxy for this, v8 has not implemented it yet (microsoft and mozilla already have it!) We are hitting this same road block in multiple locations in jsdom |
I don't understand why this wouldn't work:
What am I missing? |
When you set items using property access, the real localStorage always coerces the value to a string. Eg: localStorage.foo = 35
assert(typeof localStorage.foo === "string")
localStorage.foo = {my: 'object'}
assert(localStorage.foo === "[object Object]") This is not possible without ES6 proxy, and is an important charactoristic of localStorage |
I see. Though if you're doing that shame on you ;) |
I think more importantly if you are doing that with objects it is probably a bug that would be masked by jsdom and then occur in the browser |
+1 |
1 similar comment
+1 |
perhaps something like this would work https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage ? |
I think you'll find that you are still missing some of the behavior that the real local storage has as explained above. The linked solution makes an good polyfill but I don't think it would be a faithful enough implementation for jsdom |
@mnahkies Honestly, the polyfill is better than nothing (which is what you have now). You should just put it in and add some documentation on the limitations which 99% of people will never hit anyway. |
+1 to the last comment, example on MDN uses For now, I solve it with |
@justinmchase 's solution is great for testing. Might want to add sessionStorage too. var jsdom = require('jsdom').jsdom;
document = jsdom('hello world');
window = document.defaultView;
navigator = window.navigator;
window.localStorage = window.sessionStorage = {
getItem: function (key) {
return this[key];
},
setItem: function (key, value) {
this[key] = value;
}
}; FAIK this mocks well. Fixes some of my tests. Thanks. |
It fixs your tests until someone accidentally writes an object to local storage using property access and creates a subtle bug. In my opinion you'd be better off having an object that abstracts storage with a swappable storage backend that you can operate with an in memory solution in your tests. This also makes it easier to do more complex things like encryption, compression, write through caching etc if you require it at a later date. |
It's time!!! Node.js v6 is out, and with it... PROXIES. @Sebmaster, would you be up for updating webidl2js to be able to generate proxies when named getters/setters/deleters are present? I think we should target this request first, before worrying about other stuff like NamedNodeMap or NodeList or whatever. This is nice and self-contained and won't impact performance of core primitives. https://html.spec.whatwg.org/multipage/webstorage.html#storage-2 has the IDL that we need to support. I think the way to go would be to simply make the proxy's get behavior delegate to the impl's getItem, etc. |
+1 |
You can use node-localstorage var LocalStorage = require('node-localstorage').LocalStorage;
global.localStorage = new LocalStorage('./build/localStorageTemp');
global.document = jsdom('');
global.window = document.defaultView;
global.window.localStorage = global.localStorage; |
@adjavaherian @justinmchase I was playing around with jsdom which i use for testing in both react-jwt-auth and react-jwt-auth-redux and it works great. However, I have been also working on enverse - environment checks for isomorphic development. One of the checks is |
webidl2js support for this is now in place. If people want to take this on, I'd suggest studying the DOMStringList implementation (for dataset) that recently landed. For now we can keep everything in-memory, although eventually we should provide a way of storing to disk if people want that. (Do people want that?) |
I'd like to work on implementing this. You can assign the issue to me. |
this mock works better because when a key isn't stored, const jsdom = require('jsdom');
// setup the simplest document possible
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>');;
// get the window object out of the document
const win = doc.defaultView;
win.localStorage = win.sessionStorage = {
getItem: function(key) {
const value = this[key];
return typeof value === 'undefined' ? null : value;
},
setItem: function (key, value) {
this[key] = value;
},
removeItem: function(key) {
return delete this[key]
}
};
// set globals for mocha that make access to document and window feel
// natural in the test environment
global.document = doc;
global.window = win; Why is it relevant? The line below works fine in a browser environment.
|
@simoami Just watch out with your assignment chaining with |
https://gist.github.com/rkurbatov/17468b2ade459a7498c8209800287a03 - we use this polyfill for both local/session storages. It's based on https://github.com/capaj/localstorage-polyfill by @capaj |
Those who stumble onto this thread later on, after the recent jsdom improvements you need to set As a feedback, I couldn't find the native localStorage events mentioned as a solution to this problem And as a heads up to anyone who uses jsdom in parallel and use localStorage heavily, the |
For me, I needed: const dom: JSDOM = new JSDOM("...");
global.localStorage = dom.window.localStorage; |
Hey folks,
Is somebody working on implementing localStorage/sessionStorage APIs in jsdom?
Regards,
Alvaro
The text was updated successfully, but these errors were encountered: