Resolving a Promise in Protractor - protractor

Strange issue I don't understand yet when trying to resolve (fulfill) my promise in Protractor.
Something is very wrong with the line deferred.fulfill(rowData);, as it's NOT returning the row data as I would expect.
In other words, rowData.count() in the lower function is fine, but when returned row.count() is failing.
this.gridFunction = function (summaryData){
var rowData = getGridRowByText(gridRows, name, text);
rowData.then(function (row) {
// ** THROWS ERROR ** TypeError: row.count is not a function
expect(row.count()).toEqual(3);
row.map(function (cell) {
// iterate cell contents, compare with "summaryData"
});
});
}
function getGridRowByText(gridRows, grid, text) {
var deferred = protractor.promise.defer();
var parentId = getParId();
parentId.getAttribute("id").then(function (parentId) {
// i.e. jquery $('.grid-wrapper.fluid-wrapper #rowId_21')
var sel = '.grid-wrapper.fluid-wrapper #' + parentId;
var rowData = element(by.css(sel)).all(by.repeater('column in vm.sourceData.columns'));
// EXPECT SUCCESSFULL !!!
expect(rowData.count()).toEqual(19);
deferred.fulfill(rowData);
});
return deferred.promise;
};
Main question: am I NOT properly returning the fulfilled promise with the rowData object ?
* UPDATE *
My final solution :
It doesn't actually solve my original problem of working with the Protractor Promise, but rather just a redesign of the logic.
this.gridFunction = function (targetRowText){
var result = gridRows.all(by.cssContainingText('span', targetRowText)).first();
var parentId = result.all(by.xpath("./ancestor::div[starts-with(#id, 'rowId')]"));
parentId.getAttribute("id").then(function (parentId) {
console.log(' (ROW-ID: ', parentId);
// further iterations here...
}
}
thank you,
Bob

You don't actually need a "deferred" object here. Just return the promise from the function:
function getGridRowByText(gridRows, grid, text) {
var parentId = getParId();
return parentId.getAttribute("id").then(function (parentId) {
var sel = '.grid-wrapper.fluid-wrapper #' + parentId;
return element(by.css(sel)).all(by.repeater('column in vm.sourceData.columns'));
});
};
Usage:
var rowData = getGridRowByText(gridRows, name, text);
expect(rowData.count()).toEqual(3);
Or, if further processing needed in the getgridRowByText() function:
function getGridRowByText(gridRows, grid, text) {
var parentId = getParId();
return parentId.getAttribute("id").then(function (parentId) {
var sel = '.grid-wrapper.fluid-wrapper #' + parentId;
var rowData = element(by.css(sel)).all(by.repeater('column in vm.sourceData.columns'));
// further processing here
expect(rowData.count()).toEqual(19);
return rowData;
});
};

Related

Resolving Promise Angular 2

I have the following problem.
In a function I have a promise as a return type. This function is in the class Hierarchy.
updateNodeValues(entity: String, data: {}): Promise<any>{
let jsonBody = JSON.stringify(data);
let url = environment.endpointCore + '/api/' + entity + '/' + data['id'];
return this.http.put(url, jsonBody, this.options)
.toPromise()
.then(response => {
return response;
})
.catch(this.handleError);
}
This function is in class node.
onSubmit(): void{
var currentForm = this.form.value;
var entityName = this.inflection.classify(this.node.type).toLowerCase();
var requiredData = {};
for(var i = 0; i < this.formItems.length; i++){
this.formItems[i].value = currentForm[Object.keys(currentForm)[i]];
}
for(var i=0; i<this.formItems.length; i++){
requiredData[this.globalService.camelize(this.formItems[i].label)] = this.formItems[i].value
}
Promise.resolve(this.hierarchyService.updateNodeValues(entityName, requiredData)).then(response => {
alert(response.ok);
if(response.ok){
this.globalService.showSuccessMessage('Values updated');
this.refreshGui(requiredData);
}
});
this.editMode = false;
}
The problem is that when i try to resolve promise and invoke this.refreshGui(requireddata) nothing is happening. I have read about how the fat arrow is preserving the 'context' of this, and I do not understand why invoking this method is not doing anything, while invoking successMessage produces expected outcome.
The method that I am invoking looks like this, and it is also in the class node.
private refreshGui(data: {}){
this._node.data = data;
this.objectProperties = new Array();
this.nodeChildren = new Array();
for (var property in data) {
var propertyValue = data[property];
if (propertyValue instanceof Array) {
this.nodeChildren.push({label: property, value: "total: ".concat(propertyValue.length.toString())});
} else {
this.objectProperties.push({label: property, value: propertyValue});
}
}
}
The solution that I found to be working was to implement custom event. The problem was that within the async callback resolution, the context of what this is would "get lost". The fat arrow enabled me to invoke class method with this, but the properties within the would be "lost". Because of this reason I have took the logic from the method, and put it in the callback part and set expected and needed results in some variable. This variable was passed to my custom event and set to class variable in the custom event handler appropriately.

TYpescript : Static methods on Function as class

I have a fn that inherit an existing fn ( take Angular1 $q for example )
//$q original behavior
var defer = $q.defer();
defer.promise.then(function(result){})
//or
$q( (resolve, reject) => {
//promise execution here
}).then(function(result){});
If I want to decorate it, I would do :
var Qdecorator = function($delegate) {
var Q = function(resolver:any): any {
//do some extra stuff here
return $delegate.apply($delegate, arguments);
}
//Assign the static methods here:
Q.defer = function() {
//do some stuff
return $delegate.defer.apply($delegate, []);
}
//same goes for race, when, resole reject and so on
return Q;
}
Problem is that typescript complains about
Property defer, race, when, resolve, etc... does not exist on type '(resolver: any) => any'
I tried to use the IQService, and IPromise with no luck, btu I'd like to raise a more global question :
How do I define late static methods on function() that return an object without using new
I am copying pasting the answer to my question from this link:
https://www.typescriptlang.org/docs/handbook/interfaces.html
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

NativeScript - how can I filter an observable array with SearchBar?

Hi I'm trying to filter an observable array of data fetched via a HTTP request on keypress of the SearchBar.
I managed to get the SearchBar property change to work but I can't seem to figure out what I'm doing wrong in the filtering logic.
Ideally I want to update the list as I type in the search term in the SearchBar. I've searched the API on the Telerik site, there wasn't really any examples I could find.
XML
<Page loaded="pageLoaded">
<ActivityIndicator busy="{{ isLoading }}" />
<ActionBar title="People">
</ActionBar>
<GridLayout>
<StackLayout>
<SearchBar id="searchBar" hint="Search for someone"></SearchBar>
<ListView items="{{ peopleList }}" itemTap="showDetail">
<ListView.itemTemplate>
<StackLayout>
<Label text="{{ fullName }}" horiztonalAlignment="left" verticalAlignment="center"></Label>
<Label text="{{ company }}" class="info"></Label>
</StackLayout>
</ListView.itemTemplate>
</ListView>
</StackLayout>
</GridLayout>
</Page>
JS
var frames = require("ui/frame");
var Observable = require("data/observable").Observable;
var PeopleListViewModel = require("../../shared/people-viewModel");
var activityIndicatorModule = require("ui/activity-indicator");
var page;
var userkey;
var peopleList = new PeopleListViewModel([]);
var pageData = new Observable({ peopleList: peopleList });
exports.pageLoaded = function(args) {
page = args.object;
page.bindingContext = pageData;
userkey = userkey || page.navigationContext.userkey;
peopleList.load(userkey); // fetch data from the backend
var searchBar = page.getViewById("searchBar");
searchBar.on("propertyChange", function (args) {
var searchText = args.object.text;
if (searchText === "") {
// NOT SURE WHAT TO DO HERE.
} else {
peopleList.filter(function (element, index, array) {
// DOESN"T WORK PROPERLY
console.log("element: ", JSON.stringify(element));
return element.fullName == searchText;
});
console.log("Text types: ", searchText);
}
});
};
exports.showDetail = function(args) {
var person = peopleList.getItem(args.index);
var navigateEntry = {
moduleName: "views/people/people-detail",
context: { person: person },
animated: false
};
frames.topmost().navigate(navigateEntry);
};
PeopleListViewModel.js
var config = require("./config");
var fetchModule = require("fetch");
var ObservableArray = require("data/observable-array").ObservableArray;
function PeopleListViewModel(people) {
var viewModel = new ObservableArray(people);
viewModel.load = function (userKey) {
return fetchModule.fetch(config.baseUrl + "/api/people/all/" + userKey)
.then(function (response) {
return response.json();
})
.then(function (data) {
data.forEach(function (person) {
viewModel.push(person);
});
}, function (error) {
console.log("Error: ", error);
});
};
viewModel.empty = function () {
while (viewModel.length) {
viewModel.pop();
}
};
return viewModel;
}
function handleErrors(response) {
if (!response.ok) {
console.log("Error occurred");
}
}
module.exports = PeopleListViewModel;
Updated people-list
var frames = require("ui/frame");
var Observable = require("data/observable").Observable;
var ObservableArray = require("data/observable-array").ObservableArray;
var PeopleListViewModel = require("../../shared/people-viewModel");
var activityIndicatorModule = require("ui/activity-indicator");
var page;
var userkey;
var peopleList = new PeopleListViewModel([]);
var pageData = new Observable({ peopleList: peopleList });
var resultList = new ObservableArray([]);
exports.pageLoaded = function(args) {
page = args.object;
page.bindingContext = pageData;
userkey = userkey || page.navigationContext.userkey;
peopleList.load(userkey);
var searchBar = page.getViewById("searchBar");
searchBar.on("propertyChange", function (args) {
var searchText = args.object.text;
if (searchText === "") {
} else {
while (resultList.length > 0) {
resultList.pop();
}
peopleList.forEach(function (element) {
if (element.fullName === searchText) {
resultList.push(element);
}
});
}
});
};
I had the same issue. If you want to filter your data after every character has changed in search-bar you can try my solution.
Definitions
My playerList is your peopleList. This is the data from view-model.
resultList is an array where the data will be pushed.
var observableArrayModule = require("data/observable-array").ObservableArray;
var playerList = new PlayerListViewModel([]);
var resultList = new observableArrayModule([]);
var pageData = new observableModule.Observable({
resultList: resultList,
player: ""
});
Inside expors.loaded()
page = args.object;
searchBar = page.getViewById("search-bar");
page.bindingContext = pageData;
Load Initial Data - inside expors.loaded()
We are loading initial data when user navigates to the screen for the first time. We are also pushing the same data to resultList since we are using {{resultList}} in xml. You can add loadingIndicator while the list is populated.
playerList
.load()
.then(function() {
setTimeout(function() {
playerList.forEach(function (element) {
pageData.resultList.push(element);
});
}, 1000);
})
.catch(function(error) {
dialogsModule.alert({
message: "An error occurred while loading players.",
okButtonText: "OK"
});
});
Clear autofocus - inside expors.loaded()
This is to prevent keyboard from opening on initial screen navigation.
if (searchBar.ios) {
searchBar.ios.endEditing(true);
} else if (searchBar.android) {
searchBar.android.clearFocus();
}
Search data when character has changed - inside expors.loaded()
I am calling filter functionality. Lodash _.debounce function is used to delay looping through resultList array. Without it, the app would loop every time letter is typed. Now we are waiting for user to stop typing to start looping.
searchBar.on('propertyChange', _.debounce(searchList, 500));
searchList Function
This is the actual loop. You can change element.name for your needs.
function searchList(args) {
var searchText = args.object.text;
while(resultList.length > 0) {
resultList.pop();
}
playerList.forEach(function (element) {
if (element.name.toLowerCase().indexOf(searchText) >= 0) {
resultList.push(element);
}
});
}
Hide keyboard if search-bar is cleared - inside exports.loaded()
And finally we want to hide the keyboard if user clears the search-bar.
searchBar.on(searchBarModule.SearchBar.clearEvent, function (args) {
setTimeout(function() {
searchBar.dismissSoftInput();
}, 10);
});
PS
You probably solved your issue, but this could help someone else in the future.
Okay so your problem is a Javascript problem than a NativeScript problem. For the sake of this problem, think of observable arrays as just your ordinary arrays.
In your JS you're creating a new PeopleListViewModel which you're then attaching to the bindingContext via the pageData object. So far so good. Then you're calling the load method on the PeopleListViewModel (It returns a promise which you're not really doing anything with but for this specific problem it doesn't matter).
However, when text is inputed you're not really doing anything. This is your code:
peopleList.filter(function (element, index, array) {
// DOESN"T WORK PROPERLY
console.log("element: ", JSON.stringify(element));
return element.fullName == searchText;
});
peopleList is an instance of PeopleListViewModel which returns an ObservableArray. The ObservableArray does indeed have a method called filter (which works just like filter of a regular array. Check out the NativeScript documentation and Javascript documentation of filter).
What you need to understand here is that filter returns a new array with the filtered results. Doing peopleList.filter() will send that new array into empty space. You want to var yourNewFilteredArray = peopleList.filter(). But you don't really want to redefine the array bound to the binding context, you want to modify the content of it.
Here's an example of how you could do that:
/*
* Attach a new obsersable array to the binding context.
* you can prepopulate it with the data from the
* PeopleListViewModel if you want to
*/
var resultList = new ObservableArray([]);
var pageData = new Observable({ resultList: resultList });
/*
* Then on search/filter you want to modify this new
* array. Here I first remove every item in it and then
* push matching items to it.
*/
searchBar.on("propertyChange", function (args) {
var searchText = args.object.text;
// ...
while(resultList.length > 0) {
resultList.pop();
}
peopleList.forEach(function (element) {
if (element.fullName === searchText) {
resultList.push(element);
}
});
});

Cannot call method 'push' of undefined

I had this working in a fiddle just fine (which I can't find now), but when I moved it to VS2012 I'm getting the error "Cannot call method 'push' of undefined'.
In the code below, the createItemDiv() function works great and creates the UI elements. It's the line before that, showItem() which calls this.visibleItem.push() that is throwing the error. I had a problem with this in the fiddle originally and added the "this" to fix it. If I remove "this" as it is now, I get "visibleItem is not defined".
viewmodel.js
var dummyResults = [
{ //sample data is here }
]
var dummyItems = [
]
function VisibleItem(data) {
var self = this;
this.name = ko.observable(data.name);
this.type = ko.observable(data.type);
this.description = ko.observable("");
}
function SearchResult(data) {
var self = this;
this.name = ko.observable(data.name);
this.type = ko.observable(data.type);
}
var viewModel = {
searchResult: ko.observableArray(ko.utils.arrayMap(dummyResults, function (item) {
return new SearchResult(item);
})),
visibleItem: ko.observableArray(ko.utils.arrayMap(dummyItems, function (item) {
return new VisibleItem(item);
})),
showItem: function (item) {
this.visibleItem.push(item);
}
};
ko.applyBindings(viewModel);
site.js
$(document).on('click', '.result', function () {
var item = ko.dataFor(this);
viewModel.showItem(item); //add item to "visibleItems" viewmodel for management
createItemDiv(item); //ui function to show item on screen
});
try this :
var viewModel = function() {
var self = this;
self.searchResult = ko.observableArray(ko.utils.arrayMap(dummyResults, function (item) {
return new SearchResult(item);
})),
self.visibleItem = ko.observableArray(ko.utils.arrayMap(dummyItems, function (item) {
return new VisibleItem(item);
})),
self.showItem = function (item) {
self.visibleItem.push(item);
}
};
I just realized I had an old binding in my html still... will vote to close.

node.js / postgres... asynchronous problem?

I have a list of types: ["type1", "type2, "type3"], on which I loop. For each type:
- create / execute a query to get the items of a the current type
- run another function when the query if done ( on('end') ).
for ( var i=0; i<types.length; i++ ){
type = types[i];
value = "test";
...
// ADD ITEM: QUERY
var query = client.query(...); // QUERY ITEMS OF A THE CURRENT TYPE
// ADD ITEM: ERROR CHECKING
query.on("error", function (err) {
...
});
with ({ t: type, v: value }) { // I HAD TO DO THAT SO THAT ALL THE TYPES ARE TAKEN INTO ACCOUNT
query.on('end', function() {
my_function(t, v);
});
}
}
my_function is like:
function my_function(type, value){
console.log(type + ',' + value); // CORRECT (I CAN SEE ALL THE TYPES BEEING LISTED)
// QUERY OTHER STUFF BASED ON THE VALUE OF "type"
var query = client.query(...);
// MAIN STUFF
query.on('row', function(row){
console.log(type + ',' + value); // THIS DOES NOT WORK ANYMORE... ONLY THE LAST TYPE IS TAKEN INTO ACCOUNT WHERE I EXPECT TO GET THIS MESSAGE FOR EACH TYPES.
...
}
// FINALIZE
query.on('end', function(){
...
}
}
I guess this is linked to asynchronous process... but cannot figure out where the error is.
UPDATE
I have updated my loop so it looks like:
for ( var i=0; i<types.length; i++ ){
type = types[i];
value = "test";
...
// ADD ITEM: QUERY
var query = client.query(...); // QUERY ITEMS OF A THE CURRENT TYPE
// ADD ITEM: ERROR CHECKING
query.on("error", function (err) {
...
});
// MAIN STUFF GOES HERE
(function(t, v) { query.on("end", function() {
console.log(t + ',' + v); // OK, I CAN SEE THIS DISPLAYED FOR EACH TYPE
my_function(t, v);
}); })(type, value);
}
I modified my_function so it looks like:
function my_function(type, value){
console.log(type + ',' + value); // OK, I CAN SEE THIS DISPLAYED FOR EACH TYPE
// QUERY OTHER STUFF BASED ON THE VALUE OF "type"
var query = client.query(...);
// MAIN STUFF
(function(t, v) {
query.on("row", function(row) {
console.log('TEST:' + t + ',' + v); // KO, I CAN ONLY SEE THIS DISPLAYED FOR ONE TYPE
}); })(type, value);
// FINALIZE
query.on('end', function(){
...
}
}
with ({ t: type, v: value }) { // I HAD TO DO THAT SO THAT ALL THE TYPES ARE TAKEN INTO ACCOUNT
query.on('end', function() {
my_function(t, v);
});
}
Is broken. What you want is this
query.on("end", my_function.bind(null, type, value));
Function.prototype.bind allows you to bind parameters to a function.
Never use with. An alternative that also works would be :
(function(t, v) {
query.on("end", function() { my_function(t, v); });
})(type, value);