Update global variable with protractor on non-Angular page - angularjs

Right to it. I have a global variable which I would like to export, so I can use the value in following specs. But since protractor is not working synchronously, export happens before the variable gets updated to the right value.
The action is a click on button where player gets created and I need the username to be exported.
Console.log contains the right value and also export happens as it should, only thing, that it exports the hardcoded value or undefined if I set globalUsername = "";
Anyone can help on how to make this export sinchronized, so it will wait for all describes to finish up or the variable to get updated.
describe ("Quick add player", function() {
it ("New player is created and credentials are displayed", function() {
browser.ignoreSynchronization = true;
var playerData1 = playerData.getText().then(function(text) {
console.log("1: ", text);
return text.split("\n")[0];
//console.log(text.split(" ")[1]);
});
var globalUsername = "1234";
playerData1.then(function(text) {
globalUsername = text.split(" ")[1];
//expect(globalUsername).not.toEqual("");
console.log("*****************\n" + globalUsername);
});
});
});
module.exports = globalUsername;

Because you are using browser ignore synchronization, your tests will probably have nested then statements because you need to wait for promises to resolve. Instead of creating multiple then statements, you could put them into a single then statement. Also, you could tie your variable to the global namespace with global.username = username or add it to the global browser object with browser.username = username
describe ("Quick add player", function() {
it ("New player is created and credentials are displayed", function() {
browser.ignoreSynchronization = true;
global.username = "1234";
playerData.getText().then(function(text) {
console.log("1: ", text);
global.username = text.split("\n")[0].split(" ")[1];
console.log("*****************\n" + global.username);
});
});
});
// access using global.username tied to the global namespace
// instead of var username = require('thisfile');

So this solved my issue, where I export a string and not the variable....searched google with wrong tags.
This is placed outside the first describe at the bottom.
module.exports = {
exportUsername: function () {
return globalUsername;
}
};

Related

Mock document.activeElement in Jasmine test

I have the following function:
function focusIsNotInInput() {
// If the element currently in focus is of a certain type, then the key handler shouldn't run
var currentlyInFocus = $window.document.activeElement;
var blacklist = ['INPUT', 'TEXTAREA', 'BUTTON', 'SELECT', 'IFRAME', 'MD-OPTION'];
return !blacklist.some(function (nodeName) {
return nodeName === currentlyInFocus.nodeName;
});
}
And I need to mock that the element currently in focus is of one of the specified types, but can't get it to work.
I've tried injecting window, like this:
beforeEach(function() {
var $windowMock;
inject(function(_$window_) {
$windowMock = _$window_;
$windowMock.document.activeElement.nodeName = 'INPUT';
});
});
But when the code above runs, the active element is always still body. It's getting overwritten. I have also tried creating an element and setting focus on it:
var elementInFocus = $('<input>');
this.elem.append(elementInFocus);
elementInFocus.triggerHandler('focus');
elementInFocus.focus();
But it's the same, body is always in focus, what ever I do.
I had some trouble with this too, a possible solution (worked for me) is to add a spyOn(element, 'focus') -- here's a reference: How do I check if my element has been focussed in a unit test
My successful solution:
const htmlItem = fixture.nativeElement;
const searchBar = htmlItem.querySelector('.search-box');
let focusSpy = spyOn(searchBar, 'focus');
searchBar.focus();
expect(focusSpy).toHaveBeenCalled();

Is it possible to prevent changing of router by clicking back button?

I'am building single page using BackboneJS and I need to prevent router executing on back button in a browser. To be exact I need to show confirmation custom popup with the text "Do you really want exit room? [yes|no]". So if user clicks yes then default actions should happens but if no then user should stay in the current screen.
I use Backbone.router with pushState: true. Does Backbonejs provide something like before router event to be possible prevent router handling or how could I archive it?
I'm not sure if this is still an issue, but this is how I would get around it. It may not be the best way, but could be a step in the right direction.
Backbone.History.prototype.loadUrl = function (fragment, options) {
var result = true;
if (fragment === void (0) && options === void (0) && this.confirmationDisplay !== void(0))
{
result = confirm('Are you sure you want to leave this room?');
}
var opts = options;
fragment = Backbone.history.fragment = Backbone.history.getFragment(fragment);
if (result) {
this.confirmationDisplay = true;
return _.any(Backbone.history.handlers, function (handler) {
if (handler.route.test(fragment)) {
//We just pass in the options
handler.callback(fragment, opts);
return true;
}
});
}
return this;
}
Essentially checking if we have a fragment and options, if not, we can assume the app just started, or the user clicked the back button.
Backbone router has an execute method which is called for every route change, we can return false to prevent the current transition. The code will probably look like below :
With an asynchronous popup (untested code, but should work)
Backbone.Route.extend({
execute: function(callback,args){
if(this.lastRoute === 'room'){
showPopup().done(function(){
callback & callback.apply(this,args);
}).fail(function(){
Backbone.history.navigate('room/486',{trigger:false});
});
}else{
callback && callback.apply(this,args);
}
},
showPopup: function(){
var html = "<<div><p>Do you really want to exit</p><button id='yes'>Yes</button><button id='no'>No</button></div>"
var promise = $.Deferred();
$('body').append(html);
$(document).on('click','button#yes',function(){
promise.resolve();
});
$(document).on('click','button#no',function(){
promise.reject();
});
return promise;
}
});
With synchronous confirm popup
Backbone.Route.extend({
execute: function(callback,args){
if(this.lastRoute === 'room'){
var conf = confirm("Do you really want to exit the room ?");
if(!conf){
//Change the route back to room
Backbone.history.navigate('room/486',{trigger:false});
return false;
}
};
callback && callback.apply(this,args);
}
});
References:
http://backbonejs.org/#Router-execute

Protractor: functions overloading

I try to extend ElementFinder library. I wondering how I can require different methods with the same names?
I want to make something like:
// spec.js
var ef1 = require('./ef_extend1.js');
var ef2 = require('./ef_extend2.js');
expect(column_resizer.ef1.getWidth()).toEqual(18);
expect(column_resizer.ef2.getWidth()).toEqual(18);
Now I have an error:
TypeError: Cannot read property 'getWidth' of undefined
My required libraries:
// ef_extend1.js
var ElementFinder = $('').constructor;
ElementFinder.prototype.getWidth = function() {
return this.getSize().then(function(size) {
return size.width + 1;
});
};
And the second one:
// ef_extend2.js
var ElementFinder = $('').constructor;
ElementFinder.prototype.getWidth = function() {
return this.getSize().then(function(size) {
return size.width;
});
};
I guess you've used a solution from Protractor issue #1102, but now it can be accomplished a bit easier after PR#1633, because ElementFinder is now exposed in protractor global variable:
protractor.ElementFinder.prototype.getWidth = function () {
return this.getSize().then(function (size) {
return size.width;
});
};
expect($('body').getWidth()).toBe(100);
Update:
As I said in the comment, ElementFinder can only be extended again and again. If you already had a method getWidth, and you extend ElementFinder with one more getWidth implementation, then the first one will be overriden, there should not be any conflict. But you'll have to keep them in strict order depending on when do you want to use appropriate set of methods:
require('./ef_extend1.js');
expect(column_resizer.getWidth()).toEqual(18);
require('./ef_extend2.js');
expect(column_resizer.getWidth()).toEqual(18);
Actually I've came with some alternative approach, but I do not think it will be nice to use, but anyway. Here is a sample module with extension methods:
// ef_extend1.js
// shortcut
var EF = protractor.ElementFinder;
// holds methods you want to add to ElementFinder prototype
var extend = {
getWidth: function () {
return this.getSize().then(function (size) {
return size.width;
});
}
};
// will hold original ElementFinder methods, if they'll get overriden
// to be able to restore them back
var original = {};
// inject desired methods to prototype and also save original methods
function register() {
Object.keys(extend).forEach(function (name) {
original[name] = EF.prototype[name]; // save original method
EF.prototype[name] = extend[name]; // override
});
}
// remove injected methods and return back original ones
// to keep ElementFinder prototype clean after each execution
function unregister() {
Object.keys(original).forEach(function (name) {
if (typeof original[name] === 'undefined') {
// if there was not such a method in original object
// then get rid of meaningless property
delete EF.prototype[name];
} else {
// restore back original method
EF.prototype[name] = original[name];
}
});
original = {};
}
// pass a function, which will be executed with extended ElementFinder
function execute(callback) {
register();
callback();
unregister();
}
module.exports = execute;
And you will use them like that, being able to run protractor commands in "isolated" environments, where each of them has it's own set of methods for ElementFinder:
var ef1 = require('./ef_extend1.js');
var ef2 = require('./ef_extend2.js');
ef1(function () {
expect(column_resizer.getWidth()).toEqual(18);
});
ef2(function () {
expect(column_resizer.getWidth()).toEqual(18);
});
I'm not quire sure about it, maybe I am over-engineering here and there are solutions much easier than that.

Routing in SAPUI5: How to implement passing of URL? Model data not initialy loaded

My goal is to write a SAPUI5 Fiori app with routing support. One mail goal is to have passable URLs. For example in an E-Mail like "please approve this: link". The link is an URL matched by my rounting config, e.g.index.html#/applicants/8.
I use a typical sap.m.SplitApp kind of application. Clicking a list item in masterview changes the URL to index.html#/applicants/[id of entry in JSON]. I can click on the list, my defined routes are getting matched and the apps loads the (applicant) data as expected.
However, and here comes my question, this doeas not work when using an URL directly, say pasting [my url]/index.html#/applicants/8 into my browser. The app is launched but no detail data is loaded. I have to click on another list item again to get the data.
Actually, the controller is called when passing the URL, but it seems the model is not initiated and undefined. My JSON model is bound in the createContent function of my Component.js
// Update 2015-05-14
The problems seems to be around the getData() function. I have the model, it has the entries, but getData() returns undefined for the first time my app is loaded. I recently read getData() is deprecated. How should I improve my coding below?
// Component.js
ui5testing.Component.prototype.createContent = function(){
// create root view
var oView = sap.ui.view({
id : "app",
viewName : "ui5testing.view.Main",
type : "JS",
viewData : {
component : this
}
var oModel = new sap.ui.model.json.JSONModel("model/mock_applicants.json");
oView.setModel(oModel);
[...]
return oView;
});
// Master controller
handleApplicantSelect : function (evt) {
var oHashChanger = sap.ui.core.routing.HashChanger.getInstance();
var context = evt.getParameter("listItem").getBindingContext();
var path = context.getPath();
var model = this.getView().getModel();
var item = model.getProperty(path);
oHashChanger.setHash("applicants/" + item.id);
},
// Detail controller
onInit: function() {
this.router = sap.ui.core.UIComponent.getRouterFor(this);
this.router.attachRoutePatternMatched(this._handleRouteMatched, this);
},
_handleRouteMatched : function(evt){
var objectId = evt.getParameter("arguments").id;
var model = this.getView().getModel();
var data = model.getData()["applicants"];
var pathId;
if (data) {
for (var i = 0; data.length; i++) {
if ( objectId == data[i].id ) {
pathId = i;
break;
}
}
var sPath = "/applicants/" + pathId;
var context = new sap.ui.model.Context(model, sPath)
this.getView().setBindingContext(context);
}
},
As you've figured out that getData() returns undefined for the first time, which means the model data is still not yet loaded. So you can make use of attachRequestCompleted method of the model & fire an event from the component & listen to that event in the detail controller to ensure the routerPatternMatched() gets executed only after the data is loaded.
//Component.js
var oModel = new sap.ui.model.json.JSONModel("model/mock_applicants.json");
oModel.attachRequestCompleted(jQuery.proxy(function(){
this.fireEvent("MockDataLoaded"); // fireEvent through component
},this));
oView.setModel(oModel);
//Detail controller
onInit : function(){
this.router = sap.ui.core.UIComponent.getRouterFor(this);
var oComponent = this.getOwnerComponent();
oComponent.attachEvent("MockDataLoaded",jQuery.proxy(function(){
this.router.attachRoutePatternMatched(this._handleRouteMatched, this);
},this));
}
Or the simplest & but the dirty way would be to make an synchronous request instead of an async request to load data.
var oModel = new sap.ui.model.json.JSONModel();
oModel.loadData(""model/mock_applicants.json",{bAsync:false});
oView.setModel(oModel);

Storing constants in services in AngularJS?

Very begginer question, sorry about that!
I understand how to store constants in services with AngularJS, for example:
.value('baseUrl', 'http://127.0.0.1:8001/api/v1/')
But how can I create another constant that uses another one?
It seems there is no DI in values ?!?
For example, If I want to create a GetUsersUrl which is baseUrlstring + 'Users/' (concatenation)
I guess it's simple...but unable to find how to do it.
Thank you.
You can store values in services, configure it in the run phase, and then access the values later. For example:
.factory('UrlService', function() {
var UrlService = {};
UrlService.baseUrl = undefined; // you can set a default value
UrlService.getUsersUrl = function() {
if (UrlService.baseUrl === undefined) {
return undefined;
} else {
return UrlService.baseUrl + '/Users/';
}
};
return UrlService;
});
The run phase happens after config.
.run(function(UrlService) {
UrlService.baseUrl = 'localhost:8001/api/v1';
});
Then, in your controllers, etc. you can inject UrlService and do
UrlService.getUsersUrl()
Edit: Rewrote the answer.
Edit 2: Another approach.
It also appears to me that you only really need baseUrl to be a constant. So, you could do:
.value('baseUrl', 'localhost:1337')
.factory('urlService', ['baseUrl', function(baseUrl) {
return {
getUsersUrl: function() { return baseUrl + '/users/'; },
// OR
usersUrl: baseUrl + '/users/' // it can also be a primitive value
}
}]);
This approach works (and is more "the Angular way") if you do not need to actually configure the baseUrl. For example, if you can fill in the appropriate value of the baseUrl based on which environment (dev, production, etc) is running, you wouldn't need to configure it. Or, if the value is constant.

Resources