Эй, ребята,
Кто-нибудь работает над реализацией API-интерфейсов localStorage / sessionStorage в jsdom?
С уважением,
Альваро
К сожалению, это довольно сложно без прокси ES2015 :(. Мы могли бы заставить работать getItem / setItem / и т. Д., Но корректное применение изменений свойств на самом деле невозможно.
Было бы неплохо иметь хотя бы опыт работы в памяти, то есть 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, но мы не сможем поддерживать обратный путь (установка произвольных свойств объекта) без прокси.
Правильно, я предполагаю, что основная проблема, связанная с установкой ключей по доступу к свойствам, заключается в том, что мы не можем правильно преобразовать значения в строки, как это делает настоящий интерфейс.
Да, это очень важный аспект локального хранилища. Для этого нам понадобится ES6 Proxy, v8 еще не реализовал его (у Microsoft и 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
возможно, что-то подобное сработает https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage ?
Я думаю, вы обнаружите, что вам все еще не хватает некоторых характеристик реального локального хранилища, как описано выше.
Связанное решение является хорошим полифиллом, но я не думаю, что это будет достаточно точная реализация для jsdom.
@mnahkies Честно говоря, полифилл лучше, чем ничего (это то, что у вас есть сейчас). Вы должны просто вставить его и добавить некоторую документацию об ограничениях, которые в любом случае никогда не достигнут 99% людей.
+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;
}
};
ФАИК это хорошо издевается. Исправляет некоторые из моих тестов. Спасибо.
Он исправляет ваши тесты до тех пор, пока кто-то случайно не запишет объект в локальное хранилище, используя доступ к свойствам, и не создаст тонкую ошибку.
На мой взгляд, вам было бы лучше иметь объект, который абстрагирует хранилище с заменяемым сервером хранилища, с которым вы можете работать с решением в памяти в своих тестах.
Это также упрощает выполнение более сложных вещей, таких как шифрование, сжатие, запись через кеширование и т. Д., Если вам это потребуется позже.
Пора!!!
Node.js v6 отсутствует, а вместе с ним ... ПРОКСИ.
@Sebmaster , не могли бы вы обновить webidl2js, чтобы иметь возможность генерировать прокси, когда присутствуют именованные геттеры / сеттеры / удалители? Я думаю, мы должны сначала нацелить этот запрос, прежде чем беспокоиться о других вещах, таких как NamedNodeMap, NodeList или что-то еще. Это красиво и автономно и не повлияет на производительность основных примитивов.
https://html.spec.whatwg.org/multipage/webstorage.html#storage -2 имеет IDL, который нам необходимо поддерживать. Я думаю, что можно было бы просто сделать делегирование поведения прокси-сервера getItem имплементации и т. Д.
+1
Вы можете использовать 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 Я играл с jsdom, который я использую для тестирования как в react-jwt-auth, так и в response-jwt-auth-redux, и он отлично работает. Однако я также работал над enverse - проверками среды на изоморфность развития. Один из чеков - localStorage
и sessionStorage
. Я столкнулся с той же проблемой, как правильно протестировать, и ваш полифил отлично работает. Спасибо вам, ребята! Было бы здорово, если бы он по умолчанию шел с 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;
Почему это актуально? Строка ниже отлично работает в среде браузера. json.parse()
не работает, если передано undefined
качестве аргумента, но отлично работает с параметром 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 - мы используем этот полифилл как для локальных / сессионных хранилищ. Он основан на https://github.com/capaj/localstorage-polyfill от @capaj
Те, кто натыкается на эту ветку позже, после недавних улучшений jsdom, вам нужно установить window._localStorage
в свое собственное хранилище mocking.
В качестве обратной связи я не смог найти собственные события localStorage, упомянутые как решение этой проблемы.
И как предупреждение всем, кто использует jsdom параллельно и активно использует localStorage, node-localstorage
и т. Д. В значительной степени бесполезны, вы можете также заново изобрести колесо, они не предназначены для параллельного использования, а также базовые вещи, такие как for(var key in localStorage)
или Object.keys(localStorage)
и т. д., тоже не работают
Самый полезный комментарий
Решение @justinmchase отлично подходит для тестирования. Возможно, вы также захотите добавить sessionStorage.
ФАИК это хорошо издевается. Исправляет некоторые из моих тестов. Спасибо.