Using jasmine to compare arrays fails in ie8 - javascript

Apparently ie8 has three properties that get appended to the resulting array from a call to String.prototype.match():
input, index and lastIndex
(MSDN Documentation)
The result is that array comparison fails when using Jasmine's .toEqual() matcher.
I'm still working my way up the learning curve on unit testing, so I'm just curious of what the right way is to do deal with this failure.
The following works but seems a bit lame:
numArray = str.match(/\d+(\.\d+)?/g);
if (numArray && numArray.input) {
delete numArray.index;
delete numArray.input;
delete numArray.lastIndex;
}

Underscore's 'difference' method can help -
expect(_.difference(['item1', 'item2'], ['item1', 'item2'])).toEqual([]);
http://underscorejs.org/#difference

I think #monkeyboy's answer is not correct.
Since underscore.difference() returns the elements of the first array that are not present in the second array: _.difference([1],[1,2]); is also [] so the test will pass when it shouldn't. I couldn't find a way to solve this using underscore.
So i'm using:
expect(JSON.stringify(result)).toBe(JSON.stringify(expected));
which works as expected.
Anyway, i'd like to know how others are doing this.

Related

Asserting an element is focused

According to the How do I assert an element is focused? thread, you can check if an element is focused by switching to an activeElement() and assert this is the same element you've expected to have the focus:
expect(page.element.getAttribute('id')).toEqual(browser.driver.switchTo().activeElement().getAttribute('id'));
In my case, the currently focused element does not have an id attribute.
What should I do instead of checking an id?
Bonus question: Also, as you can see from my tries to solve it, it looks like I cannot expect/assert an element (or web element) as a complete object. Why?
I've tried:
expect(page.element).toEqual(browser.driver.switchTo().activeElement());
But is is failing with an error I cannot even understand - there is a huge traceback (it is about 10 minutes to scroll in the console), but no user-friendly error inside.
I've also tried to use getWebElement():
expect(page.element.getWebElement()).toEqual(browser.driver.switchTo().activeElement());
But this resulted into the following error:
Error: expect called with WebElement argument, expected a Promise. Did
you mean to use .getText()?
Using the latest protractor development version.
In my answer I'm going to assume activeElem and pageElem are both protractor element finders, and are pointing to the same web element.
First to answer your question about why
expect(activeElem).toEqual(pageElem);
Gets into an infinite loop, it's because protractor patched jasmine's expect to resolve the promise before asserting, so that things like expect(activeElem.getText()).toEqual('text'); works without having to do
activeElem.getText().then(function(text) {
expect(text).toEqual('text');
})
You could say, why not just resolve the promise once? But then there are nested promises.
So now you might be thinking this is an issue, but it really isn't because you would never compare two elementFinders in a real use case. Jasmine's toEqual does a reference check, and not a deep compare, so expect(activeElem).toEqual(pageElem), is just the same as a simple reference comparison: (activeElem === pageElem).toToTruthy(), and there's really no point doing that. (Note element(by.css('html')) === element(by.css('html')) is false because it's not the same reference.)
So, to answer the real question for this thread: how to see if two elementFinders have the same underlying webelements:
expect(activeElem.getId()).toEqual(pageElem.getId());
It's weird that it's expecting a promise only and could not handle a webdriver element... I had the same HUGE stacktrace as you.
Anyway, would you accept this kind of solution: send a "dumb" promise with a nice comment to justify why you had to do that. It's more a workaround than a semantic solution I admit.
expect(page.element.getInnerHtml())
.toEqual(browser.driver.switchTo().activeElement().getInnerHtml());
It's working for me ;)
EDIT: Bonus answer
The reason you can't call expect with a WebElement comes from the Webdriver Control Flow Principle (I'm sure you already know about) and this line in jasminewd, the adapter for jasmine to Webdriver developped and used by Protractor ;)
For the future readers, I've created a custom matcher to assert if an element is active/focused:
beforeEach(function() {
jasmine.addMatchers({
toBeActive: function() {
return {
compare: function(elm) {
return {
pass: elm.getId().then(function (activeElementID) {
return browser.driver.switchTo().activeElement().getId().then(function (currentElementID) {
return jasmine.matchersUtil.equals(currentElementID, activeElementID);
});
})
};
}
};
}
});
});
Usage:
expect(element(by.css("#myid")).toBeActive();
What about using CSS selectors and :focus
expect(element.all(by.css('#myid:focus')).count()).toBe(1);
I solved it by using protractor's:
ElementFinder.prototype.equals
const activeElement = await browser.driver.switchTo().activeElement();
const potentialyFocusedElement = await component.$('your-locator');
const isFocused = await potentialyFocusedElement.equals(activeElement);
I am not sure how exactly equals work, if it does a deep compare, or it somehow compares instances. But it works.
Note that you can not call activeElement.equals(...) since it is not an ElementFinder, it is a WebElement.
You need to come up with a way to tell Protractor/webdriver how to find your element on the page. Protractor uses JavaScript to find elements, so any of the tools available for inspecting the DOM are available. Protractor and webdriver wrap these APIs in the various by flavors:
http://angular.github.io/protractor/#/api?view=ProtractorBy
In addition to the basic flavors, Protractor adds Angular-aware flavors (so you can search by Angular binding, etc).
If your element does not have any distinguishing characteristics, then it might be worth adding something to make it easier to test. Or, (though this is often frowned upon because its so fragile), you can use an xpath. See https://github.com/angular/protractor/blob/master/docs/locators.md
You can simply check the text (or anything else) of the element:
var currentElement = browser.switchTo().activeElement();
expect(currentElement.getText()).toEqual(page.element.getText());

Can you add a function to a hijacked JavaScript Array?

This question is related to What are the best practices to follow when declaring an array in Javascript?
Let's say a client, let's call them "D. B. Cooper", has a first requirement that the following code must run before any other JavaScript code:
Array = function(){
alert('Mwahahahaha');
};
Furthermore, Cooper requires that custom functions must be added to the built in Array object (not the hijacked one). For example, if Array was unhijacked, this would be done with:
Array.prototype.coolCustomFunction = function(){
alert('I have ' + this.length + ' elements! Cool!');
};
Which would afford:
var myArray = [];
myArray.coolCustomFunction();
However, this is not compatible with the first requirement. Thus, how can you best fulfill both of D. B. Cooper's requirements?
Note: D.B. even wrote a test fiddle to help make sure solutions meet his requirements...what a guy!
Update:
For those of you who like a challenge: please try to find an unhijackable cross-browser solution to this problem. For example, here's an even more hijacked test case (thanks for reformatting this Bergi) that hijacks Array, Object, Array.prototype.constructor, and Object.prototype.constructor. Thus far, it looks like there may be a browser-specific solution to this (see Bergi's comment on his answer, and let us know if you find a way to hijack it in FF), but it is unclear at this point if there is a cross-browser solution to this.
Whatever your Array function/constructor is, the literal syntax for arrays will always generate "real" arrays with their [[prototype]] set to the native array prototype object (once, this was a security vulnerability). So, you can always access that by using
Object.getPrototypeOf([])
even if Array or [].constructor are hijacked. (Will of course not work when Object is hijacked, then it get's really complicated)
(Brought D.B. down!)
If you want to use a workaround, in FF the following line will always work (and is not hijackable):
[].__proto__.coolCustomFunction = coolCustomFunction;
Since Array is not necessarily equal to [].constructor, you could use [].constructor to refer to the original Array function since this is hardwired and Array = function(){} won't alter it.
Array = function () { alert("foo")};
// this will always point to the original Array
[].constructor.prototype.foo = "bar";
var myArray = [0, 1];
alert(myArray.foo) // alerts "bar"
http://jsfiddle.net/yXPJ8/5/
Yes ... you just did ... but you created the array using [] .. if you use new Array() it works fine ...
See example here

Mocha testing using expectjs, getting model to match hash?

Currently I'm doing this:
expect( tasks.at(0).get('title') ).to.be('Root')
expect( tasks.at(0).get('next') ).to.be(true)
But it seems like there should be a better way. I'd rather do something like this:
expect(tasks.at(0).attributes).to.eql({title:'Root', next:true})
But that doesn't work because tasks.at(1).attributes has many other attributes, so it doesn't strictly match. Any ideas? I'm fine with using something other than expectjs.
I don't know if it's the best way, but I took the advice from #skizeey and made my own test extension. In other words, now I call (in coffeescript):
expect(tasks).to.have.modelAttributes
1: title:'Root'
2: title:'Child'
The code I used is pretty specific to the testing library I am using (switched to Chai), but the general idea is that I iterate over the hash, extract the index, and then look at each key:value. Here's a generic example of my code:
# Check each index
for index of properties
# Iterate over each key:value in each index
for key of properties[index]

Having trouble understanding a reflection test in Javascript Koans

There is already an answer posted to the test itself, which can be found here, but I can't seem to figure out why that answer is correct.
The part of the test that is giving me trouble is:
var keys = [];
var fruits = ['apple', 'orange'];
for(propertyName in fruits) {
keys.push(propertyName);
}
ok(keys.equalTo(['__', '__', '__']), 'what are the properties of the array?');
The (apparently) correct answer, as noted in the above linked question is
ok(keys.equalTo(['0', '1', 'fruits.prototype'), 'what are the properties of the array?');
I tried inserting the answer - fixed the syntax error - and my test still fails.
In the same test file, another test is nearly identical and the answer is what I would expect it to be:
test("property enumeration", function() {
var keys = [];
var values = [];
var person = {name: 'Amory Blaine', age: 102, unemployed: true};
for(propertyName in person) {
keys.push(propertyName);
values.push(person[propertyName]);
}
ok(keys.equalTo(['name','age','unemployed']), 'what are the property names of the object?');
ok(values.equalTo(['Amory Blaine',102,true]), 'what are the property values of the object?');
});
The only difference I can see between these two tests is that the second is using an object rather than an array.
I ran the code from the first test by itself (outside of the unit testing framework) and output the value of keys, which it showed as ["0","1"] - what I would expect. Where is this hidden third value, and how can I access it?
So, I guess I ultimately have two questions:
Why is the answer from the other question not working for me?
What is different about the first test and the second test?
Disclaimer: I'm pretty sure this is right, but haven't bothered testing it. Could you try my answer out, since you've got the tests running?
Looking at the files on GitHub, there is a helper script called koan.js. I'm assuming it gets loaded before the tests because I am too lazy to run them myself :P. (It's in the support directory.)
In this file, there is a method called equalTo defined on all arrays:
Array.prototype.equalTo = function(compareTo) { ... }
So the answers to your questions:
Because the answer to the other question was wrong. Completely misguided. Etc.
Because the method is defined on Array rather than Object.
Seems a little bit underwhelming.
If you define a function like this on the prototype, it will be inherited by all arrays. Try defining something like that in the console and then evaluate [].equalTo. And then, for more fun, try something like:
for (x in []) console.log(x)
Since this method is defined on the prototype, the loop iterates over it as well. So the answer is to the test is probably 0, 1, 'equalTo'. However, if you use the for loop with the hasOwnProperty check, it will naturally not iterate over the method.
This is really an object lesson about no using for in to iterate over arrays :). You never know what's going to sneak in... Coincidentally, this is why prototype.js fell out of favor despite actually being a nice framework.

Array is not array

I am running
var n = [];
jQuery.toJSON( n );
On one page I get "[]" on the other ""[]"". On both pages I run the same jQuery version with a toJson Plugin.
In Firefox DOM I can see both arrays have the same function names, but ... different:
all b(B, A)
any j(B, A)
all all(iterator, context)
any any(iterator, context)
I guess there are some Array.prototype functions before my script. That causing the arrays to be different. I can't change the other code I have to somehow deal with this.
I tried new Array() and jQuery.makeArray(n), still same result. I actually don't care that the arrays are not equal but how do I get the same JSON code for this? It's getting worse if I have strings in the array: ""[\"a\", \"b\"]""
The extra quotes are caused by the
Array.prototype.toJSON
function that is defined by the Prototype library and possibly other libraries as well. This function is called by jQuery.toJSON (or JSON.Stringify() for that matter) and produces the extra quotes. If you would use
delete Array.prototype.toJSON // remove toJSON for all Arrays
//or
delete n.toJSON // remove toJSON for specific Array
before you do the jQuery.toJSON, that should work!
As another suggestion, it is better to use
JSON.stringify(object)
instead of jQuery.toJSON. It is supported natively in most browsers. If you want to be sure it works everywhere, use https://github.com/douglascrockford/JSON-js, it is the basis used for the JSON.stringify() function.
For JSON.stringify(), see https://developer.mozilla.org/En/Using_native_JSON for more info.
My dirty hack for this is:
function cleanJson(s)
{
if (s[0] == '"')
{
s = s.substring(1, s.length-1).replace(/\\"/g,'"');
}
return s;
}
Does the job, but I am still looking for a cleaner solution.

Categories

Resources