嘿伙计,
是否有人致力于在 jsdom 中实现 localStorage/sessionStorage API?
问候,
阿尔瓦罗
不幸的是,如果没有 ES2015 代理,这些很难:(。我们可以让 getItem/setItem/etc. 工作,但正确应用属性修改是不可能的。
至少有一个内存中的体验会很好,即 localStorage 具有类似于 sessionStorage 的行为
那应该不那么复杂,不是吗?
不,因为我们不能很好地模拟 API。 对于简单的用例(即仅使用 getItem/setItem),它可能有效,但一旦直接访问属性,我们的实现就会崩溃。
@Sebmaster :我为存储接口编写了一个简单的垫片,用于我的单独包的单元测试。
https://github.com/mnahkies/node-storage-shim
它目前是一个临时解决方案,并且禁止设置与存储接口的方法名称冲突的键。 它目前也没有实现规范的事件触发部分。
Real localStorage 确实允许使用 setItem 设置这些键,但是使用属性访问确实会破坏函数定义,并且一旦设置,您也无法使用属性访问来访问这些键。
然而,除了这些限制之外,我认为这是一个非常忠实的实现:
https://github.com/mnahkies/node-storage-shim/blob/master/test.js
您能否详细说明 API 的哪些方面不能很好地模拟?
据我所知,目前无法模拟的主要内容是响应属性设置而触发的存储事件。
如果您认为上面提到的警告已经足够完整,我很乐意尝试将它与 jsdom 集成。
您能否详细说明 API 的哪些方面不能很好地模拟?
localStorage 支持直接在界面上设置任何属性名称,如
localStorage.myKey = "myVal";
localStorage.getItem('myKey') // 'myVal'
localStorage.setItem('otherKey', 'val')
localStorage.otherKey // 'val'
如果 setItem 被调用,我们可以通过总是创建一个 getter 来模拟一些,但是如果没有代理,我们将无法支持相反的方式(在对象上设置任意属性)。
是的,我想通过属性访问设置键的主要问题是我们不能像真正的接口那样正确地将值强制转换为字符串。
是的,这是本地存储的一个非常重要的方面。 为此我们需要 ES6 代理,v8 还没有实现它(微软和 Mozilla 已经有了它!)
我们在 jsdom 的多个位置遇到了同样的障碍
我不明白为什么这不起作用:
var localStorage = {
getItem: function (key) {
return this[key];
},
setItem: function (key, value) {
this[key] = value;
}
};
localStorage.setItem('foo', 'bar');
localStorage.bar = 'foo'
assert(localStorage.foo === 'bar')
assert(localStorage.getItem('bar') === 'foo')
我错过了什么?
当您使用属性访问设置项目时,真正的 localStorage 总是将值强制转换为字符串。
例如:
localStorage.foo = 35
assert(typeof localStorage.foo === "string")
localStorage.foo = {my: 'object'}
assert(localStorage.foo === "[object Object]")
这在没有 ES6 代理的情况下是不可能的,并且是 localStorage 的一个重要特性
我知道了。 虽然如果你对自己感到羞耻 ;)
我认为更重要的是,如果你对对象这样做,它可能是一个错误,会被 jsdom 掩盖,然后出现在浏览器中
+1
+1
我想您会发现您仍然缺少真实本地存储具有的一些行为,如上所述。
链接的解决方案是一个很好的 polyfill,但我认为这对 jsdom 来说不是一个足够忠实的实现
@mnahkies老实说,
+1 最后一条评论, MDN上的setItem
和getItem
作为存储访问器。 所以它可以被认为是我们真正缺少的一个常见用例。
现在,我用jasmine.createSpy
解决它(因为我和 Jasmine 一起工作,其他间谍库也可以做到)
@justinmchase的解决方案非常适合测试。 可能还想添加 sessionStorage。
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 这很好笑。 修复了我的一些测试。 谢谢。
它会修复您的测试,直到有人不小心使用属性访问将对象写入本地存储并创建了一个微妙的错误。
在我看来,您最好拥有一个使用可交换存储后端抽象存储的对象,您可以在测试中使用内存解决方案进行操作。
如果以后需要,这也可以更轻松地执行更复杂的操作,例如加密、压缩、直写缓存等。
是时候了!!!
Node.js v6 出来了,随之而来的是......代理。
@Sebmaster ,当存在命名的 getter/setter/deleters 时,您是否愿意更新 webidl2js 以便能够生成代理? 我认为我们应该首先针对这个请求,然后再担心 NamedNodeMap 或 NodeList 之类的其他东西。 这是很好的和自包含的,不会影响核心原语的性能。
https://html.spec.whatwg.org/multipage/webstorage.html#storage -2 有我们需要支持的 IDL。 我认为要走的路是简单地将代理的 get 行为委托给 impl 的 getItem 等。
+1
您可以使用节点本地存储
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我在玩 jsdom,我用它在react-jwt-auth和react-jwt-auth-redux 中进行测试,而且效果很好。 然而,我也一直在致力于同构开发的localStorage
和sessionStorage
。 我遇到了完全相同的问题,即如何正确测试它,并且您的 polyfil 运行良好。 谢谢你们! 如果它默认带有 jsdom 那就太好了。
webidl2js 对此的支持现已到位。 如果人们想接受这个,我建议研究最近登陆的 DOMStringList 实现(用于数据集)。
现在我们可以将所有内容保存在内存中,但最终我们应该提供一种存储到磁盘的方法,如果人们需要的话。 (人们想要那个吗?)
我想努力实现这一点。 您可以将问题分配给我。
这个模拟效果更好,因为当没有存储密钥时, localStorage.getItem()
应该返回null
,而不是undefined
:
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;
为什么是相关的? 下面的行在浏览器环境中工作正常。 如果将undefined
作为参数传递,则json.parse()
失败,但使用null
作为参数可以正常工作:
let users = JSON.parse(localStorage.getItem('users')) || [];
@simoami 请注意您的作业链接win.localStorage = win.sessionStorage = { ... }
。 这是分配给两个变量的相同对象引用,因此调用任何一个的 get/set 函数将访问相同的底层对象。 例如,调用localStorage.set('foo', 'bar')
意味着sessionStorage.get('foo')
将起作用——这对于简单的测试来说可能没问题,但会弄乱任何需要单独存储的东西。
https://gist.github.com/rkurbatov/17468b2ade459a7498c8209800287a03 - 我们将此 polyfill 用于本地/会话存储。 它基于@capaj 的https://github.com/capaj/localstorage-polyfill
那些后来偶然发现这个线程的人,在最近的 jsdom 改进之后,你需要将window._localStorage
为你自己的模拟存储
作为反馈,我找不到提到的本地 localStorage 事件作为解决此问题的方法
作为并行使用 jsdom 并大量使用 localStorage 的任何人的提醒, node-localstorage
等几乎没用,您不妨重新发明轮子,它们不是为并行使用而设计的,也像for(var key in localStorage)
或Object.keys(localStorage)
等基本的东西也不起作用
最有用的评论
@justinmchase的解决方案非常适合测试。 可能还想添加 sessionStorage。
FAIK 这很好笑。 修复了我的一些测试。 谢谢。