Estrange behaviour in JavaScript array clone equality assertion - javascript

I have found a failing assertion in a JavaScript unit test that I would like to fix. The unit test code is the following (the full code can be found here):
beforeEach(function() {
arrNeedle = ['waffles'];
objNeedle = {w: 'waffles'};
strNeedle = 'waffles';
numNeedle = 3.14159
arrDupe = JSON.parse(JSON.stringify(arrNeedle));
objDupe = JSON.parse(JSON.stringify(objNeedle));
strDupe = JSON.parse(JSON.stringify(strNeedle));
numDupe = JSON.parse(JSON.stringify(numNeedle));
arrContainer = [arrDupe, objDupe, strDupe, numDupe];
objContainer = {
arr: arrDupe
, obj: objDupe
, str: strDupe
, num: numDupe
};
arrMissing = ['chan'];
objMissing = {missing: 'chan'}
strMissing = 'chan';
});
it("has its test set up correctly", function() {
arrNeedle.should.not.equal(arrDupe);
objNeedle.should.not.equal(objDupe);
arrContainer.should.not.contain(arrNeedle);
arrContainer.should.not.contain(objNeedle); // fails
objContainer.arr.should.not.equal(arrNeedle);
objContainer.obj.should.not.equal(objNeedle);
});
In the test we are cloning an object and inserting it into an array:
objNeedle = {w: 'waffles'}; // original
objDupe = JSON.parse(JSON.stringify(objNeedle)); // clone
arrContainer = [arrDupe, objDupe, strDupe, numDupe]; // add clone to array
The failing assertion checks that the array (contains the cloned object) doesn't contain the original object.
arrContainer.should.not.contain(objNeedle); // fails
I tried with an external assertion plugging (chai-things) with no luck:
arrContainer.should.not.include(objNeedle); // fails
arrContainer.should.not.include.something.that.deep.equals(objNeedle); // fails
The following assertion pass the test but is not the ideal solution:
arrContainer[0].should.not.equal(objNeedle); // pass
Do you know why is the array considered equal to it's clone only in some cases?
Thanks in advance :)

If you take a look at the ChaiJS code, you will see on line 189 of /lib/chai/core/assertions.js the following:
if (_.type(obj) === 'array' && _.type(val) === 'object') {
for (var i in obj) {
if (_.eql(obj[i], val)) {
expected = true;
break;
}
}
}
This is inside the include(val, msg) function, which is what is used by the .contains() matcher (see line 215).
This means that if the obj (the thing being tested) is an array and the val (the parameter to the .contains() matcher function) is an object, as it is in your case, it will check for deep equality using _.eql() (_.eql is an alias for the function provided/exported by the external deep-eql module).

Related

How do I set a JavaScript object's value to null

I have created this JS object from an array.
var rv = {};
$( ".part-name:visible" ).each(function( index ) {
//rv[$(this).text()] = arrayPartsName[$(this).text()];
rv[$(this).text()] = arrayPartsName[$(this).text()];
console.log(rv);
})
4GN: "4GN"
4GNTS: "4GNTS"
042645-00: "042645-00"
503711-03: "503711-03"
573699-05: "573699-05"
I have to use this object with Materialize Autocomplete and I have to edit it. The correct object must be, for example, like this
4GN: null
4GNTS: null
042645-00: null
503711-03: null
573699-05: null
How can do this?
Picking up from my comment. You can just set it to null ;) JavaScript is quite a cool language... you can pretty much set any object's properties to anything you want, null, a specific value, or even a function... see some more on the topic
But to focus on your specific question:
Change this line
rv[$(this).text()] = arrayPartsName[$(this).text()];
to
rv[$(this).text()] = null;
Something to be aware of
If you have property or key values in the JSON object with a dash in the name, you have to wrap it in quotes ", otherwise it wont be seen as valid. Although this might not be as evident, or an issue in your example as your keys are being added via the following function $(this).text().
var fruit = {
"pear": null, // something null
"talk": function() { console.log('WOOHOO!'); } // function
}
var apple = "app-le";
fruit[apple.toString()] = 'with a dash';
fruit["bana-na"] = 'with a dash';
// below is not allowed, the values will be evaluated as
// properties that dont exist, and then your js will fail
// fruit[pe-ar] = 'with a dash';
fruit.talk();
console.log(fruit);

Cannot Read Property 'Push' of undefined - Typescript

When I try to add to an array in Typescript (wrapped in Ionic2) I get an error telling me the array is undefined even though I've declared it. I've tried declaring it using two different declarations and not found the problem. The two declarations I used are:
tracker: any[];
and
tracker: Array<any>;
The first time I try to add anything to the array and where I get the error is below. I wanted to include the whole function, just in case there was something in there that could be redefining what 'this' is:
// Answer Correctly
answerQuestionCorrectly(answer) {
let answerButton = <HTMLButtonElement>document.getElementById('answer-' + answer.AnswerId);
answerButton.addEventListener('click', (event) => {
// Increase the score
this.currentScore = this.currentScore + this.countdown;
// Set up quiz review
var correct = answer.AnswerText;
var qTrack = {no: this.questionNo, q: this.questionText, a: answer.AnswerText, c: correct}
console.log(qTrack);
this.tracker.push(qTrack);
console.log(this.tracker);
// Check for end of questions
if (this.questionNo < this.noOfQuestions) {
// Remove the old answers
var parent = document.getElementById('answers');
this.answers.forEach(element => {
var button = <HTMLButtonElement>document.getElementById('answer-' + element.AnswerId);
parent.removeChild(button);
});
// Re-init the timer
this.timer.initTimer();
// Load Next Question
this.loadQuestion();
} else {
// End the Quiz
this.endOfQuiz();
}
});
}
Those declarations only specify the type of the variable — it also needs a value. Try something like
var tracker: any[] = [];
to initialise the variable to an empty array.
You have to initialize the array before you can push an object into it.
tracker: any[ ] = [ ];
You must initialize it like this:
tracker: Array<any>=[];

Why does shift() work on one array but not the other, unless applied generically on the other

In the following code the compiler (at 'compile' time) makes no complaints about groups.shift() but complains that depths.shift() is not a function. What am I being blind to? (I tried renaming depths, retyping, etc.)
const tag1x = (elem, content, groups = ['?','?','?'], depths = ['?','?'], optional = true, level = 0) => {
let option = optional ? '?' : '';
let template = `
${'\t'.repeat(level)}(${groups.shift()}:<$1[^>]*?DDD(${depths.shift()}:[0-9]+)[^>]*>)$3
${'\t'.repeat(level)}(${groups.shift()}:$2)
${'\t'.repeat(level)}(${groups.shift()}:</$1[^>]*?DDD(${depths.shift()}:[0-9]+)[^>]*>)$3
`;
return form(template, elem, content, option);
}
However, if I use shift generically it works fine on all counts:
const tag1x = (elem, content, groups = ['?','?','?'], depths = ['?','?'], optional = true, level = 0) => {
let option = optional ? '?' : '';
let template = `
${'\t'.repeat(level)}(${groups.shift()}:<$1[^>]*?DDD(${[].shift.call(depths)}:[0-9]+)[^>]*>)$3
${'\t'.repeat(level)}(${groups.shift()}:$2)
${'\t'.repeat(level)}(${groups.shift()}:</$1[^>]*?DDD(${[].shift.call(depths)}:[0-9]+)[^>]*>)$3
`;
return form(template, elem, content, option);
}
The above is fully functional.
I misread the situation. The error was occurring at runtime, and thus pretty clearly because of a string input instead of an array input. A string input will get fixed up by [].shift.call(myAccidentallyAString) while, of course, a shift() call directly on the string is not a function.
It acts like Array.isArray(myStuff) ? myStuff.shift() : [myStuff].shift(), which makes sense because (I'll guess) myStuff is getting boxed to an object then called on by shift().

Library or object that can check containment of lists

This question is an extension of this one: Checking containment in set of lists in javascript. I want to be able to use a set like function in nodejs or Javascript that can support checking whether or not a list belongs to a collection. For example, given the example in the link, I would like the behavior:
var s = new SetWithListCheckingAbility([[1,2], [2,3]])
s.has([2, 3])
true
I was unable to find any nodejs library that has this functionality, however. The other obvious solution seems to be JSON serializing each object that is added to the set object, and doing checking based on the JSON string, since Javascript equality works for strings. This would probably require subclassing the Set object in ES6. However, I am not sure how to do this for this case...
What you can do is take each member of the set and convert it to a string format (this answer looks like an elegant way to do that conversion from numbers to strings).
For your example, if you want s.has([3, 2]) to return false because [2,3] doesn't count as a match, the array to string conversion would look like array.join(','), otherwise array.sort().join(',') if order doesn't matter.
function setOfListsHasElement(theSet, theElement) {
let newSet = new Set();
theSet.forEach(e => newSet.add(e.join(',')) );
return newSet.has(theElement.join(','));
}
Example usage:
var theSet = new Set();
theSet.add([1,2]);
theSet.add([2,3]);
setOfListsHasElement(theSet, [2,3]); // true
setOfListsHasElement(theSet, [3,2]); // false
setOfListsHasElement(theSet, [2,6]); // false
setOfListsHasElement(theSet, ["1", "2"]); // true - don't know how you want to handle scenarios like this, where the string representation of ["1", "2"] matches that of [1,2]
I figured out how to write a custom class that does what we want:
class SetImproved extends Set{
constructor(){
super();
this.classDict = {};
this._size = 0;
}
get size(){
return this._size
}
add(x){
if(!(JSON.stringify(x) in this.classDict)){
this._size += 1;
}
this.classDict[JSON.stringify(x)] = x;
}
has(x){
return JSON.stringify(x) in this.classDict;
}
delete(x){
if(JSON.stringify(x) in this.classDict){
this._size -= 1;
}
delete this.classDict[JSON.stringify(x)];
}
clear(){
this.classDict = {};
}
keys(){
return Object.keys(this.classDict).map(x => this.classDict[x]);
}
entries(){
return Object.keys(this.classDict).map(x => this.classDict[x]);
}
}
Some examples of the functionality:
var setImproved = new SetImproved()
setImproved.add([1, "b"])
setImproved.add([2, "c"])
setImproved.add(3)
setImproved.add("asdf")
console.log(setImproved.has([1, "b"]))
console.log(setImproved.has([3]))
setImproved.delete([4])
setImproved.delete([1, "b"])
console.log(setImproved.has(3))
console.log(setImproved.entries())
console.log(setImproved.size)

Ember store adding attributes incorrectly

I'm using the latest version of ember-cli, ember-data, ember-localstorage-adapter, and ember.
I have a Node object which has a parent and children. Since I had issues with creating multiple relationships with the same type of object, I decided to store the parentID in a string, and the childIDs in an array of strings. However, when I create a new Node and try to add the new Node's to the parents array of IDs, the ID ends up being added to the correct parent, but also other parents.
level 1 0
/ \
level 2 1 2
| |
level 3 3 4
In a structure like this, 0, 1, and 2 all have correct child and parent IDs. However, after adding 3 and 4, node 1 and node 2's childIDs are [3, 4], instead of [3], [4] respectively.
The Array attribute:
var ArrayTransform = DS.Transform.extend({
serialize: function(value) {
if (!value) {
return [];
}
return value;
},
deserialize: function(value) {
if (!value) {
return [];
}
return value;
}
});
The insertNode code:
insert: function(elem) {
var i,
_store = elem.node.store,
newNodeJSON = elem.node.serialize();
newNodeJSON.childIds = [];
newNodeJSON.level = getNextLevel();
_store.filter('node', function(node) {
return node.get('level') === newnodeJSON.level-1;
}).then(function(prevLevelNodes) {
// if no other nodes yet
if (prevLevelNodes.toArray().length === 0) {
makeNewNode(_store, newNodeJSON, elem.node);
}
// else, generates however many nodes that are in the previous level
else {
prevLevelNodes.toArray().forEach(function(node, idx) {
newNodeJSON.parentId = node.get('id');
makeNewNode(_store, newNodeJSON, elem.node);
});
}
});
}
var makeNewNode = function(_store, newNodeJSON, node) {
console.log(newNodeJSON.parentId); // returns correct value
var newNode = _store.createRecord('node', newNodeJSON);
newNode.save();
var newNodeId = newNode.get('id');
if (newNode.get('parentId')) {
_store.find('node', newNode.get('parentId')).then(function(n) {
var cids = n.get('childIds');
console.log(newNodeId); // returns expected value
console.log(cids); // **DOESN'T RETURN AN EMPTY ARRAY**: returns array with [3,4]
cids.push(newNodeId);
console.log(n.get('childIds')); // returns array with [3,4]
n.save();
});
}
To top this off, this error happens 90% of the time, but 10% of the time it performs as expected. This seems to suggest that there's some sort of race condition, but I'm not sure where that would even be. Some places that I feel like might be causing issues: the ember-cli compilation, passing the entire _store in when making a new node, ember-data being weird, ember-localstorage-adapter being funky... no clue.
For anyone else who may have this problem in the future: the problem lies in two things.
In ArrayTransform, typically I am returning the value sans modification.
In my insert code, I'm passing the same JSON that I defined at the top of the function to makeNewNode.
This JSON contains a reference to a single childIds array; therefore, each new node that gets created uses this same reference for its childIds. Although this doesn't quite explain why the cids array wasn't empty before the push executed (perhaps this is some sort of compiler oddity or console printing lag), it explains why these both Level 3 children were in both Level 2 parents' childIds array.
tl;dr: pass by value vs pass by reference error

Categories

Resources