How to mock global variables in Web Component Tester - polymer

We have several situations where our Polymer elements have methods that rely on global behaviour, such as viewport size detection, or an analytics package that injects a global variable.
Now I'm trying to test these methods using Web Component Tester, but I can't see how to inject e.g. stubs into the window object (e.g. like is possible with webdriver's execute function). How do I do this?
An example of a test that didn't work:
test('my-element should not crash when `Analytics` is blocked', function () {
// I'd like window.Analytics in my app to be undefined,
// but this doesn't work, obviously:
window.Analytics = undefined;
myEl = fixture('my-element');
expect(myEl.ready).to.not.throw();
});

You could try using before or beforeEach and after or afterEach hooks.
var tempAnalytics = window.Analytics;
before(function() {
// runs before all tests in this block
window.Analytics = undefined;
});
after(function() {
// runs after all tests in this block
window.Analytics = tempAnalytics;
});
Another option is to use Sinon sandboxes to stub the property.
var sandbox = sinon.sandbox.create();
sandbox.stub(window, "Analytics", undefined);
// then restore when finished like above
sandbox.restore();

Related

Save result to variable?

So I have been developing a note taking app, and I am having some trouble with displaying the username! Normally you would get a result like this:
con.query('SELECT someVariable FROM someColumn', function (result) {
console.log(result)
})
But I would like to save the result to a variable like this:
var variable = '';
con.query('SELECT someVariable FROM someColumn', function (result) {
variable = result
})
console.log("Here is some data: " + variable)
But that obviously wouldn't work. So how can I do this???
The point
How would I save a mySQL result to a variable, so it can be used later in my code? Also Im not an experienced developer so you might have to do a bit more explaining that usual, thanks.
If you're new to Node and/or JavaScript then you've just stumbled on one of the major problems of asynchronous programming. Here's your code as the JavaScript runtime sees it:
// Declare variable
var variable = '';
var callback = function(result) {
variable = result;
};
// This code runs right away
con.query('SELECT someVariable FROM someColumn', callback);
// Then this code runs
console.log("Here is some data: " + variable);
// Then, a thousand years later (from a CPU's perspective) this callback executes
callback(result);
You can see you're jumping the gun here. You can't depend on any behaviour until the callback has run, or in other words, you need to put any dependent behaviour inside the callback.
Since it's 2020 you can also do this with async and await if you're using a Promise-capable library. Your code could look like:
// Assign variable to the result of the query call, waiting as long as necessary
let variable = await con.query('SELECT someVariable FROM someColumn');
console.log("Here is some data: " + variable);
This will be properly sequenced.

How to manage application variables in a ReactJS app w/ gulp?

I have some application/configurations variables, such as MY_ENDPOINT, that should point to http://dev.myendpoint/ for dev and http://myendpoint/ for production. Where should I store this information in a ReactJS application that uses gulpjs (and flux)?
If you're using browserify, you can use the envify transform to provide compile time variables.
var envParams = {};
function bundle(){
return gulp.src('src/app.js')
.pipe(gulpBrowserify({
transform: [
'reactify',
['envify', envParams]
]
}))
.pipe(gulp.dest('dist'));
};
gulp.task('scripts-dev', function(){
envParams = {MY_ENDPOINT: 'http://dev.myendpoint/'};
return bundle();
});
gulp.task('scripts-prod', function(){
envParams = {MY_ENDPOINT: 'http://myendpoint/'};
return bundle();
});
And in your code:
fetch(process.env.MY_ENDPOINT + "api/foo')...
You can also supply env vars on the command line, however the gulp file will override them by default:
MY_ENDPOINT=something gulp scripts-dev
And to allow command line to take precedence:
envParams = {MY_ENDPOINT: process.env.MY_ENDPOINT || 'http://myendpoint/'};
// or with es6 shim
envParams = Object.assign({MY_ENDPOINT: 'http://myendpoint/'}, process.env);
You can also use envify before other transforms which require static strings, like brfs:
var config = JSON.parse(fs.readFileSync(process.env.CONFIG_PATH));
Which will compile into:
var config = JSON.parse(fs.readFileSync('/home/.../dev-config.json'));
And finally into:
var config = JSON.parse('{"foo": "bar"}');

How to make the library work with the caller script PropertiesService?

Until Google extends the import/export API to container-bound Apps Script projects, I have moved most of my project to a library which can use that API, and then made Google Docs project into a shell that just calls through to the library.
My problem is having the library access the same properties (PropertiesService) as the Google Doc project. Since I have existing users of my Docs Add-on, I need to keep using these properties.
In my Google Doc project, I tried
$.PropertiesService = PropertiesService;
(where $ is my library).
It didn't work. The library kept using its own properties.
So then I tried:
function _mock(obj) {
var ret = {};
for(var key in obj) {
if(typeof obj[key] == 'function') {
ret[key] = obj[key].bind(obj);
} else {
ret[key] = obj[key];
}
}
return ret;
}
$.PropertiesService = _mock(PropertiesService);
Still not working. Trying again:
function _mock(obj) {
var ret = {};
for(var key in obj) {
if(typeof obj[key] == 'function') {
ret[key] = (function(val) {
return function() {
return val.apply(obj, arguments);
};
})(obj[key]);
} else {
ret[key] = obj[key];
}
}
return ret;
}
$.PropertiesService = _mock(PropertiesService);
This works.
At this point, I'm wondering:
Why did the first two ways not work, but the third way did?
Can I expect this to continue working?
Is there a better way to have a library access the main script's properties?
Documentation is sparse. There is this, but the PropertiesService is not mentioned.
Sharing of resources
As you are aware, libraries have shared and non-shared resources. PropertiesService is listed under non-shared resources, meaning that the library has its own instance of the service that is accessed when you reference it in the library code.
const getStore = () => PropertiesService.getScriptProperties();
If the function above is declared in the library, it will use the library's resource, if in the calling script - its own instance.
V8 runtime solution
V8 runtime does not create a special context for your code and gives you access to built-in services directly. Because of this when using the runtime, the service can be injected by simply defining or replacing a property on a global this:
//in the library;
var getProperty = ((ctxt) => (key) => {
var service = ctxt.injectedService;
var store = service.getScriptProperties();
return store.getProperty(key);
})(this);
var setProperty = ((ctxt) => (key, val) => {
var service = ctxt.injectedService;
var store = service.getScriptProperties();
return store.setProperty(key, val);
})(this);
var inject = ((ctxt) => (service) => ctxt.injectedService = service)(this);
var greet = ((ctxt) => () => {
var store = ctxt.injectedService.getScriptProperties();
return store.getProperty("greeting") || "Ola!";
})(this);
//in the calling script;
function testSharedResources() {
PropertiesService.getScriptProperties().setProperty("greeting", "Hello, lib!");
$.inject(PropertiesService);
Logger.log($.greet()); //Hello, lib!
$.setProperty("greeting", "Hello, world!");
Logger.log($.greet()); //Hello, world!
}
In some contexts global this will be undefined (I encountered this when adding a library to a bound script). In this case, simply define a private global namespace (to avoid leaking to the caller script):
//in the library;
var Dependencies_ = {
properties : PropertiesService
};
var use = (service) => {
if ("getScriptProperties" in service) {
Dependencies_.properties = service;
}
};
//in the calling script;
$.use(PropertiesService);
Rhino runtime solution
Older Rhino runtime, on the other hand, creates a special implicit context. This means that you have no access to built-in services or the global this. Your only option is to bypass calling the service in the library (your approach #3 is perfect for doing so).
Questions
Why did the first two ways not work, but the third way did?
All issues with your approaches boil down to:
Resource sharing (libraries have their own service instances)
Special implicit context (no external access to lib built-ins in Rhino)
But there is a catch: all 3 approaches do work as designed.
First, Approach one does work if you specifically reference the PropertiesService on $. This makes sense as the library is included as a namespace with members mapped to global declarations in the library. For example:
//in the caller script
PropertiesService.getScriptProperties().setProperty("test", "test");
$.PropertiesService = PropertiesService;
Logger.log( $.PropertiesService.getScriptProperties().getProperty("test") ); // "test"
Logger.log( $.getProperty("test") ); // "null"
//in the library
function getProperty(key) {
var store = PropertiesService.getScriptProperties();
return store.getProperty(key);
}
Approach two also works. Binding of the function in the caller script does not change the fact if called in the library it receives library context, but if you call the bound copy directly in the calling script, it works:
//in the caller script
PropertiesService.getScriptProperties().setProperty("test", "test");
var bound = $.PropertiesService.getScriptProperties.bind(PropertiesService);
var obj = { getScriptProperties : bound };
$.PropertiesService = obj;
Logger.log( bound().getProperty("test") ); // "test"
Logger.log( $.getProperty("test") ); // "null"
Now, why does the third approach work out of the box? Because of the closure resulting from the wrapped function capturing the PropertiesService of the calling script and applying the getScriptProperties method. To illustrate:
//in the caller script
var appl = {
getScriptProperties : (function(val) {
return function() {
return val.apply(PropertiesService);
};
})(PropertiesService.getScriptProperties)
};
$.PropertiesService = appl;
Logger.log( $.getProperty("test") ); // "test"
Can I expect this to continue working?
Yes and no. Yes, because your _mock function behavior exhibits the expected behavior in all cases. No, because apply relies on the getScriptProperties not being implemented as an arrow function where this override will be ignored.
Is there a better way to have library access the main script's properties?
For Rhino runtime - don't think so. For V8 - direct injection of the service will suffice.

backbone.js fetch json success will not hit

i use fetch from backbone.js to load a json model but success will not hit.
var DialogModel = Backbone.Model.extend({
url : function() {
return '/messages/getDialog';
},
parse : function(res) {
return res.dialog;
}
});
var DialogView = Backbone.View.extend({
el: $("#page"),
initialize: function() {
var onDataHandler = function() {
this.render();
};
this.model = new DialogModel();
this.model.fetch({ success : onDataHandler});
},
render: function(){
var data = {
dialogModel : this.model
};
var form = new Backbone.Form({
model: data
});
$(this.el).html(form.render().el);
}
});
What happens now:
DialogView initialize is called.
this.model.fetch is called but the onDataHandler function will not be hit if success.
/messages/getDialog throws a json file back.
The json file is loading well as i can see in the network browser.
Thanks for your help!
Oleg
The problem you're having is due to a typical JS gotcha and not related to Backbone itself. Try
var that = this;
this.model.fetch({
success : function () {
that.render();
}
});
The way you're currently passing onDataHandler is problematic as it will cause this to refer to the global object instead of the DialogView, when the function is called.
This fiddle demonstrates the problematic version vs one that works.
(You may also want to take a look at JS strict mode which can shield you from this type of errors.)
Even better is to listen for an event:
this.model.on("sync", this.render).fetch();
I ran across this question while looking for something else, but the currently accepted answer drives me nuts. There's no good reason to be sprinkling this and that all over your code. Backbone (underscore) includes a context parameter that you can bind to.
that = this makes no sense. If you must implement obsolete 2007-era Crockford patterns, then say var self = this. Saying that = this is like saying left = right. Everyone Stop.

slimbox wraps DOM in a var -

I have installed Slimbox which is working fine.
However the Slimbox author has wrapped all the Slimbox code and is initializing the DOM in a var, after setting some global vars.
var Slimbox = (function() {
// Global variables, accessible to Slimbox only
var win = window, ie6 = Browser.ie6, options, images, activeImage = -1, activeURL, prevImage, nextImage, compatibleOverlay, middle, centerWidth, centerHeight,
// Preload images
preload = {}, preloadPrev = new Image(), preloadNext = new Image(),
// DOM elements
overlay, center, image, sizer, prevLink, nextLink, bottomContainer, bottom, caption, number,
// Effects
fxOverlay, fxResize, fxImage, fxBottom;
// now initialize the DOM
window.addEvent('domready', function() {
// *************
// This is where the problem arises.
// I call ajax actions here, and some functions which are external to 'domready'
// *************
pageInit();
setupFormSubmit('product_bid', 'afterPlaceBid');
setupAjaxAction('delete_validate_id_link', 'afterDelete');
setupAjaxAction('move_validate_down_link', 'afterMoveValidate');
//etc...
});
// end the DOM function
function afterMoveValidate(){
}
function afterDelete() {
}
// all the Slimbox functions are here too...
etc..
//end the var Slimbox
})
The problem is that my external functions, while still in the var, don't have global scope, yet the Slimbox functions do.
This did work without Slimbox, where I initialized the DOM and had external functions.
Can anyone help with an idea / explanation?
thanks
What the author of slimbox has done is implemented the model pattern. If you test the code bellow you will notice that you can not access privateVar or privateFunction from the outside. However, you can access them through publicVar and `publicFunction.
So everything that is put in the slimbox function (or the modelPattern bellow) is not accessible from the outside, only the parts that are "exported" in object that is returned. Because only that part will be in the "slimbox" variable (or the modelPattern bellow).
var modelPattern = (function() {
// Global variables, accessible to Slimbox only
var privateVar = 'private';
function privateFunction() {
console.log(privateVar);
}
return {
publicVar: 'public',
publicFunction: function() {
console.log(privateVar);
}
};
})();
modelPattern.publicFunction();
if (!modelPattern.privateFunction) {
console.log("privateFunction is not defined");
}​
You can see the result of the code in this fiddle, just start your javascript console so you can see the output. http://jsfiddle.net/ArE2x/