Jasmine testing setting component variable - javascript

I'm using spyOn, in a jasmine test, to "listen" to a function call from a service,
that function returns an Observable.
I'm getting the error unexpected token U JSON;
The error is generated from the component line:
this.config = JSON.parse(localStorage.getItem('configuration'));
The localStorage item was JSON.stringified;
I understand that this error is usually thrown when JSON.parse = undefined,
So I tried to set the variable within my test i.e.
component.config = mockConfig;
So..
// Storage Mock
function storageMock() {
var storage = {};
return {
setItem: function(key, value) {
storage[key] = value || '';
},
getItem: function(key) {
return key in storage ? storage[key] : null;
},
removeItem: function(key) {
delete storage[key];
},
get length() {
return Object.keys(storage).length;
},
key: function(i) {
var keys = Object.keys(storage);
return keys[i] || null;
}
};
}
let mockConfig = JSON.stringify({
base_url:"http://image_url/",
poster_sizes:['w9', 'w100']
})
//Set storage
let m = storageMock()
m.setItem('configuration', mockConfig)
it('Should set items array with values from MoviesService', () => {
component.config = JSON.parse(m.getItem('configuration'));
let spy = spyOn(moviesService, 'getPreview').and.callFake(()=>{
return Observable.from([[{id1: 1, title: 'a'}, {id1: 2, title: 'b'}]])
})
component.ngAfterViewInit();
expect(component.items.length).toBeGreaterThan(0);
});

For anyone who come across this problem I got this working by by placing this code in the beforeEach wrapper
Object.defineProperty(window, 'localStorage', { value: m });
Basically it uses my mock localStorage variable instead of the one from the window object.

Related

Chrome clears localStorage on closing browser/ error in my code/ I do not understand something totally

I work over a small React app that should store some data in local storage. And if I understand it correctly, it should keep them till clearly ordered to clear. The reason for this post is that while app data perfectly survives the refreshing page action, it disappears after closing/opening Chrome.
Here is the function which creates the storage:
export function initGlobalStorage() {
var Storage = function (options) {
options = options || {};
for (var i in options) {
this[i] = options[i];
}
};
const prefix = window.location.href;
Storage.prototype = {
storage: window.localStorage,
addPrefix: function(key){return (prefix + key); },
set: function (key, value) {
this.storage.setItem(this.addPrefix(key), JSON.stringify(value));
},
get: function (key) {
return this.storage.getItem(this.addPrefix(key));
},
remove: function (key, value) {
this.storage.remove(key, value);
},
clear: function () {
this.storage.clear();
},
key: function (index) {
return this.storage.key(index);
},
each: function (fn) {
if (typeof fn === "function") {
for (var i = 0, key; i < this.storage.length; ++i) {
key = this.storage.key(i);
fn(this.storage.getItem(key), key, i);
}
}
},
getAll:function(){
let result =[];
for(var key in this.storage){
if (key.includes(prefix)){result.push(JSON.parse(this.storage.getItem(key)))};
}
return result;
},
hasItems:function(){
console.log(this.getAll().length);
return this.getAll().length? true:false;
},
};
window.Storage = {
local: new Storage({ storage: window.localStorage }),
session: new Storage({ storage: window.sessionStorage }),
};
};
window.local.href is for distinguish 'my items' in localStorage from others that possibly exists there on client computer. BTW, currently I only test this app on localhost.
Here is how above function is applied
export function checkSupportForCache() {
return dispatch => {
if (storageAvailable('localStorage')) {
dispatch(cacheSupported());
console.warn("Storage available");
initGlobalStorage();
if (window.Storage.local.hasItems()){
console.log('Storage contains items');
dispatch(cacheNotEmpty());
}else{
console.warn("No items in storage");
}
} else {
console.warn("Storage not available");
}
};
}
storageAvailable('localStorage') is a function that checks support for localStorage in a certain browser.
As I have written, when I refresh page localStorage is still there - then I suppose code is OK.
But what happens when do close browser then? I do not consciously request for any sort of purging action. Do I unconsciously? I have checked Chrome settings and there is nothing that looks suspicious. Do I not understand anything at all as per subject? Maybe, just give me a hint.

TypeError: Cannot set property localStorage of #<Window> which has only a getter

I am mocking localStorage in unitests like
function storageMock() {
var storage = {};
....
}
and setting localStorage like
window.localStorage = localStorageMock()
It was working fine until, I have updated Node to 10.15.1.
It is throwing error that TypeError: Cannot set property localStorage of #<Window> which has only a getter.
Any idea that how I can mock localStorage and set it to window.localStorage.
P.S I am getting answers like setItem and getItem on localStorage, is there any way that I can set whole localStorage at once.
Add
Object.defineProperty(window, 'localStorage', {
value: storageMock
});
Full example:
const localStorageMock = (() => {
let store = {};
return {
getItem(key) {
return store[key] || null;
},
setItem(key, value) {
store[key] = value.toString();
},
removeItem(key) {
delete store[key];
},
clear() {
store = {};
}
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
window.localStorage.setItem("KEY", "INPUT")

Localstorage service is not notifying of property changes

I have a local storage service I've created which looks like this
import Ember from 'ember';
import ENV from 'bidr/config/environment';
const stringify = JSON.stringify;
const parse = JSON.parse;
function setItem(key, value) {
localStorage.setItem(key, stringify(value));
}
function getItem(key) {
var result = localStorage.getItem(key);
if (result) {
return parse(result);
}
}
export default Ember.Service.extend({
namespace: ENV.APP.LocalStorageKey,
user: null,
init: function() {
this.set('user',this.getItem('user'));
},
setItem: function (key, object) {
var ttlOptions = arguments[2];
if (ttlOptions) {
this._setTTLKey(key, ttlOptions);
}
this.set(key,object);
setItem(this._namespacedKey(key), object);
},
getItem: function (key) {
return getItem(this._namespacedKey(key));
},
keyExpired: function (key, now) {
var ttl = this.getItem(`_ttl_${key}`);
if (ttl) {
var expiry = new Date(ttl.lastUpdated)
.setTime(new Date(ttl.lastUpdated)
.getTime() + ttl.ttl);
now = now || new Date();
return now > expiry;
}
return true;
},
_setTTLKey: function (key, ttlOptions) {
var dateTime = new Date();
setItem(this._namespacedKey(`_ttl_${key}`),
{ttl: ttlOptions.ttl, lastUpdated: dateTime}
);
},
_namespacedKey: function (key) {
return this.get('namespace') + `.${key}`;
}
});
The user object is so I can access it directly in a template or as a computed property value.
The problem is another computed property that depends on a property of user in the service is not being notified when it gets changed. In my application controller I have an action that updates a property on the user property in the service
updateActiveEvent(eventInfo) {
var currentUserInfo = this.get('localStorage').getItem('user');
currentUserInfo.active_auction = eventInfo.eid;
this.get('localStorage').setItem('user',currentUserInfo);
},
(this comes in from a socket service) this is working properly as after this action fires I can check the browsers localstorage object and see the active_auction property of it has changed. But in my template where I am displaying that (or a computed property based on that) it's not updating.
I saw there is possibly a way I can force ember to notify of property changes but couldn't seem to get this to work, not sure if I was doing it right.

Console logging "this" returns "null"

I am trying to create a flux store for a React app I am building. I am using an object-assign polyfill npm package and Facebook's Flux library.
Initially I was getting the error "Cannot read property '_data' of null' error in the console which was refering to var currIds = this._data.map(function(m){return m.id;});. That method is currently the only one being called directly. I then did console.log(this) which returned "null".
I find this strange. What is going on?
My code:
var Assign = require('object-assign');
var EventEmitterProto = require('events').EventEmitter.prototype;
var CHANGE_EVENT = 'CHANGE';
var StoreMethods = {
init: function() {},
set: function (arr) {
console.log(this);
var currIds = this._data.map(function(m){return m.id;});
arr.filter(function (item){
return currIds.indexOf(item.id) === -1;
}).forEach(this.add.bind(this));
},
add: function(item){
console.log(this);
this._data.push(item);
},
all: function() {
return this._data;
},
get: function(id){
return this._data.filter(function(item){
return item.cid === id;
})[0];
},
addChangeListener: function(fn) {
this.on(CHANGE_EVENT, fn);
},
removeChangeListener: function(fn) {
this.removeListener(CHANGE_EVENT, fn);
},
emitChange: function() {
this.emit(CHANGE_EVENT);
},
bind: function(actionType, actionFn) {
if(this.actions[actionType]){
this.actions[actionType].push(actionFn);
} else {
this.actions[actionType] = [actionFn];
}
}
};
exports.extend = function(methods) {
var store = {
_data: [],
actions: {}
};
Assign(store, EventEmitterProto, StoreMethods, methods);
store.init();
require('../dispatcher').register(function(action){
if(store.actions[action.actionType]){
store.actions[action.actionType].forEach(function(fn){
fn.call(null, action.data);
})
}
});
return store;
};
I can't see where set is called, however your this can be null if the function is invoked through call (see here) or apply, and your first argument is null.
This also happens in your require.register callback:
fn.call(null, action.data) //First parameter is your 'this'.

JavaScript object that saves/loads its state

I'd like to create a Javascript object that can save and load its state (to local storage).
This is the basic pattern I'm using:
var obj = function () {
// private members
//
return {
// public members
load: function () {
this.state = JSON.parse(localStorage.getItem('obj'));
if (this.state === null) {
this.state = {
name: 'foo'
};
}
},
save: function () {
localStorage.setItem('obj', JSON.stringify(this.state));
}
};
}();
// load state
obj.load();
console.log(obj.state.name);
// save state
obj.state.name = 'bar';
obj.save();
But there's one thing that annoys me about this pattern: I have to access the object's persistent properties through the 'state' property.
How can I rewrite this so I can use the object in a more natural way, like:
// load state
obj.load();
console.log(obj.name);
// save state
obj.name = 'bar';
obj.save();
This is a very simple 'state', but the solution has to work for a complex state object with nested objects, arrays etc., so simply adding a 'name' property to my object is not what I'm after.
If you don't care which properties get loaded/saved then you can simply copy all from state into self. For example, after reading into var state (instead of this.state since you don't want state to be a part of this anymore): for(x in state) this[x] = state[x];
similarly, you'd save out: var state = {}; for(x in this) state[x] = this[x]
However, if you want to have a pre-defined list, then I'd recommend: var fields = ['name', 'zip', 'age'];
And then use for(x in fields) this[x] = state[x] to load and for(x in fields) state[x] = this[x]; to save.
Sorry it's a bit pieced together, but I hope you can follow what I mean :)
EDIT: Added full example per OPs request.
An example of a full solution using this technique is as follows:
var obj = function () {
// private members
//
return {
// public members
load: function () {
var state = JSON.parse(localStorage.getItem('obj'));
if(state == null) state = { name: 'foo' };
for(x in state) this[x] = state[x];
},
save: function ()
{
var state = {};
// check if it's a function. This version taken from underscorejs
var isFunction = function(obj) {
return !!(obj && obj.constructor && obj.call && obj.apply);
};
for(x in this)
{
if(isFunction(this[x])) continue; // skip functions
state[x] = this[x];
}
localStorage.setItem('obj', JSON.stringify(state));
}
};
};
You can also accomplish a direct save when a property changes,
by using ES5 getters/setters or by using Watch.js
Watch.js example:
var obj = (function () {
// private members
//
var self = {
// Some properties
name: '',
otherName: '',
// Try to load state or use "foo state"
state: JSON.parse(localStorage.getItem('obj')) || {
name: 'foo'
},
save: function () {
localStorage.setItem('obj', JSON.stringify(this.state));
}
};
// Watch the object and save it to local storage, when a property changes
// (Of course, you don't need to call the save method here...)
watch(self, function(property, value) {
console.log('saving state!');
self.state[property] = value;
self.save();
});
return self;
}());
// Update some properties and see that it is saved to local storage.
obj.name = "Some name";
obj.otherName = "Some other name";
console.log(JSON.parse(localStorage.getItem('obj')));
​
Example on JsFiddle.
You could make the state internal and surface getters and setters:
var obj = function () {
// private members
var state = {};
return {
// public members
load: function () {
var loadedState = JSON.parse(localStorage.getItem('obj'));
if (loadedState === null) {
state = {
name: 'foo'
};
} else {
state = loadedState;
}
},
save: function () {
localStorage.setItem('obj', JSON.stringify(state));
},
getState: function (key) {
return state[key];
},
setState: function (key, value) {
state[key] = value;
}
};
};
Using jQuery's extend():
var obj = (function () {
return {
load: function () {
var stored = localStorage.getItem("obj");
var state = stored ? JSON.parse(stored) : {
name: 'foo'
};
$.extend(this, state);
},
save: function () {
localStorage.setItem("obj", JSON.stringify(this));
}
};
})();
// load state
obj.load();
console.log(obj);
// save state
obj.name = 'bar';
obj.save();
jsfiddle
All credit to pimvdb.

Categories