jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000; var dumpErrors = false; var container; function getViewBox(path) { let bounds = path.getBounds(); return `${(bounds.fLeft-2)*.95} ${(bounds.fTop-2)*.95} ${(bounds.fRight+2)*1.05} ${(bounds.fBottom+2)*1.05}`; } function addSVG(testName, expectedPath, actualPath, message) { if (!dumpErrors) { return; } if (!container) { let styleEl = document.createElement('style'); document.head.appendChild(styleEl); let sheet = styleEl.sheet; sheet.insertRule(`svg { border: 1px solid #DDD; max-width: 45%; vertical-align: top; }`, 0); container = document.createElement('div'); document.body.appendChild(container); } let thisTest = document.createElement('div'); thisTest.innerHTML = ` <h2>Failed test ${testName}</h2> <div>${message}</div> <svg class='expected' viewBox='${getViewBox(expectedPath)}'> <path stroke=black fill=white stroke-width=0.01 d="${expectedPath.toSVGString()}"></path> </svg> <svg class='actual' viewBox='${getViewBox(actualPath)}'> <path stroke=black fill=white stroke-width=0.01 d="${actualPath.toSVGString()}"></path> </svg> `; container.appendChild(thisTest); } const TOLERANCE = 0.0001; function diffPaths(expected, actual) { // Look through commands and see if they are within tolerance. let eCmds = expected.toCmds(), aCmds = actual.toCmds(); if (eCmds.length !== aCmds.length) { //console.log(`Expected: ${JSON.stringify(eCmds)} and Actual: ${JSON.stringify(aCmds)}`); return `Different amount of verbs. Expected had ${eCmds.length}, Actual had ${aCmds.length}`; } for(let idx = 0; idx < eCmds.length; idx++){ let eCmd = eCmds[idx], aCmd = aCmds[idx]; if (eCmd.length !== aCmd.length) { // Should never happen, means WASM code is returning bad ops. return `Command index ${idx} differs in num arguments. Expected had ${eCmd.length}, Actual had ${aCmd.length}`; } let eVerb = eCmd[0], aVerb = aCmd[0]; if (eVerb !== aVerb) { return `Command index ${idx} differs. Expected had ${eVerb}, Actual had ${aVerb}`; } for (let arg = 1; arg < eCmd.length; arg++) { if (Math.abs(eCmd[arg] - aCmd[arg]) > TOLERANCE) { return `Command index ${idx} has different argument for verb ${eVerb} at position ${arg}. Expected had ${eCmd[arg]}, Actual had ${aCmd[arg]}` } } } return null; } describe('PathKit\'s PathOps Behavior', function() { // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up. var PathKit = null; var PATHOP_MAP = {}; var FILLTYPE_MAP = {}; const LoadPathKit = new Promise(function(resolve, reject) { if (PathKit) { resolve(); } else { PathKitInit({ locateFile: (file) => '/pathkit/'+file, }).ready().then((_PathKit) => { PathKit = _PathKit; PATHOP_MAP = { 'kIntersect_SkPathOp': PathKit.PathOp.INTERSECT, 'kDifference_SkPathOp': PathKit.PathOp.DIFFERENCE, 'kUnion_SkPathOp': PathKit.PathOp.UNION, 'kXOR_SkPathOp': PathKit.PathOp.XOR, 'kXOR_PathOp': PathKit.PathOp.XOR, 'kReverseDifference_SkPathOp': PathKit.PathOp.REVERSE_DIFFERENCE, }; FILLTYPE_MAP = { 'kWinding_FillType': PathKit.FillType.WINDING, 'kEvenOdd_FillType': PathKit.FillType.EVENODD, 'kInverseWinding_FillType': PathKit.FillType.INVERSE_WINDING, 'kInverseEvenOdd_FillType': PathKit.FillType.INVERSE_EVENODD, }; resolve(); }); } }); function getFillType(str) { let e = FILLTYPE_MAP[str]; expect(e).toBeTruthy(`Could not find FillType Enum for ${str}`); return e; } function getPathOp(str) { let e = PATHOP_MAP[str]; expect(e).toBeTruthy(`Could not find PathOp Enum for ${str}`); return e; } it('combines two paths with .op() and matches what we see from C++', function(done) { LoadPathKit.then(catchException(done, () => { // Test JSON created with: // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsOp.json -m PathOpsOp$ fetch('/base/tests/PathOpsOp.json').then((r) => { r.json().then((json) => { expect(json).toBeTruthy(); let testNames = Object.keys(json); // Assert we loaded a non-zero amount of tests, i.e. the JSON is valid. expect(testNames.length > 0).toBeTruthy(); testNames.sort(); for (testName of testNames) { let test = json[testName]; let path1 = PathKit.FromCmds(test.p1); expect(path1).not.toBeNull(`path1 error when loading cmds '${test.p1}'`); path1.setFillType(getFillType(test.fillType1)); let path2 = PathKit.FromCmds(test.p2); expect(path2).not.toBeNull(`path2 error when loading cmds '${test.p2}'`); path2.setFillType(getFillType(test.fillType2)); let combined = path1.op(path2, getPathOp(test.op)); if (test.expectSuccess === 'no') { expect(combined).toBeNull(`Test ${testName} should have not created output, but did`); } else { expect(combined).not.toBeNull(); let expected = PathKit.FromCmds(test.out); // Do a tolerant match. let diff = diffPaths(expected, combined); if (test.expectMatch === 'yes'){ // Check fill type expect(combined.getFillType().value).toEqual(getFillType(test.fillTypeOut).value); // diff should be null if the paths are identical (modulo rounding) if (diff) { expect(`[${testName}] ${diff}`).toBe(''); addSVG('[PathOps] ' + testName, expected, combined, diff); } } else if (test.expectMatch === 'flaky') { // Don't worry about it, at least it didn't crash. } else { if (!diff) { expect(`[${testName}] was expected to have paths that differed`).not.toBe(''); } } expected.delete(); } // combined === path1, so we only have to delete one. path1.delete(); path2.delete(); } done(); }); }); })); }); it('simplifies a path with .simplify() and matches what we see from C++', function(done) { LoadPathKit.then(catchException(done, () => { // Test JSON created with: // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsSimplify.json -m PathOpsSimplify$ fetch('/base/tests/PathOpsSimplify.json').then((r) => { r.json().then((json) => { expect(json).toBeTruthy(); let testNames = Object.keys(json); // Assert we loaded a non-zero amount of tests, i.e. the JSON is valid. expect(testNames.length > 0).toBeTruthy(); testNames.sort(); for (testName of testNames) { let test = json[testName]; let path = PathKit.FromCmds(test.path); expect(path).not.toBeNull(`path1 error when loading cmds '${test.path}'`); path.setFillType(getFillType(test.fillType)); let simplified = path.simplify(); if (test.expectSuccess === 'no') { expect(simplified).toBeNull(`Test ${testName} should have not created output, but did`); } else { expect(simplified).not.toBeNull(); let expected = PathKit.FromCmds(test.out); // Do a tolerant match. let diff = diffPaths(expected, simplified); if (test.expectMatch === 'yes'){ // Check fill type expect(simplified.getFillType().value).toEqual(getFillType(test.fillTypeOut).value); // diff should be null if the paths are identical (modulo rounding) if (diff) { expect(`[${testName}] ${diff}`).toBe(''); addSVG('[Simplify] ' + testName, expected, simplified, diff); } } else if (test.expectMatch === 'flaky') { // Don't worry about it, at least it didn't crash. } else { if (!diff) { expect(`[${testName}] was expected to not match output`).not.toBe(''); } } expected.delete(); } // simplified === path, so we only have to delete one. path.delete(); } done(); }); }); })); }); });