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