custom assertion to compare arrays in Nightwatch.js does not work - javascript

I use nightwatch-cucumber that based on Nightwatch.js as testing framework to implement my automated end2end tests. So, I'm still new to 'JavaScript' and 'node.js'. Currently, I want to create a custom assertion in Nightwatch, but I get an error while test execution and I don't know what I'm doing wrong.
I get the following error:
Testing if arrays are equal. - Expected "true" but got: "[object Object]"
The assertion should compare different arrays in a given array, if they inherit equal values. So the array to give as parameter should like this var myArray[[1,2,3], [3,2,1]]. The assertion should compare all arrays in a given array with each other.
Here is my custom assertion:
var equals = require('array-equal');
var util = require('util');
exports.assertion = function(array, msg=null) {
this.message = msg || util.format('Testing if arrays are equal.');
this.expected = true;
this.pass = function(value) {
return value === this.expected;
};
this.value = function(result) {
return result.value;
};
this.command = function(callback) {
let params = [array];
let execute = function(array) {
for (var i = 0; i < array.length; i++) {
for (var k = i+1; k < array.length; k++) {
array[i].sort();
array[k].sort();
if (equals(array[i], array[k]) === false) {
return false;
}
}
}
return true;
};
let execcallback = result => {
if (callback) {
return callback.call(this, result);
}
};
return this.api.execute(execute, params, execcallback);
};
};
I use the PageObject pattern to write my tests.
Here is my code for executing my assertion:
module.exports = {
elements: {},
commands: [{
compareArrays() {
var list = [[1,2,3],[3,2,1]];
//I expect a passed assertion because the values in both arrays are equal
this.assert.arraysEqual(list);
return this.api;
}
}]
};

What are you doing in the loops?
Why don't place just
array[i].sort();
array[k].sort();
if (equals(array[i], array[k]) === false) {
return false;
}

Related

How to make this removeMiddle test pass when just return 1 value

I am trying to create a function to make this test pass
Here is my following code for my test cases:
var removeMiddle = require("./remove-middle");
test("Remove middle", function () {
var words = ["mouse", "giraffe", "queen", "window", "bottle"];
var expectedWords = ["mouse", "giraffe", "window", "bottle"];
var expectedOutput = ["queen"];
var output = removeMiddle(words);
expect(output).toEqual(expectedOutput);
expect(words).toEqual(expectedWords);
});
Here is my function that I created:
function removeMiddle(words) {
let index = [];
words.splice(2, 1);
for (let i = 0; i < words.length; i++) {
if (words[i] === "queen") {
index.push(words[i]);
}
}
return words;
}
module.exports = removeMiddle;
Right now my function just passed the second test case which is: expect(words).toEqual(expectedWords);
Firstly, you expect your output to be the word(s) removed. But you are returning the words array from removeWords. You probably can return index.
Also, once you have spliced your words array, queen will no more be a part of the array. So your for loop does nothing and index returns an empty array.
You can modify your code like this to get your expected output:
function removeMiddle(words) {
let index = [words[2]];
words.splice(2, 1);
return index;
}
But right now your code is a little hard coded, works only for a fixed length of array(5). You can do the following if you want a more generic solution.
function removeMiddle(words) {
if(words.length == 0) return [];
let index = [];
let middleElements = [];
if(words.length % 2 == 0){
console.log(words[Math.floor(words.length/2)]);
middleElements.push(words[Math.floor(words.length/2)]);
words.splice(words.length/2,1);
console.log(words[Math.floor(words.length/2)]);
middleElements.push(words[Math.floor(words.length/2)]);
words.splice(words.length/2,1);
}
else{
console.log(words.length/2);
middleElements.push(words[Math.floor(words.length/2)]);
words.splice(words.length/2,1);
}
return middleElements;
}

Removing outer array object if an inner array meets a condition

I am dealing with a fairly complex object. It contains 2 arrays, which contain 3 arrays each of objects:
I'm trying to delete one of the history: Array[2] if one of the objects in it has username: null.
var resultsArray = result.history;
var arrayCounter = 0;
resultsArray.forEach(function(item) {
item.forEach(function(innerItem) {
if (innerItem.username == null) {
resultsArray.splice(arrayCounter,1);
};
});
arrayCounter++;
});
Looking through answers it's recommended to do something like:
resultsArray.splice(arrayCounter,1);
This isn't working in this situation because more than one of the objects could have username == null and in that case it will delete multiple history objects, not just the one that I want.
How do I remove only the one specific history array index if username == null?
splice is evil. I think using immutable array methods like filter might be easier to reason about:
x.history =
x.history.filter(function (h) {
return !h.some(function (item) {
return item.username === null
})
})
Go through all the histories, and do not include them in the filter if they have a username that is null.
My understanding was that you only want to delete the first outer array that has an inner array that has an object with a null username. Heres one solution closest to your current form:
var resultsArray = result.history;
var arrayCounter = 0;
var foundFirstMatch = false;
resultsArray.forEach(function(item) {
if (!foundFirstMatch) {
item.forEach(function(innerItem) {
if (innerItem.username == null && !foundFirstMatch) {
foundFirstMatch = true;
};
});
arrayCounter++;
}
});
if (foundFirstMatch > 0)
resultsArray.splice(arrayCounter, 1);
Other syntax:
var resultsArray = result.history;
var outerNdx;
var innerNdx;
var foundMatch = false;
for (outerNdx = 0; !foundMatch && outerNdx < resultsArray.length; outerNdx++) {
for (innerNdx = 0; !foundMatch && innerNdx < resultsArray[outerNdx].length; innerNdx++) {
if (resultsArray[outerNdx][innerNdx].username == null) {
foundMatch = true;
}
}
}
if (foundMatch)
resultsArray.splice(outerNdx, 1);
Update - here's how I'd do it now, without lodash:
thing.history.forEach((arr, i) => {
thing.history[i] = arr.filter( (x) => x.username !== null );
});
Previous answer:
I'd use lodash like this:
_.each(thing.history, function(array, k){
thing.history[k] = _.filter(array, function(v){
return v.username !== null;
})
});
Here's a jsfiddle:
https://jsfiddle.net/mckinleymedia/n4sjjkwn/2/
You should write something like this:
var resultsArray = result.history.filter(function(item){
return !item.some(function(inner){ return inner.username==null; });
});
The foreach loop cant break in this way but a regular for loop can. This is working:
result.history.forEach(function(item) {
loop2:
for (var i = 0; i < item.length; i++) {
var innerItem = item[i];
console.log(innerItem);
break loop2;
}
});

Get objects position in array

I have an array players[]
function that gets certain object from such array by looking up its gameSocketId value and returns that object
getUserInfo : function(user)
{
var userInfo = Game.players.filter(function(e) {
return e.gameSocketId === user;
}).pop();
return userInfo;
}
so I store it in a variable like var user = getUserInfo(userId) How can I than find out what is the position of user in array of players[] knowing all info about it?
Use .findIndex:
getUserInfo : function(user)
{
var userInfoIndex = Game.players.findIndex(function(e) {
return e.gameSocketId === user;
});
return userInfoIndex;
}
Note that .findIndex, while fully specified is not included in most JS engines by default yet - there is a polyfill on mdn:
if (!Array.prototype.findIndex) {
Array.prototype.findIndex = function(predicate) {
if (this == null) {
throw new TypeError('Array.prototype.findIndex called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return i;
}
}
return -1;
};
}
This polyfill works on ES3 and ES5 browsers just fine :)
Of course, one can also use a normal for loop to do this which works all the way through ES1 - but then you don't get the fun syntax that conveys intent pretty clearly:
getUserInfo : function(user) {
for(var i = 0; i < Game.players.length; i++){
if(Game.players[i].gameSocketId === user) return i;
}
return -1;
}
We don't always have to be clever :)
Of course, we can also always be inefficient and just call .indexOf after obtaining the item using your original method.
The second param of Array.filter is the index for the current item. The below will still return you the userInfo that you originally specified plus you can use the index for whatever you want.
getUserInfo : function(user)
{
var playerIndex;
var userInfo = Game.players.filter(function(e, index) {
if (e.gameSocketId === user) {
playerIndex = index;
return true;
}
}).pop();
console.log(Game.players[playerIndex]) // <- the player that is also "user"
return userInfo;
}
How about using indexof()
getUserInfo : function(user){
var userInfo = Game.players.filter(function(e) {
return e.gameSocketId === user;
}).pop();
return userInfo;
}
// and later
var user = getUserInfo(userId)
console.log(Game.players.indexOf(user));

Push objects to array so long as object property is not NaN

I am aware that I could use .filter to achieve this, however I am not sure how to implement it.
I have objects as follows within an array
item {
title : g.attributes.title,
category : g.attributes.categoryid,
possible: g.attributes.possible
}
however, some items in the array have a possible property of NaN.
I need to make sure that only items whose possible property is not NaN, are pushed into the array.
Here is an excerpt of my complete code:
function load(id){
itemPath = lev1.lev2.lev3;
items = [];
for (var i = 0; i<itemPath.length; i++) {
if(itemPath[i].attributes.id==id) {
return itemPath[i].attributes.grades.models.map(function(g) {
items.push(
{
title : g.attributes.title,
category : g.attributes.categoryid,
possible: g.attributes.possible
});
});
}
}
}
function load(id){
itemPath = lev1.lev2.lev3;
items = [];
for (var i = 0; i<itemPath.length; i++) {
if(itemPath[i].attributes.id==id) {
return itemPath[i].attributes.grades.models.map(function(g) {
if(g.attributes.possible !== g.attributes.possible){
return;
}
items.push(
{
title : g.attributes.title,
category : g.attributes.categoryid,
possible: g.attributes.possible
});
});
}
}
}
NaN is the only property in javascript that does not equal itself. Just loop over the properties and check them for this, or use the built in NaN() function within the loop as suggested elsewhere.
Update
Since you're only worried about the possible property, just check that one as part of the if statement using === self, or isNaN()
Just change your test line from
if(itemPath[i].attributes.id==id)
to use isNaN function on the properties you want to check
var attr = itemPath[i].attributes;
if (attr.id==id && !isNaN(attr.title) && !isNaN(attr.categoryid) && !isNaN(attr.possible))
You can use the isNaN() and test it before adding it...
function load(id){
itemPath = lev1.lev2.lev3;
items = [];
for (var i = 0; i<itemPath.length; i++) {
if(itemPath[i].attributes.id==id) {
return itemPath[i].attributes.grades.models.map(function(g) {
if( isNaN(g.attributes.title) || isNaN(g.attributes.categoryid) || isNaN(g.attributes.possible) ){
items.push(
{
title : g.attributes.title,
category : g.attributes.categoryid,
possible: g.attributes.possible
});
}
});
}
}
}
You're code is a little confusing
function load(id){
itemPath = lev1.lev2.lev3;
items = [];
for (var i = 0; i<itemPath.length; i++) {
if(itemPath[i].attributes.id==id) {
return itemPath[i].attributes.grades.models.map(function(g) {
items.push(
{
title : g.attributes.title,
category : g.attributes.categoryid,
possible: g.attributes.possible
});
});
}
}
}
It doesn't look like you are using map right. Map works like list compresions in the sense that it iterates over a sequence to preform some kind of operation on each element and returns a new sequence
var arr = [1,2,3];
var complexMagic = arr.map( function(n) { return n + 10; } );
// complexMagic === [11,12,13]
FYI, this is how filter, works. Filter takes in a predicate function( aka, Boolean function) to build a new sequence. If the predicate returns true, then the element will be stored in the new sequence.
var arr = [1, 123, 42, 1001, 1100];
var oddNumbers = arr.filter( function(n) {
return 1 === (n & (-n) );
} );
// oddNumbers === [1, 123, 1001] );
// Bit hacks are fun ;P
It looks like you don't need items array or to even push new elements onto it.
function load(id){
itemPath = lev1.lev2.lev3;
items = [];
for (var i = 0; i<itemPath.length; i++) {
if(itemPath[i].attributes.id==id) {
return itemPath[i].attributes.grades.models.map(function(g) {
// You don't have to return anything.
// This might be an ok place for the NaN check.
return ({
title : g.attributes.title,
category : g.attributes.categoryid,
possible: g.attributes.possible
});
});
}
}
}
I'm lazy and didn't testing any of my code so reader beware. Also avoid the push method if possible. It can be a inefficient approach to append new elements onto an array.

How to flatten or combine member names into one list?

For example if I have something like so:
var Constants = {
scope:{
namespaceA: { A_X: "TEST_AX" , A_Y: "TEST_AY" },
namespaceN: { N_X: "TEST_NX" , N_Y: "TEST_NY" }
}
_mapping: [],
getMapping: function(){...}
}
var flattenList = flatten(Constants.scope); //returns ["TEST_AX","TEST_AY","TEST_NX","TEST_NY"]
var anotherWayFlattened = flatten(Constants.scope.namespaceA,Constants.scope.namespaceB); //returns same result as above
EDIT: one way would be to iterate over the scope via for-each loop but I was looking for something more elegent?
DOUBLE EDIT: ok I just whipped something up like so:
var flattenedList = (function(list){
var flatList = []
$.each(list,function(i,items){
for(var p in items) flatList.push(items[p]);
})
return flatList;
})([Constants.scope.namespaceA,Constants.scope.namespaceB]);
but was wondering if we can avoid passing in the particular property and just pass in Constants and search for the list of namespaces
[Constants.scope.namespaceA,Constants.scope.namespaceB]
I'm wondering why you pass the sub-objects explicitly in an array. Why not just pass the whole Constants.scope object?
var flattenedList = (function(obj){
var flatList = []
for (var prop in obj) {
var items = obj[prop];
for (var p in items)
flatList.push(items[p]);
}
return flatList;
})(Constants.scope);
From your comment it looks like you wanted this:
var flattenedList = (function(obj, test){
var flatList = []
for (var prop in obj) {
if (!test(prop))
continue;
var items = obj[prop];
for (var p in items)
flatList.push(items[p]);
}
return flatList;
})(Constants, function(name) {
return name.substr(0, 9) == "namespace";
// or maybe
return /^namespace[A-Z]$/.test(name);
});
if you wanted to recurse to any (non cyclical!) depth, you could do this :
function flattenList(list, accumulator){
accumulator = accumulator || [];
for(var p in list){
if(list.hasOwnProperty(p)) {
if(typeof list[p] === "string") {
accumulator.push(list[p]);
} else if(typeof list[p] === "object") { // this is not a reliable test!
flattenList(list[p], accumulator);
}
}
}
return accumulator;
}
This code makes a number of assumptions - we only have strings at the end of our objects etc. Alternatively, if you know the depth in advance, your current solution can be optimized by using concat :
var flattenedList = (function(list){
return Array.prototype.concat.apply([], list);
})([Constants.scope.namespaceA,Constants.scope.namespaceB]);
Here's an approach that allows for deeper nesting. I know that wasn't part of the goals, but I found it a more interesting problem. :-)
var flatten = (function() {
var toString = Object.prototype.toString, slice = Array.prototype.slice;
var flatten = function(input, output) {
var value;
output = (toString.call(output) == "[object Array]") ? output : [];
for (name in input) {if (input.hasOwnProperty(name)) {
value = input[name];
if (toString.call(value) == "[object Object]") {
flatten(value, output);
} else {
output.push(value);
}
}};
return output;
};
var merge = function(first, second) {
return first.concat(second);
}
return function() {
return slice.call(arguments).map(flatten).reduce(merge);
};
}());
This allows either approach:
flatten(Constants.scope);
flatten(Constants.scope.namespaceA, Constants.scope.namespaceN);
You can pass in as many separate arguments as you like, or one argument. They'll all be searched to arbitrary depths.
For some environments, you might have to shim Array.prototype functions map and reduce.

Categories

Resources