Is there an Array equality match function that ignores element position in jest.js? - jasmine

I get that .toEqual() checks equality of all fields for plain objects:
expect(
{"key1":"pink wool","key2":"diorite"}
).toEqual(
{"key2":"diorite","key1":"pink wool"}
);
So this passes.
But the same is not true for arrays:
expect(["pink wool", "diorite"]).toEqual(["diorite", "pink wool"]);
There does not seem to be a matcher function that does this in the jest docs, i.e. that tests for the equality of two arrays irrespective of their elements positions. Do I have to test each element in one array against all the elements in the other and vice versa? Or is there another way?

There is no built-in method to compare arrays without comparing the order, but you can simply sort the arrays using .sort() before making a comparison:
expect(["ping wool", "diorite"].sort()).toEqual(["diorite", "pink wool"].sort());
You can check the example in this fiddle.

As already mentioned expect.arrayContaining checks if the actual array contains the expected array as a subset.
To check for equivalence one may
either assert that the length of both arrays is the same (but that wouldn't result in a helpful failure message)
or assert the reverse: That the expected array contains the actual array:
// This is TypeScript, but remove the types and you get JavaScript
const expectArrayEquivalence = <T>(actual: T[], expected: T[]) => {
expect(actual).toEqual(expect.arrayContaining(expected));
expect(expected).toEqual(expect.arrayContaining(actual));
};
This still has the problem that when the test fails in the first assertion one is only made aware of the elements missing from actual and not of the extra ones that are not in expected.

Put the elements into a set. Jest knows how to match these.
expect(new Set(["pink wool", "diorite"])).toEqual(new Set(["diorite", "pink wool"]));

this does not answer the question exactly, but still may help people that end up here by google search:
if you only care that a subset of the array has certain elements, use expect.arrayContaining() https://jestjs.io/docs/en/expect#expectarraycontainingarray
e.g.,
expect(["ping wool", "diorite"])
.toEqual(expect.arrayContaining(["diorite", "pink wool"]));

Another way is to use the custom matcher .toIncludeSameMembers() from jest-community/jest-extended.
Example given from the README
test('passes when arrays match in a different order', () => {
expect([1, 2, 3]).toIncludeSameMembers([3, 1, 2]);
expect([{ foo: 'bar' }, { baz: 'qux' }]).toIncludeSameMembers([{ baz: 'qux' }, { foo: 'bar' }]);
});
It might not make sense to import a library just for one matcher but they have a lot of other useful matchers I've find useful.

What about checking the content and the length?
expect(resultArray).toEqual(expect.arrayContaining(expectedArray));
expect(resultArray.length).toEqual(expectedArray.length);

If you want to compare two arrays in JEST use the bellow model.
Official link: https://jestjs.io/docs/en/expect#expectarraycontainingarray
const array1 = ['a', 'b', 'c'];
const array2 = ['a', 'b', 'c'];
const array3 = ['a', 'b'];
it("test two arrays, this will be true", () => {
expect(array1).toEqual(expect.arrayContaining(array2));
});
it("test two arrays, this will be false", () => {
expect(array3).toEqual(expect.arrayContaining(array1));
});

You can combine using sets as stated in this answer with checking length of actual result and expectation. This will ignore element position and protect you from duplicated elements in the same time.
const materials = ['pink wool', 'diorite'];
const expectedMaterials = ['diorite', 'pink wool'];
expect(new Set(materials)).toEqual(new Set(expectedMaterials));
expect(materials.length).toBe(expectedMaterials.length);
EDIT: As there is suggested in comment below, this will only work for arrays with unique values.

If you don't have array of objects, then you can simply use sort() function for sorting before comparison.(mentioned in accepted answer):
expect(["ping wool", "diorite"].sort()).toEqual(["diorite", "pink wool"].sort());
However, problem arises if you have array of objects in which case sort function won't work. In this case, you need to provide custom sorting function.
Example:
const x = [
{key: 'forecast', visible: true},
{key: 'pForecast', visible: false},
{key: 'effForecast', visible: true},
{key: 'effRegForecast', visible: true}
]
// In my use case, i wanted to sort by key
const sortByKey = (a, b) => {
if(a.key < b.key) return -1;
else if(a.key > b.key) return 1;
else return 0;
}
x.sort(sortByKey)
console.log(x)
Hope it helps someone someday.

Still a work in progress, but this should work albeit, the error messages may not be clear:
expect.extend({
arrayContainingExactly(receivedOriginal, expected) {
const received = [...receivedOriginal];
if (received.length !== expected.length) return {
message: () => `Expected array of length ${expected.length} but got an array of length ${received.length}`,
pass: false,
};
const pass = expected.every((expectedItem, index) => {
const receivedIndex = findIndex(received, receivedItem => {
if (expectedItem.asymmetricMatch) return expectedItem.asymmetricMatch(receivedItem);
return isEqual(expectedItem, receivedItem);
});
if (receivedIndex === -1) return false;
received.splice(receivedIndex, 1);
return true;
});
return {
message: () => 'Success',
pass,
}
}
});
Then use it like this:
expect(['foo', 'bar']).arrayContainingExactly(['foo']) // This should fail
or
expect({foo: ['foo', 'bar']}).toEqual({
foo: expect.arrayContainingExactly(['bar', 'foo'])
}) // This should pass
We are looping through each value and removing it from the received array so that we can take advantage of the asymmetric matching provided by Jest. If we just wanted to do direct equivalency this could be simplified to just compare the 2 sorted arrays.
Note: This solution uses findIndex and isEqual from lodash.

You can use jest toContainEqual to check if an array contains an element. Then just do that for each element in your expected array:
const actual = [{ foobar: 'C' }, { foo: 'A' }, { bar: 'B' }];
const expected = [{ foo: 'A' }, { bar: 'B' }, { foobar: 'C' }];
expect(actual).toContainEqual(expected[0]);
expect(actual).toContainEqual(expected[1]);
expect(actual).toContainEqual(expected[2]);
(Or put the expect statement in a loop if you have too many elements to check)

Related

CYPRESS - Making the ":contains()" in a filter selector case-insensitive

I am attempting to use the .filter command of cy.get() to return multiple instances of a DOM object that has the same word, but different cases, and can't get it working.
Eg: I want the following code example to return both Hello, hello and even heLLo
cy.get('tbody tr')
.filter(':contains("hello")')
Update: One further piece of information that may affect things is what I am doing after this. Here's a beefier piece of code example:
cy.get('tbody tr')
.filter(':contains("hello")')
.within(() => {
cy.get('td').then(($rows) => {
<do stuff>
};
};
Is this even possible? According to the documentation here the text selection is case-sensitive, but I'm hoping there is another option, eg: am I able to use Regex in this instance instead?
Many thanks in advance.
You are quite right, .contains(...) is not the way, it only returns one result, in the docs
.contains() yields the new DOM element it found.
One way to filter by text case-insensitive is to use a .filter() with callback.
Testing this
<table>
<tbody>
<tr><td>hello</td></tr>
<tr><td>Hello</td></tr>
<tr><td>heLLO</td></tr>
<tr><td>goodbye</td></tr>
</tbody>
</table>
this test succeeds
cy.get('tbody tr')
.filter((i, el) => el.innerText.toLowerCase().includes('hello'))
.should('have.length', 3) // 3 out of 4 rows
or you can add your own pseudo
Cypress.$.expr.pseudos.containsInsensitive = function(a, i, m) {
return Cypress.$(a).text().toLowerCase()
.indexOf(m[3].toLowerCase()) >= 0;
};
cy.get('tbody tr')
.filter(':containsInsensitive("hello")')
.should('have.length', 3) // 3 out of 4 rows
or if you prefer regex
Cypress.$.expr.pseudos.match = function(a, i, m) {
const expr = m[3].split('/')[1]
const flags = m[3].split('/')[2]
const regex = new RegExp(expr, flags)
return regex.test(Cypress.$(a).text())
};
cy.get('tbody tr')
.filter(':match(/hello/i)')
.should('have.length', 3) // 3 out of 4 rows
How about you just use .contains() then you can pass the case sensitivity flag matchCase as false. Cypress Docs
cy.get('tbody tr').contains('hello', { matchCase: false })

Lodash sortedby object list is stuck in _wrapper_

I am experimenting with lodash sorting. I got the lodash to sort the object list although the sorted results are stuck in wrapper. If I use .value(), then I get unsorted keys output.
var testData = {c:1,b:2,a:3}
var sortedKeys = _(testData).keys().sortBy(key => {return testData.key});
console.log(sortedKeys);
will produce:
LodashWrapper {__wrapped__: {…}, __actions__: Array(2), __chain__: false, __index__: 0, __values__: undefined}
__actions__:(2) [{…}, {…}]
__chain__:false
__index__:0
__values__:undefined
__wrapped__:
a:3
b:2
c:1
__proto__:Object
__proto__:lodash
What is it that I am missing in order to get sorted object list out of lodash wrapper.
When you do testData.key, I'm pretty confident that you actually mean to be doing testData[key].
That alone allows the method to work properly i.e. return an array of Object keys sorted by values. Note that you still have to call .value() if you'd like to unwrap the lodash object.
If there's something else you're expecting, please clarify.
const testData = {c:1,b:2,a:0}
const sortedKeys = _(testData).keys().sortBy(key => {return testData[key]})
/* can do without the return like the below as well */
// const sortedKeys = _(testData).keys().sortBy(key => testData[key])
console.log(sortedKeys.value())
// produces ['a','c','b']
If you want the key and value pair, try the below.
_(obj).toPairs().sortBy(0).value()
There are few things that are happening here which I think are important to note:
First you are starting your sorting statement with the short notation for the lodash _.chain method which allows the results of one operation to flow into the next one. This is similar to how _.flow works in lodash/fp.
Chaining requires the last operation in the chain to end with values() in order to get your actual result from the lodash wrapper. So if you did:
_(testData).keys().sortBy(key => {return testData.key}).values(); // OR
_.chian(testData).keys().sortBy(key => {return testData.key}).values();
You would get some results.
Second issue is that in your flow you get the keys of the objects but then you are not actually sorting by them. To do this you need something like this:
var testData = {c:1, b:2, a:3}
console.log(_.chain(testData).keys().sortBy().value());
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
The difference here is in the sort function where you already have the keys as `[ 'c', 'b', 'a' ] so you just need to sort them. They are not objects anymore but simply strings.
Hope this helps.

Access variable hash depth values with square brackets notation

Given this hash:
hash1= { node1: { node2: { node3: { node4: { node5: 1 } } } } }
We access inside nodes with square brackets like this:
hash1[:node1][:node2][:node3][:node4]
Now I have a hash that I know will always be nested as it is an XML response from a SOAP webservice, but neither the depth of the hash nor the names of the nodes stay the same. So it would be nice if I could ask the user of my application for the hash depth and store it in a variable. And then be able to do hash1[:hash_depth] and achieve the same result as above.
I have accomplished what I want by the following code:
str = 'node1,node2,node3,node4'
str_a = str.split(',')
hash_copy = hash1
str_a.each { |s| hash_copy = hash_copy.[](s.to_sym) }
hash_copy
=> {:node5=>1}
hash1[:node1][:node2][:node3][:node4]
=> {:node5=>1}
that is asking the user to enter the hash depth separated by commas, store it in a string, split it, make an array, clone the original hash, go down each level and modify the hash till I get to the desired node. Is there a way to do it with the square brackets notation and using a variable to store the depth without modifying the hash or needing to clone it?
Edit:
someone answered with the following (can't see his post anymore???)
hash_depth="[:node1][:node2][:node3][:node4]"
eval "hash1#{hash_depth}"
Although eval does everything you need, there is another approach, since you already have the working code for comma-separated list:
hash_depth="[:node1][:node2][:node3][:node4]"
csh = hash_depth.gsub(/\A\[:|\]\[:|\]\Z/, { '][:' => ',' })
#⇒ "node1,node2,node3,node4"
And now you are free to apply your existing function to csh.
If this is a webapp, I think you should prepare a list of short textareas, which starts with a single text item, and the user can keep adding a new item to the list by clicking on a button. The areas will be filled by the user, and will be sent.
Then, you will probably receive this through some serialized form. You decode this to get an array of strings:
str_a = ["node1", "node2", "node3", "node4"]
and you can reach the inner element by doing:
str_a.inject(hash1){|h, s| h[s.to_sym]} #=> {:node5 => 1}

Underscore.js Case Insensitive Sorting

Having some slight issues trying to get underscore.js to do case-insensitive sorting. I have an array of objects and would like to be able to sort by property name.
Using shortcut method sortBy
iteratee may also be the string name of the property to sort by (eg. length).
Array to be sorted:
var array = [{ name: 'test_1234', description: 'zzaaa bb cc'},
{ name: 'zz1111', description: 'ZAAbbbcc'},
{ name: 'TEST', description: '4422'},
{ name: '1a2929', description: 'abcdef'},
{ name: 'abc', description: 'Full description'},
{ name: 'GGGGH', description: '123456'}];
Sorting using this method, sortProperty = 'name', the result places uppercase before lowercase.
var sorted = _.sortBy(array, sortProperty);
1a2929 - abcdef
GGGGH - 123456
TEST - 4422
abc - Full description
test_1234 - zzaaa bb cc
zz1111 - ZAAbbbcc
I assume this has to do with case sensitivity, but I can't figure out how to change names in the array to lowercase and compare that way.
Any help is greatly appreciated.
Edit:
As pointed out, you pass in name or a function, so just adjusted function to return which field to sort by:
http://jsfiddle.net/rjaqp1vg/5/
The name to sort by can be the field name OR a function, so pass a function that does a lower-case conversion.
var sorted = _.sortBy(array, function (i) { return i.name.toLowerCase(); });
should do the trick.
Don't use _.sortBy for this. The correct way to sort strings alphabetically is to use localeCompare. Here's an example in pure Javascript:
['Z', 'A','z','á', 'V'].sort(function(a, b){
return a.localeCompare(b, undefined /* Ignore language */, { sensitivity: 'base' })
});
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare.
Chris' answer worked well for me, and I made it a little shorter with an arrow function:
var sorted = _.sortBy(array, (i) => i.name.toLowerCase());

CaspserJS assertEval with variables

I'd like to use CasperJS to evaluate a variable equals a certain value.
I simplified my exemple as much as I could that way:
var testDate = "24/03/14";
casper.test.begin('TEST', 1, function suite(test) {
casper.start('http://www.google.com/', function() {
this.test.assertEval(function() {
return testDate == "24/03/14";
}, "testDate is 24/03/14" );
});
casper.run(function() {
this.test.done();
});
});
I don't know why it fails, here is what I get in my console:
Test file: tests.js
#TEST
FAIL testDate is 24/03/14
# type: assertEval
# file: tests.js:7
# code: }, "testDate is 24/03/14" );
# subject: null
# fn: undefined
# params: undefined
FAIL 1 test executed in 2.896s, 0 passed, 1 failed, 0 dubious, 0 skipped.
Details for the 1 failed test:
In tests.js:7
TEST
assertEval: testDate is 24/03/14
Any idea ?
UPDATE
I realised my simplified example was faulty, it didn't represent what I really needed.
Actually, what I want to achieve is to test if a variable from the current page DOM context equals a local variable.
As per manual Asserteval:
Asserts that a code evaluation in remote DOM strictly resolves to a boolean true:
your testdate variable is local to the casperjs script and is not accessible in the remote dom. You would have to inject it to the window like described here.
Ok found the answer myself.
To test if a variable from the current page DOM context equals a local variable, I realised I could use a simple assertEvalEquals():
test.assertEvalEquals(function() {
return variableFromPageDomContext;
}, localVariable);
Likewise, when testing if a variable from the current page DOM context matches a RegExp pattern, we have to use evaluate() to get the variable from the DOM as the first parameter of an assertMatch():
test.assertMatch(this.evaluate(function() {
return variableFromPageDomContext;
}), RegExpPattern);
Hope that can help.
As #Surreal answers its possible to use the assertEvalEquals() passing the function and the expected value.
However the original question wants to pass a variable from casperjs context to assertEval() function, you can simply do it as follows, passing to assertEval() three arguments: the function which receive the value, a message for the assert and the value:
var varPassToEval = 'someValue';
test.assertEval(
function(varFromCasperContext){
return varFromPageDomContext === varFromCasperContext;
},
'Assert Eval to test',
varPassToEval
);
With the above example probably is clear to use assertEvalEquals() however could be useful for more complex cases, for example imagine that you want to check if a text appears in a some <li> inside <ul> in DOM which it's dynamic and can change but you don't know at first where your text is... for this case you can use:
var somePartOfText = 'blue';
test.assertEval(
function(varFromCasperContext){
return document.getElementsByTagName("ul")[0].textContent.indexOf(varFromCasperContext) != -1;
},
'Assert Eval to test',
somePartOfText
);
Hope it helps,

Resources