1var dumpErrors = false; 2var container; 3 4function getViewBox(path) { 5 let bounds = path.getBounds(); 6 return `${(bounds.fLeft-2)*.95} ${(bounds.fTop-2)*.95} ${(bounds.fRight+2)*1.05} ${(bounds.fBottom+2)*1.05}`; 7} 8 9function addSVG(testName, expectedPath, actualPath, message) { 10 if (!dumpErrors) { 11 return; 12 } 13 if (!container) { 14 let styleEl = document.createElement('style'); 15 document.head.appendChild(styleEl); 16 let sheet = styleEl.sheet; 17 sheet.insertRule(`svg { 18 border: 1px solid #DDD; 19 max-width: 45%; 20 vertical-align: top; 21 }`, 0); 22 23 container = document.createElement('div'); 24 document.body.appendChild(container); 25 26 } 27 28 let thisTest = document.createElement('div'); 29 thisTest.innerHTML = ` 30 <h2>Failed test ${testName}</h2> 31 32 <div>${message}</div> 33 34 <svg class='expected' viewBox='${getViewBox(expectedPath)}'> 35 <path stroke=black fill=white stroke-width=0.01 d="${expectedPath.toSVGString()}"></path> 36 </svg> 37 38 <svg class='actual' viewBox='${getViewBox(actualPath)}'> 39 <path stroke=black fill=white stroke-width=0.01 d="${actualPath.toSVGString()}"></path> 40 </svg> 41`; 42 container.appendChild(thisTest); 43 44} 45 46const TOLERANCE = 0.0001; 47 48function diffPaths(expected, actual) { 49 // Look through commands and see if they are within tolerance. 50 let eCmds = expected.toCmds(), aCmds = actual.toCmds(); 51 if (eCmds.length !== aCmds.length) { 52 //console.log(`Expected: ${JSON.stringify(eCmds)} and Actual: ${JSON.stringify(aCmds)}`); 53 return `Different amount of verbs. Expected had ${eCmds.length}, Actual had ${aCmds.length}`; 54 } 55 for(let idx = 0; idx < eCmds.length; idx++){ 56 let eCmd = eCmds[idx], aCmd = aCmds[idx]; 57 if (eCmd.length !== aCmd.length) { 58 // Should never happen, means WASM code is returning bad ops. 59 return `Command index ${idx} differs in num arguments. Expected had ${eCmd.length}, Actual had ${aCmd.length}`; 60 } 61 let eVerb = eCmd[0], aVerb = aCmd[0]; 62 if (eVerb !== aVerb) { 63 return `Command index ${idx} differs. Expected had ${eVerb}, Actual had ${aVerb}`; 64 } 65 for (let arg = 1; arg < eCmd.length; arg++) { 66 if (Math.abs(eCmd[arg] - aCmd[arg]) > TOLERANCE) { 67 return `Command index ${idx} has different argument for verb ${eVerb} at position ${arg}. Expected had ${eCmd[arg]}, Actual had ${aCmd[arg]}` 68 } 69 } 70 } 71 return null; 72} 73 74describe('PathKit\'s PathOps Behavior', function() { 75 var PATHOP_MAP = {}; 76 var FILLTYPE_MAP = {}; 77 78 function init() { 79 if (PathKit && !PATHOP_MAP['kIntersect_SkPathOp']) { 80 PATHOP_MAP = { 81 'kIntersect_SkPathOp': PathKit.PathOp.INTERSECT, 82 'kDifference_SkPathOp': PathKit.PathOp.DIFFERENCE, 83 'kUnion_SkPathOp': PathKit.PathOp.UNION, 84 'kXOR_SkPathOp': PathKit.PathOp.XOR, 85 'kXOR_PathOp': PathKit.PathOp.XOR, 86 'kReverseDifference_SkPathOp': PathKit.PathOp.REVERSE_DIFFERENCE, 87 }; 88 FILLTYPE_MAP = { 89 'kWinding_FillType': PathKit.FillType.WINDING, 90 'kEvenOdd_FillType': PathKit.FillType.EVENODD, 91 'kInverseWinding_FillType': PathKit.FillType.INVERSE_WINDING, 92 'kInverseEvenOdd_FillType': PathKit.FillType.INVERSE_EVENODD, 93 }; 94 } 95 } 96 97 function getFillType(str) { 98 let e = FILLTYPE_MAP[str]; 99 expect(e).toBeTruthy(`Could not find FillType Enum for ${str}`); 100 return e; 101 } 102 103 function getPathOp(str) { 104 let e = PATHOP_MAP[str]; 105 expect(e).toBeTruthy(`Could not find PathOp Enum for ${str}`); 106 return e; 107 } 108 109 it('combines two paths with .op() and matches what we see from C++', function(done) { 110 LoadPathKit.then(catchException(done, () => { 111 init(); 112 // Test JSON created with: 113 // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsOp.json -m PathOpsOp$ 114 fetch('/base/tests/PathOpsOp.json').then((r) => { 115 r.json().then((json) => { 116 expect(json).toBeTruthy(); 117 let testNames = Object.keys(json); 118 // Assert we loaded a non-zero amount of tests, i.e. the JSON is valid. 119 expect(testNames.length > 0).toBeTruthy(); 120 testNames.sort(); 121 for (testName of testNames) { 122 let test = json[testName]; 123 124 let path1 = PathKit.FromCmds(test.p1); 125 expect(path1).not.toBeNull(`path1 error when loading cmds '${test.p1}'`); 126 path1.setFillType(getFillType(test.fillType1)); 127 128 let path2 = PathKit.FromCmds(test.p2); 129 expect(path2).not.toBeNull(`path2 error when loading cmds '${test.p2}'`); 130 path2.setFillType(getFillType(test.fillType2)); 131 132 let combined = path1.op(path2, getPathOp(test.op)); 133 134 if (test.expectSuccess === 'no') { 135 expect(combined).toBeNull(`Test ${testName} should have not created output, but did`); 136 } else { 137 expect(combined).not.toBeNull(); 138 let expected = PathKit.FromCmds(test.out); 139 // Do a tolerant match. 140 let diff = diffPaths(expected, combined); 141 if (test.expectMatch === 'yes'){ 142 // Check fill type 143 expect(combined.getFillType().value).toEqual(getFillType(test.fillTypeOut).value); 144 // diff should be null if the paths are identical (modulo rounding) 145 if (diff) { 146 expect(`[${testName}] ${diff}`).toBe(''); 147 addSVG('[PathOps] ' + testName, expected, combined, diff); 148 } 149 } else if (test.expectMatch === 'flaky') { 150 // Don't worry about it, at least it didn't crash. 151 } else { 152 if (!diff) { 153 expect(`[${testName}] was expected to have paths that differed`).not.toBe(''); 154 } 155 } 156 expected.delete(); 157 } 158 // combined === path1, so we only have to delete one. 159 path1.delete(); 160 path2.delete(); 161 } 162 done(); 163 }); 164 }); 165 })); 166 }); 167 168 it('simplifies a path with .simplify() and matches what we see from C++', function(done) { 169 LoadPathKit.then(catchException(done, () => { 170 init(); 171 // Test JSON created with: 172 // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsSimplify.json -m PathOpsSimplify$ 173 fetch('/base/tests/PathOpsSimplify.json').then((r) => { 174 r.json().then((json) => { 175 expect(json).toBeTruthy(); 176 let testNames = Object.keys(json); 177 // Assert we loaded a non-zero amount of tests, i.e. the JSON is valid. 178 expect(testNames.length > 0).toBeTruthy(); 179 testNames.sort(); 180 for (testName of testNames) { 181 let test = json[testName]; 182 183 let path = PathKit.FromCmds(test.path); 184 expect(path).not.toBeNull(`path1 error when loading cmds '${test.path}'`); 185 path.setFillType(getFillType(test.fillType)); 186 187 let simplified = path.simplify(); 188 189 if (test.expectSuccess === 'no') { 190 expect(simplified).toBeNull(`Test ${testName} should have not created output, but did`); 191 } else { 192 expect(simplified).not.toBeNull(); 193 let expected = PathKit.FromCmds(test.out); 194 // Do a tolerant match. 195 let diff = diffPaths(expected, simplified); 196 if (test.expectMatch === 'yes'){ 197 // Check fill type 198 expect(simplified.getFillType().value).toEqual(getFillType(test.fillTypeOut).value); 199 // diff should be null if the paths are identical (modulo rounding) 200 if (diff) { 201 expect(`[${testName}] ${diff}`).toBe(''); 202 addSVG('[Simplify] ' + testName, expected, simplified, diff); 203 } 204 } else if (test.expectMatch === 'flaky') { 205 // Don't worry about it, at least it didn't crash. 206 } else { 207 if (!diff) { 208 expect(`[${testName}] was expected to not match output`).not.toBe(''); 209 } 210 } 211 expected.delete(); 212 } 213 // simplified === path, so we only have to delete one. 214 path.delete(); 215 } 216 done(); 217 }); 218 }); 219 })); 220 }); 221}); 222