Stubbing byId() in SAPUI5-sinon - sapui5

I'm filddling with sinonjs in SAPUI5. But there are some things I can't get my head around.
QUnit.module("Validation of Betaalwijze", {
beforeEach : function () {
this.oMainViewController = new MainViewController();
this.oViewStub = new ManagedObject();
var data = {
IBANPrimair: "123",
IBANSecundair: "456",
Betaalwijze: ""
};
var oModel = new JSONModel(data);
var fakeBetaalwijzeField = new Input();
sinon.stub(this.oViewStub, "getModel").returns(oModel);
sinon.stub(this.oViewStub, "byId").returns(fakeBetaalwijzeField);
sinon.stub(this.oMainViewController, "getView").returns(this.oViewStub);
},
afterEach : function() {
this.oMainViewController.destroy();
this.oViewStub.destroy();
this.fakeBetaalwijzeField.destroy();
}
});
QUnit.test("Should set an ValueState Error", function (assert) {
// Arrange
//All preparation here above.
// Act
this.oMainViewController._validateInput();
// Assert
//TODO
});
The getModel-stub works nicely when I use a "sap/ui/base/ManagedObject" for the oViewStub. But the byId-stub causes the message "Attempted to wrap undefined property byId as function" in that case.
When I use a "sap/ui/core/mvc/View" for the oViewStub, the getModel-stub is not found. (But this gives an error in the beforeEach also: Cannot read property 'viewData' of undefined.)
What is the right way to stub the View and it's methods getModel() and byId()?

The answer is quiete simple: sap.ui.base.ManagedObject does not have a method byId. This is a method of sap.ui.core.mvc.View. Just create a View instead of a ManagedObject in beforeEach and you should be fine.
BR
Chris

Related

Optional routing parameters ar not passed on Prem

Hello,
we are facing an issue with passing optional parameters using routing.
Parameters are passed to another view when the app runs on BTP, but not on Prem.
The route is defined as follows:
{
name: "routname",
pattern: "thisisname/{mandaroty1},{mandatory2}/:?optional:",
target: ["targetName"]
}
This is how I navigate:
oRouter.navTo("routname", {
mandatory1: "test",
mandatory2: "test2",
"?optional": {
optional1: "value1",
optional2: "value2"
}
});
In target view:
in onInit:
var oRouter = this.getRouter();
oRouter.getRoute("shapeIT").attachPatternMatched(this._onRouteMatched, this);
in _onRouteMatched:
_onRouteMatched: function (oEvent) {
var oArgs = oEvent ? oEvent.getParameter("arguments") : null;
console.log(oArgs);
}
In console of the onPrem I see as follows:
{
mandatory1: "test",
mandatory2: "test2",
"?optional": undefined
}
On BTP I see all values.
What do I miss?
You are trying to pass an object in the optional parameter. That's cleaver but I believe the problem is because of that. I would recommend using a JSON.stringify() to convert your optional parameters to a string and convert the string back to a JS object by using JSON.parse() on your _onRouteMatched handler.

SAPUI5: getModel returns undefined if called within the same function of setModel

I'm trying to set a model and retrieving it from OData after pressing a certain button.
The problem is when I call getModel right after setting the model, it returns undefined.
However, if I call getModel from another function (after model being stetted from other functions), it returns the desired output.
Code for reference:
onPressButton1: function(){
var vEntityURL = "/CustomerSet(ID='000')";
var sServiceUrl = "/Customers_SRV/";
var oServiceModel = new sap.ui.model.odata.ODataModel(sServiceUrl, true);
var oJsonModel = new sap.ui.model.json.JSONModel();
oServiceModel.read(vEntityURL, {
success: function(oData) {
oJsonModel.setData(oData);
}
});
this.getView().setModel(oJsonModel, "Customers");
var oCustomer = this.getView().getModel("Customers");
console.log(oCustomer.getProperty("/Name"));
}
The above returns undefined in the console.
However, it works if I press another button with the following function.
onPressButton2: function(){
var oCustomer = this.getView().getModel("Customers");
console.log(oCustomer.getProperty("/Name"));
}
This is not a sapui5 problem, it is the common behaviour of asynchronous code: you can be sure to have your data only in the success callback of the read method.
Move the last three lines of code inside the success function and you're done :-)

How to pass a key field as variable instead of hard-coded key value to OData operation?

I am calling the GetEntity OData read method from the SAP UI5 view controller and passing a key value in the request URL. I am getting the proper response from the back-end when I hardcode the key value.
However, when I try to pass the key value dynamically in a variable by appending it to the URL, it doesn't work. I get the following error
HTTP request failed 404
In below code, sGrant is the variable and it doesn't work. But if I replace the variable name with its value hard-coded in below code, for example, in the read method like this: "/GrantMasterSet('TY560003')", then it works:
var sGrant = this.byId("grantNbr").getValue();
var oMod = this.getOwnerComponent().getModel();
oMod.read("/GrantMasterSet('sGrant')", {
success: function(oData) {
var oJsonModel = new JSONModel();
oJsonModel.setData(oData);
this.getView().setModel(oJsonModel);
}.bind(this),
error: function(oError) {
MessageToast.show("Read Failed");
}
});
UI5 has a method to generate the right URI for you, no matter what is the data type of the key of your entity type.
The method is createKey of the sap.ui.model.odata.v2.ODataModel class. See its documentation
Inside your controller, use the following source code.
onInit: function () {
var oRouter = this.getOwnerComponent().getRouter();
oRouter.getRoute("routeName").attachPatternMatched( this.onPatternMatched , this );
},
onPatternMatched: function(oEvent){
var oParameters = oEvent.getParameters();
var oArguments = oParameters.arguments; // is not a function - without ()
var sKey = oArguments.id; // route parameter passed when using navTo
var oDataModel = this.getView().getModel(); // v2.ODataModel
oDataModel.metadataLoaded().then(function() {
var sPath = oDataModel.createKey("EntitySet", { Key: sKey });
this.getView().bindElement("/" + sPath);
}.bind(this)
);
}
Usually this is necessary in details pages, in order to apply element binding to a page. As the createKey method relies on the $metadata of your service, you must make sure that it is already loaded in your app. This can be achieved by using method metadataLoaded, provided in the snippet as well.
You should concatenate the variable to the rest of the string, like this:
oMod.read("/GrantMasterSet('" + sGrant + "')", {
Or, you can use a template literal, which comes down to the same thing (notice the backtics):
oMod.read(`/GrantMasterSet('${sGrant}')`, {
You should escape 'sGrant' so it can be evaluated.
It should be something like that :
var sGrant = this.byId("grantNbr").getValue();
var oMod = this.getOwnerComponent().getModel();
oMod.read("/GrantMasterSet("+sGrant+")", {
success: function(oData) {
var oJsonModel = new sap.ui.model.json.JSONModel();
oJsonModel.setData(oData);
this.getView().setModel(oJsonModel);
}.bind(this),
error: function(oError) {
MessageToast.show("Read Failed");
}
});

What's the correct Protractor's syntax for Page Objects?

I've come across different types of syntax for Protractor's Page Objects and I was wondering, what's their background and which way is suggested.
This is the official PageObject syntax from Protractor's tutorial. I like it the most, because it's clear and readable:
use strict;
var AngularHomepage = function() {
var nameInput = element(by.model('yourName'));
var greeting = element(by.binding('yourName'));
this.get = function() {
browser.get('http://www.angularjs.org');
};
this.setName = function(name) {
nameInput.sendKeys(name);
};
this.getGreeting = function() {
return greeting.getText();
};
};
module.exports = AngularHomepage;
However, I've also found this kind:
'use strict';
var AngularPage = function () {
browser.get('http://www.angularjs.org');
};
AngularPage.prototype = Object.create({}, {
todoText: { get: function () { return element(by.model('todoText')); }},
addButton: { get: function () { return element(by.css('[value="add"]')); }},
yourName: { get: function () { return element(by.model('yourName')); }},
greeting: { get: function () { return element(by.binding('yourName')).getText(); }},
todoList: { get: function () { return element.all(by.repeater('todo in todos')); }},
typeName: { value: function (keys) { return this.yourName.sendKeys(keys); }} ,
todoAt: { value: function (idx) { return this.todoList.get(idx).getText(); }},
addTodo: { value: function (todo) {
this.todoText.sendKeys(todo);
this.addButton.click();
}}
});
module.exports = AngularPage;
What are the pros/cons of those two approaches (apart from readability)? Is the second one up-to-date? I've seen that WebdriverIO uses that format.
I've also heard from one guy on Gitter that the first entry is inefficient. Can someone explain to me why?
Page Object Model framework becomes popular mainly because of:
Less code duplicate
Easy to maintain for long
High readability
So, generally we develop test framework(pom) for our convenience based on testing scope and needs by following suitable framework(pom) patterns. There are NO such rules which says that, strictly we should follow any framework.
NOTE: Framework is, to make our task easy, result oriented and effective
In your case, 1st one looks good and easy. And it does not leads to confusion or conflict while in maintenance phase of it.
Example: 1st case-> element locator's declaration happens at top of each page. It would be easy to change in case any element locator changed in future.
Whereas in 2nd case, locators declared in block level(scatter across the page). It would be a time taking process to identify and change the locators if required in future.
So, Choose which one you feel comfortable based on above points.
I prefer to use ES6 class syntax (http://es6-features.org/#ClassDefinition). Here, i prepared some simple example how i work with page objects using ES6 classes and some helpful tricks.
var Page = require('../Page')
var Fragment = require('../Fragment')
class LoginPage extends Page {
constructor() {
super('/login');
this.emailField = $('input.email');
this.passwordField = $('input.password');
this.submitButton = $('button.login');
this.restorePasswordButton = $('button.restore');
}
login(username, password) {
this.email.sendKeys(username);
this.passwordField.sendKeys(password);
this.submit.click();
}
restorePassword(email) {
this.restorePasswordButton.click();
new RestorePasswordModalWindow().submitEmail(email);
}
}
class RestorePasswordModalWindow extends Fragment {
constructor() {
//Passing element that will be used as this.fragment;
super($('div.modal'));
}
submitEmail(email) {
//This how you can use methods from super class, just example - it is not perfect.
this.waitUntilAppear(2000, 'Popup should appear before manipulating');
//I love to use fragments, because they provides small and reusable parts of page.
this.fragment.$('input.email').sendKeys(email);
this.fragment.$('button.submit')click();
this.waitUntilDisappear(2000, 'Popup should disappear before manipulating');
}
}
module.exports = LoginPage;
// Page.js
class Page {
constructor(url){
//this will be part of page to add to base URL.
this.url = url;
}
open() {
//getting baseURL from params object in config.
browser.get(browser.params.baseURL + this.url);
return this; // this will allow chaining methods.
}
}
module.exports = Page;
// Fragment.js
class Fragment {
constructor(fragment) {
this.fragment = fragment;
}
//Example of some general methods for all fragments. Notice that default method parameters will work only in node.js 6.x
waitUntilAppear(timeout=5000, message) {
browser.wait(this.EC.visibilityOf(this.fragment), timeout, message);
}
waitUntilDisappear(timeout=5000, message) {
browser.wait(this.EC.invisibilityOf(this.fragment), timeout, message);
}
}
module.exports = Fragment;
// Then in your test:
let loginPage = new LoginPage().open(); //chaining in action - getting LoginPage instance in return.
loginPage.restorePassword('batman#gmail.com'); // all logic is hidden in Fragment object
loginPage.login('superman#gmail.com')

jQuery 'this' in callback

I have the filepicker.io dialog coming up fine but on the success call back I seem to lose my 'this' context.
So my code is like this
var fileProcess ={
saveFileInfo: function () {
.....process info here
},
selectFile: function () {
filepicker.pick({ mimetype: 'image/*' }, function (Blob) {
this.saveFileInfo();
});
}
}
So is there something like "context: this" like I can do in an ajax call?
Try creating a new variable named self or me set to the current value of this OUTSIDE your callback. Then, you use closures, so you can access this from your callback through me or self.
Like:
this.mesg = "Hello";
var self = this;
function handler() {
alert(self.mesg);
}
later after a context switch...
handler(); // Will alert 'Hello'
EDIT: Oh nuts... Just realized that won't work... Try:
function handler() {
alert(this.msg);
}
var newHandler = handler.bind(this);
Later...
newHandler();
Function.prototype.bind() takes an object, and returns a function. Whenever the returned function is called, the object passed to bind() is used as this.