From 644fb71dc17a8e36d4eea007beee6e6181c7f2bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ju=CC=88rg=20Lehni?= <juerg@scratchdisk.com>
Date: Sun, 28 Dec 2014 18:10:53 +0100
Subject: [PATCH] Implement unit test comparator for Item & co + Project, and
 further improve new comparator code.

---
 test/lib/helpers.js        | 256 ++++++++++++++++---------------------
 test/tests/Item_Cloning.js |   4 +-
 test/tests/JSON.js         |   2 +-
 test/tests/SVGImport.js    |  38 +++---
 4 files changed, 132 insertions(+), 168 deletions(-)

diff --git a/test/lib/helpers.js b/test/lib/helpers.js
index 8219be1b..159c0a55 100644
--- a/test/lib/helpers.js
+++ b/test/lib/helpers.js
@@ -27,6 +27,34 @@ QUnit.jsDump.setParser('object', function (obj, stack) {
             : objectParser).call(this, obj, stack);
 });
 
+function compareProperties(actual, expected, properties, message, options) {
+    Base.each(properties, function(key) {
+        equals(actual[key], expected[key], message + '.' + key, options);
+    });
+}
+
+function compareItem(actual, expected, message, options, properties) {
+    if (options && options.cloned)
+        QUnit.notStrictEqual(actual.id, 'not ' + expected.id, message + '.id');
+    QUnit.strictEqual(actual.constructor, expected.constructor,
+            message + '.constructor');
+    // When item was cloned and had a name, the name will be versioned
+    equals(options && options.cloned && actual.name ? actual.name + ' 1'
+            : actual.name, expected.name,
+            message + '.name');
+    compareProperties(actual, expected, ['children', 'bounds', 'position',
+            'matrix', 'data', 'opacity', 'locked', 'visible', 'blendMode',
+            'selected', 'fullySelected', 'clipMask', 'guide'],
+            message, options);
+    if (properties)
+        compareProperties(actual, expected, properties, message, options);
+    // Style
+    compareProperties(actual.style, expected.style, ['fillColor', 'strokeColor',
+            'strokeCap', 'strokeJoin', 'dashArray', 'dashOffset', 'miterLimit',
+            'fontSize', 'font', 'leading', 'justification'],
+            message + '.style', options);
+}
+
 var comparators = {
     Null: QUnit.strictEqual,
     Undefined: QUnit.strictEqual,
@@ -48,26 +76,23 @@ var comparators = {
     },
 
     Array: function(actual, expected, message, options) {
-        QUnit.strictEqual(actual.length, expected.length,
-            (message || '') + ' length');
+        QUnit.strictEqual(actual.length, expected.length, message + '.length');
         for (var i = 0, l = actual.length; i < l; i++) {
-            equals(actual[i], expected[i], (message || '') + ' [' + i + ']',
+            equals(actual[i], expected[i], (message || '') + '[' + i + ']',
                 options);
         }
     },
 
     Point: function(actual, expected, message, options) {
-        comparators.Number(actual.x, expected.x, (message || '') + ' x',
-            options);
-        comparators.Number(actual.y, expected.y, (message || '') + ' y',
-            options);
+        comparators.Number(actual.x, expected.x, message + '.x', options);
+        comparators.Number(actual.y, expected.y, message + '.y', options);
     },
 
     Size: function(actual, expected, message, options) {
-        comparators.Number(actual.width, expected.width,
-                (message || '') + ' width', options);
-        comparators.Number(actual.height, expected.height,
-                (message || '') + ' height', options);
+        comparators.Number(actual.width, expected.width, message + '.width',
+                options);
+        comparators.Number(actual.height, expected.height, message + '.height',
+                options);
     },
 
     Rectangle: function(actual, expected, message, options) {
@@ -81,20 +106,24 @@ var comparators = {
 
     Color: function(actual, expected, message, options) {
         if (actual && expected) {
-            equals(actual.type, expected.type,
-                    (message || '') + ' type', options);
+            equals(actual.type, expected.type, message + '.type', options);
             // NOTE: This also compares gradients, with identity checks and all.
             equals(actual.components, expected.components,
-                    (message || '') + ' components', options);
+                    message + '.components', options);
         } else {
-            equals(actual, expected, message, options);
+            QUnit.strictEqual(actual, expected, message);
         }
     },
 
+    Symbol: function(actual, expected, message, options) {
+        equals(actual.definition, expected.definition, message + '.definition',
+                options);
+    },
+
     Segment: function(actual, expected, message, options) {
         Base.each(['handleIn', 'handleOut', 'point', 'selected'],
             function(key) {
-                equals(actual[key], expected[key], (message || '') + ' ' + key);
+                equals(actual[key], expected[key], message + '.' + key);
             }
         );
     },
@@ -102,11 +131,67 @@ var comparators = {
     SegmentPoint: function(actual, expected, message, options) {
         comparators.Point(actual, expected, message, options);
         comparators.Boolean(actual.selected, expected.selected,
-                (message || '') + ' selected', options);
+                message + '.selected', options);
+    },
+
+    Item: compareItem,
+
+    Group: function(actual, expected, message, options) {
+        compareItem(actual, expected, message, options,
+                ['clipped']);
+    },
+
+    Layer: function(actual, expected, message, options) {
+        compareItem(actual, expected, message, options);
+        equals(function() {
+            return options && options.dontShareProject
+                    ? actual.project !== expected.project
+                    : actual.project === expected.project;
+        }, true);
+    },
+
+    Path: function(actual, expected, message, options) {
+        compareItem(actual, expected, message, options,
+                ['segments', 'closed', 'clockwise', 'length']);
+    },
+
+    CompoundPath: function(actual, expected, message, options) {
+        compareItem(actual, expected, message, options);
+    },
+
+    Raster: function(actual, expected, message, options) {
+        compareItem(actual, expected, message, options,
+                ['size', 'width', 'height', 'ppi', 'source', 'image']);
+        equals(actual.toDataURL(), expected.toDataURL(),
+                message + '.toDataUrl()');
+    },
+
+    Shape: function(actual, expected, message, options) {
+        compareItem(actual, expected, message, options,
+                ['shape', 'size', 'radius']);
+    },
+
+    PlacedSymbol: function(actual, expected, message, options) {
+        compareItem(actual, expected, message,
+                // Cloning PlacedSymbols does not result in cloned Symbols
+                options && options.cloned
+                        ? new Base(options, { cloned: false })
+                        : options,
+                ['symbol']);
+    },
+
+    PointText: function(actual, expected, message, options) {
+        compareItem(actual, expected, message, options,
+                ['content', 'point']);
+    },
+
+    Project: function(actual, expected, message, options) {
+        compareProperties(actual, expected, ['symbols', 'layers'],
+                message, options);
     }
 };
 
-var identicalAfterClone = {
+var strictIdenticalAfterCloning = {
     Gradient: true,
     Symbol: true
 };
@@ -145,6 +230,11 @@ function equals(actual, expected, message, options) {
             || (cls = expected && expected._class)
             || type === 'object' && 'Object';
     var comparator = type && comparators[type];
+    if (!message) {
+        message = type
+                ? type.charAt(0).toLowerCase() + type.substring(1)
+                : 'value';
+    }
     if (comparator) {
         comparator(actual, expected, message, options);
     } else if (expected && expected.equals) {
@@ -155,10 +245,10 @@ function equals(actual, expected, message, options) {
         QUnit.push(actual === expected, actual, expected, message);
     }
     if (options && options.cloned && cls) {
-        var identical = identicalAfterClone[cls];
+        var identical = strictIdenticalAfterCloning[cls];
         QUnit.push(identical ? actual === expected : actual !== expected,
                 actual, identical ? expected : 'not ' + expected,
-                (message || '') + ' identity');
+                message + ': identical after cloning');
     }
 }
 
@@ -180,132 +270,6 @@ function asyncTest(testName, expected) {
     });
 }
 
-function compareItems(item, item2, options) {
-    if (options && options.cloned)
-        QUnit.notStrictEqual(item.id, item2.id, 'Compare Item#id');
-
-    QUnit.strictEqual(item.constructor, item2.constructor,
-            'Compare Item#constructor');
-    // When item was cloned and had a name, the name will be versioned
-    equals(options && options.cloned && item.name ? item.name + ' 1'
-            : item.name, item2.name, 'Compare Item#name');
-    Base.each(['bounds', 'position', 'data', 'matrix', 'opacity', 'locked',
-            'visible', 'blendMode', 'selected', 'fullySelected', 'clipMask',
-            'guide'],
-        function(key) {
-            equals(item[key], item2[key],  'Compare Item#' + key, options);
-        }
-    );
-
-    // Style
-    Base.each(['fillColor', 'strokeColor', 'strokeCap', 'strokeJoin',
-            'dashArray', 'dashOffset', 'miterLimit',
-            'fontSize', 'font', 'leading', 'justification'],
-        function(key) {
-            equals(item.style[key], item2.style[key], 'Compare Style#' + key,
-                options);
-        }
-    );
-
-    // Path specific
-    if (item instanceof Path) {
-        Base.each(['segments', 'closed', 'clockwise', 'length'],
-            function(key) {
-                equals(item[key], item2[key], 'Compare Path#' + key, options);
-            }
-        );
-    }
-
-    // Shape specific
-    if (item instanceof Shape) {
-        Base.each(['shape', 'size', 'radius'],
-            function(key) {
-                equals(item[key], item2[key], 'Compare Shape#' + key, options);
-            }
-        );
-    }
-
-    // Group specific
-    if (item instanceof Group) {
-        equals(item.clipped, item2.clipped, 'Compare Group#clipped', options);
-    }
-
-    // Layer specific
-    if (item instanceof Layer) {
-        equals(function() {
-            return options && options.dontShareProject
-                    ? item.project != item2.project
-                    : item.project == item2.project;
-        }, true);
-    }
-
-    // PlacedSymbol specific
-    if (item instanceof PlacedSymbol) {
-        if (options.dontShareProject) {
-            compareItems(item.symbol.definition, item2.symbol.definition,
-                    options,
-                    'Compare Symbol#definition');
-        } else {
-            equals(item.symbol, item2.symbol, 'Compare PlacedSymbol#symbol',
-                options);
-        }
-    }
-
-    // Raster specific
-    if (item instanceof Raster) {
-        equals(item.size, item2.size, 'Compare Raster#size');
-        equals(item.width, item2.width, 'Compare Raster#width');
-        equals(item.height, item2.height, 'Compare Raster#height');
-        equals(item.ppi, item2.ppi, 'Compare Raster#ppi');
-        equals(item.source, item2.source, 'Compare Raster#source');
-        equals(item.image, item2.image, 'Compare Raster#image');
-        equals(item.toDataURL(), item2.toDataURL(),
-                'Compare Raster#toDataUrl()');
-    }
-
-    // TextItem specific:
-    if (item instanceof TextItem) {
-        equals(item.content, item2.content, 'Compare Item#content');
-    }
-
-    // PointText specific:
-    if (item instanceof PointText) {
-        equals(item.point, item2.point, 'Compare Item#point');
-    }
-
-    // Check length of children and recursively compare them:
-    if (item.children) {
-        equals(function() {
-            return item.children.length == item2.children.length;
-        }, true);
-        for (var i = 0, l = item.children.length; i < l; i++) {
-            compareItems(item.children[i], item2.children[i], options);
-        }
-    }
-}
-
-function compareProjects(project, project2) {
-    // Compare Project#symbols:
-    equals(function() {
-        return project.symbols.length == project2.symbols.length;
-    }, true);
-    for (var i = 0, l = project.symbols.length; i < l; i++) {
-        var definition1 = project.symbols[i].definition;
-        var definition2 = project2.symbols[i].definition;
-        compareItems(definition1, definition2, { dontShareProject: true },
-                'Compare Symbol#definition');
-    }
-
-    // Compare Project#layers:
-    equals(function() {
-        return project.layers.length == project2.layers.length;
-    }, true);
-    for (var i = 0, l = project.layers.length; i < l; i++) {
-        compareItems(project.layers[i], project2.layers[i],
-                { dontShareProject: true });
-    }
-}
-
 // SVG
 
 function createSVG(xml) {
diff --git a/test/tests/Item_Cloning.js b/test/tests/Item_Cloning.js
index 884a08e2..d2076126 100644
--- a/test/tests/Item_Cloning.js
+++ b/test/tests/Item_Cloning.js
@@ -24,7 +24,7 @@ function cloneAndCompare(item) {
             return copy.parent.children[copy.name] == copy;
         }, true);
     }
-    compareItems(item, copy, { cloned: true });
+    equals(item, copy, 'item.clone()', { cloned: true });
     // Remove the cloned item to restore the document:
     copy.remove();
 }
@@ -134,7 +134,7 @@ test('Symbol#clone()', function() {
     path.selected = true;
     var symbol = new Symbol(path);
     var copy = symbol.clone();
-    compareItems(symbol.definition, copy.definition);
+    equals(symbol.definition, copy.definition, 'symbol.definition');
     equals(function() {
         return symbol.project == copy.project;
     }, true);
diff --git a/test/tests/JSON.js b/test/tests/JSON.js
index 7ec2eebd..00712cba 100644
--- a/test/tests/JSON.js
+++ b/test/tests/JSON.js
@@ -17,7 +17,7 @@ function testExportImportJSON(project) {
     var json = project.exportJSON({ precision: 8 });
     var project2 = new Project();
     project2.importJSON(json);
-    compareProjects(project2, project);
+    equals(project, project2, null, { dontShareProject: true });
 }
 
 test('Circles', function() {
diff --git a/test/tests/SVGImport.js b/test/tests/SVGImport.js
index 9d6f3dcc..52a43b47 100644
--- a/test/tests/SVGImport.js
+++ b/test/tests/SVGImport.js
@@ -15,7 +15,7 @@ module('SVGImport');
 test('Import complex CompoundPath and clone', function() {
     var svg = createSVG('<path id="path" fill="red" d="M4,14h20v-2H4V14z M15,26h7v-2h-7V26z M15,22h9v-2h-9V22z M15,18h9v-2h-9V18z M4,26h9V16H4V26z M28,10V6H0v22c0,0,0,4,4,4 h25c0,0,3-0.062,3-4V10H28z M4,30c-2,0-2-2-2-2V8h24v20c0,0.921,0.284,1.558,0.676,2H4z"/>;');
     var item = paper.project.importSVG(svg.getElementById('path'));
-    compareItems(item, item.clone(), { cloned: true });
+    equals(item, item.clone(), null, { cloned: true });
 });
 
 test('make an svg line', function() {
@@ -34,7 +34,7 @@ test('make an svg line', function() {
 
     var line = new Path.Line([x1, y1], [x2, y2]);
 
-    compareItems(importedLine, line);
+    equals(importedLine, line);
 });
 
 test('make an svg line with invalid values', function() {
@@ -49,7 +49,7 @@ test('make an svg line with invalid values', function() {
 
     var line = new Path.Line([0, 0], [0, 0]);
 
-    compareItems(importedLine, line);
+    equals(importedLine, line);
 });
 
 test('compare rectangle values', function() {
@@ -71,7 +71,7 @@ test('compare rectangle values', function() {
     var rectangle = new Rectangle(topLeft, size);
     var realRectangle = new Shape.Rectangle(rectangle);
 
-    compareItems(importedRectangle, realRectangle);
+    equals(importedRectangle, realRectangle);
 });
 
 
@@ -93,7 +93,7 @@ test('compare negative rectangle values', function() {
         var rectangle = new Rectangle(topLeft, size);
         var realRectangle = new Shape.Rectangle(rectangle);
 
-    compareItems(importedRectangle, realRectangle);
+    equals(importedRectangle, realRectangle);
 });
 
 
@@ -113,7 +113,7 @@ test('compare invalid rectangle values', function() {
     var rectangle = new Rectangle(topLeft, size);
     var realRectangle = new Shape.Rectangle(rectangle);
 
-    compareItems(importedRectangle, realRectangle);
+    equals(importedRectangle, realRectangle);
 });
 
 test('compare round rectangle values', function() {
@@ -140,7 +140,7 @@ test('compare round rectangle values', function() {
     var rectangle = new Rectangle(topLeft, size);
     var roundRect = new Shape.Rectangle(rectangle, cornerSize);
 
-    compareItems(importedRectangle, roundRect);
+    equals(importedRectangle, roundRect);
 });
 
 test('compare negative round rectangle values', function() {
@@ -167,7 +167,7 @@ test('compare negative round rectangle values', function() {
     var rectangle = new Rectangle(topLeft, size);
     var roundRect = new Shape.Rectangle(rectangle, cornerSize);
 
-    compareItems(importedRectangle, roundRect);
+    equals(importedRectangle, roundRect);
 });
 
 test('compare invalid round rectangle values', function() {
@@ -194,7 +194,7 @@ test('compare invalid round rectangle values', function() {
     var rectangle = new Rectangle(topLeft, size);
     var roundRect = new Shape.Rectangle(rectangle, cornerSize);
 
-    compareItems(importedRectangle, roundRect);
+    equals(importedRectangle, roundRect);
 });
 
 test('compare ellipse values', function() {
@@ -216,7 +216,7 @@ test('compare ellipse values', function() {
         radius: new Point(rx, ry)
     });
 
-    compareItems(importedEllipse, ellipse);
+    equals(importedEllipse, ellipse);
 });
 
 test('compare negative ellipse values', function() {
@@ -238,7 +238,7 @@ test('compare negative ellipse values', function() {
         radius: new Point(rx, ry)
     });
 
-    compareItems(importedEllipse, ellipse);
+    equals(importedEllipse, ellipse);
 });
 
 test('compare invalid ellipse values', function() {
@@ -256,7 +256,7 @@ test('compare invalid ellipse values', function() {
         radius: new Point(0, 0)
     });
 
-    compareItems(importedEllipse, ellipse);
+    equals(importedEllipse, ellipse);
 });
 
 test('compare circle values', function() {
@@ -274,7 +274,7 @@ test('compare circle values', function() {
     var center = new Point(cx, cy);
     var circle = new Shape.Circle(center, r);
 
-    compareItems(importedCircle, circle);
+    equals(importedCircle, circle);
 });
 
 test('compare negative circle values', function() {
@@ -292,7 +292,7 @@ test('compare negative circle values', function() {
     var center = new Point(cx, cy);
     var circle = new Shape.Circle(center, r);
 
-    compareItems(importedCircle, circle);
+    equals(importedCircle, circle);
 });
 
 
@@ -308,7 +308,7 @@ test('compare invalid circle values', function() {
     var center = new Point(0, 0);
     var circle = new Shape.Circle(center, 0);
 
-    compareItems(importedCircle, circle);
+    equals(importedCircle, circle);
 
 });
 
@@ -330,7 +330,7 @@ test('compare polygon values', function() {
         poly.closePath();
     }
 
-    compareItems(importedPolygon, poly);
+    equals(importedPolygon, poly);
 });
 
 test('compare negative polygon values', function() {
@@ -351,7 +351,7 @@ test('compare negative polygon values', function() {
         poly.closePath();
     }
 
-    compareItems(importedPolygon, poly);
+    equals(importedPolygon, poly);
 });
 
 test('compare polyline values', function() {
@@ -372,7 +372,7 @@ test('compare polyline values', function() {
         poly.closePath();
     }
 
-    compareItems(importedPolyline, poly);
+    equals(importedPolyline, poly);
 });
 
 test('compare negative polyline values', function() {
@@ -393,5 +393,5 @@ test('compare negative polyline values', function() {
         poly.closePath();
     }
 
-    compareItems(importedPolyline, poly);
+    equals(importedPolyline, poly);
 });