diff --git a/package.json b/package.json index 5b573bb1353..e4bd5dddcc1 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "brfs": "^1.4.3", "browserify": "^13.0.0", "browserify-transform-tools": "^1.5.1", + "deep-equal": "^1.0.1", "ecstatic": "^1.4.0", "eslint": "^3.5.0", "falafel": "^1.2.0", diff --git a/test/jasmine/assets/custom_matchers.js b/test/jasmine/assets/custom_matchers.js index 675bc02f70c..5aeb2de7764 100644 --- a/test/jasmine/assets/custom_matchers.js +++ b/test/jasmine/assets/custom_matchers.js @@ -1,9 +1,53 @@ 'use strict'; var isNumeric = require('fast-isnumeric'); - +var Lib = require('@src/lib'); +var deepEqual = require('deep-equal'); module.exports = { + // toEqual except with sparse arrays populated. This arises because: + // + // var x = new Array(2) + // expect(x).toEqual([undefined, undefined]) + // + // will fail assertion even though x[0] === undefined and x[1] === undefined. + // This is because the array elements don't exist until assigned. Of course it + // only fails on *some* platforms (old firefox, looking at you), which is why + // this is worth all the footwork. + toLooseDeepEqual: function() { + function populateUndefinedArrayEls(x) { + var i; + if(Array.isArray(x)) { + for(i = 0; i < x.length; i++) { + x[i] = x[i]; + } + } else if(Lib.isPlainObject(x)) { + var keys = Object.keys(x); + for(i = 0; i < keys.length; i++) { + populateUndefinedArrayEls(x[keys[i]]); + } + } + return x; + } + + return { + compare: function(actual, expected, msgExtra) { + var actualExpanded = populateUndefinedArrayEls(Lib.extendDeep({}, actual)); + var expectedExpanded = populateUndefinedArrayEls(Lib.extendDeep({}, expected)); + + var passed = deepEqual(actualExpanded, expectedExpanded); + + var message = [ + 'Expected', JSON.stringify(actual), 'to be close to', JSON.stringify(expected), msgExtra + ].join(' '); + + return { + pass: passed, + message: message + }; + } + }; + }, // toBeCloseTo... but for arrays toBeCloseToArray: function() { diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 1cdf8b20f98..815e0fd3b4f 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -7,7 +7,7 @@ var PlotlyInternal = require('@src/plotly'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var Plots = PlotlyInternal.Plots; - +var customMatchers = require('../assets/custom_matchers'); describe('Test lib.js:', function() { 'use strict'; @@ -477,6 +477,10 @@ describe('Test lib.js:', function() { }); describe('expandObjectPaths', function() { + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); + it('returns the original object', function() { var x = {}; expect(Lib.expandObjectPaths(x)).toBe(x); @@ -485,37 +489,37 @@ describe('Test lib.js:', function() { it('unpacks top-level paths', function() { var input = {'marker.color': 'red', 'marker.size': [1, 2, 3]}; var expected = {marker: {color: 'red', size: [1, 2, 3]}}; - expect(Lib.expandObjectPaths(input)).toEqual(expected); + expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); it('unpacks recursively', function() { var input = {'marker.color': {'red.certainty': 'definitely'}}; var expected = {marker: {color: {red: {certainty: 'definitely'}}}}; - expect(Lib.expandObjectPaths(input)).toEqual(expected); + expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); it('unpacks deep paths', function() { var input = {'foo.bar.baz': 'red'}; var expected = {foo: {bar: {baz: 'red'}}}; - expect(Lib.expandObjectPaths(input)).toEqual(expected); + expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); it('unpacks non-top-level deep paths', function() { var input = {color: {'foo.bar.baz': 'red'}}; var expected = {color: {foo: {bar: {baz: 'red'}}}}; - expect(Lib.expandObjectPaths(input)).toEqual(expected); + expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); it('merges dotted properties into objects', function() { var input = {marker: {color: 'red'}, 'marker.size': 8}; var expected = {marker: {color: 'red', size: 8}}; - expect(Lib.expandObjectPaths(input)).toEqual(expected); + expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); it('merges objects into dotted properties', function() { var input = {'marker.size': 8, marker: {color: 'red'}}; var expected = {marker: {color: 'red', size: 8}}; - expect(Lib.expandObjectPaths(input)).toEqual(expected); + expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); it('retains the identity of nested objects', function() { @@ -541,49 +545,49 @@ describe('Test lib.js:', function() { it('expands bracketed array notation', function() { var input = {'marker[1]': {color: 'red'}}; var expected = {marker: [undefined, {color: 'red'}]}; - expect(Lib.expandObjectPaths(input)).toEqual(expected); + expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); it('expands nested arrays', function() { var input = {'marker[1].range[1]': 5}; var expected = {marker: [undefined, {range: [undefined, 5]}]}; var computed = Lib.expandObjectPaths(input); - expect(computed).toEqual(expected); + expect(computed).toLooseDeepEqual(expected); }); it('expands bracketed array with more nested attributes', function() { var input = {'marker[1]': {'color.alpha': 2}}; var expected = {marker: [undefined, {color: {alpha: 2}}]}; var computed = Lib.expandObjectPaths(input); - expect(computed).toEqual(expected); + expect(computed).toLooseDeepEqual(expected); }); it('expands bracketed array notation without further nesting', function() { var input = {'marker[1]': 8}; var expected = {marker: [undefined, 8]}; var computed = Lib.expandObjectPaths(input); - expect(computed).toEqual(expected); + expect(computed).toLooseDeepEqual(expected); }); it('expands bracketed array notation with further nesting', function() { var input = {'marker[1].size': 8}; var expected = {marker: [undefined, {size: 8}]}; var computed = Lib.expandObjectPaths(input); - expect(computed).toEqual(expected); + expect(computed).toLooseDeepEqual(expected); }); it('expands bracketed array notation with further nesting', function() { var input = {'marker[1].size.magnitude': 8}; var expected = {marker: [undefined, {size: {magnitude: 8}}]}; var computed = Lib.expandObjectPaths(input); - expect(computed).toEqual(expected); + expect(computed).toLooseDeepEqual(expected); }); it('combines changes with single array nesting', function() { var input = {'marker[1].foo': 5, 'marker[0].foo': 4}; var expected = {marker: [{foo: 4}, {foo: 5}]}; var computed = Lib.expandObjectPaths(input); - expect(computed).toEqual(expected); + expect(computed).toLooseDeepEqual(expected); }); // TODO: This test is unimplemented since it's a currently-unused corner case.