1jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
2
3describe('PathKit\'s Path2D API', function() {
4    // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
5    var PathKit = null;
6    const LoadPathKit = new Promise(function(resolve, reject) {
7        if (PathKit) {
8            resolve();
9        } else {
10            PathKitInit({
11                locateFile: (file) => '/pathkit/'+file,
12            }).ready().then((_PathKit) => {
13                PathKit = _PathKit;
14                resolve();
15            });
16        }
17    });
18
19    it('can do everything in the Path2D API w/o crashing', function(done) {
20        LoadPathKit.then(catchException(done, () => {
21            // This is taken from example.html
22            let path = PathKit.NewPath();
23
24            path.moveTo(20, 5);
25            path.lineTo(30, 20);
26            path.lineTo(40, 10);
27            path.lineTo(50, 20);
28            path.lineTo(60, 0);
29            path.lineTo(20, 5);
30
31            path.moveTo(20, 80);
32            path.bezierCurveTo(90, 10, 160, 150, 190, 10);
33
34            path.moveTo(36, 148);
35            path.quadraticCurveTo(66, 188, 120, 136);
36            path.lineTo(36, 148);
37
38            path.rect(5, 170, 20, 20);
39
40            path.moveTo(150, 180);
41            path.arcTo(150, 100, 50, 200, 20);
42            path.lineTo(160, 160);
43
44            path.moveTo(20, 120);
45            path.arc(20, 120, 18, 0, 1.75 * Math.PI);
46            path.lineTo(20, 120);
47
48            let secondPath = PathKit.NewPath();
49            secondPath.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI, false);
50
51            path.addPath(secondPath);
52
53            let m = document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGMatrix();
54            m.a = 1; m.b = 0;
55            m.c = 0; m.d = 1;
56            m.e = 0; m.f = 20.5;
57
58            path.addPath(secondPath, m);
59
60            let canvas = document.createElement('canvas');
61            let canvasCtx = canvas.getContext('2d');
62            // Set canvas size and make it a bit bigger to zoom in on the lines
63            standardizedCanvasSize(canvasCtx);
64            canvasCtx.scale(3.0, 3.0);
65            canvasCtx.fillStyle = 'blue';
66            canvasCtx.stroke(path.toPath2D());
67
68            let svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
69            svgPath.setAttribute('stroke', 'black');
70            svgPath.setAttribute('fill', 'rgba(255,255,255,0.0)');
71            svgPath.setAttribute('transform', 'scale(3.0, 3.0)');
72            svgPath.setAttribute('d', path.toSVGString());
73
74            let newSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
75            newSVG.appendChild(svgPath);
76            newSVG.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
77            newSVG.setAttribute('width', 600);
78            newSVG.setAttribute('height', 600);
79
80            path.delete();
81            secondPath.delete();
82
83            reportCanvas(canvas, 'path2D_api_example').then(() => {
84                reportSVG(newSVG, 'path2D_api_example').then(() => {
85                    done();
86                }).catch(reportError(done));
87            }).catch(reportError(done));
88        }));
89    });
90
91    it('can chain by returning the same object', function(done) {
92        LoadPathKit.then(catchException(done, () => {
93            let path = PathKit.NewPath();
94
95            let p1 = path.moveTo(20, 5)
96                .lineTo(30, 20)
97                .quadTo(66, 188, 120, 136)
98                .close();
99
100            // these should be the same object
101            expect(path === p1).toBe(true);
102            p1.delete();
103            try {
104                // This should throw an exception because
105                // the underlying path was already deleted.
106                path.delete();
107                expect('should not have gotten here').toBe(false);
108            } catch (e) {
109                // all is well
110            }
111            done();
112        }));
113    });
114
115    it('does not leak path objects when chaining', function(done) {
116        LoadPathKit.then(catchException(done, () => {
117            // By default, we have 16 MB of memory assigned to our PathKit
118            // library. This can be configured by -S TOTAL_MEMORY=NN
119            // and defaults to 16MB (we likely don't need to touch this).
120            // If there's a leak in here, we should OOM pretty quick.
121            // Testing showed around 50k is enough to see one if we leak a path,
122            // so run 250k times just to be safe.
123            for(let i = 0; i < 250000; i++) {
124                let path = PathKit.NewPath()
125                                  .moveTo(20, 5)
126                                  .lineTo(30, 20)
127                                  .quadTo(66, 188, 120, 136)
128                                  .close();
129                path.delete();
130            }
131            done();
132        }));
133    });
134
135    function drawTriangle() {
136        let path = PathKit.NewPath();
137        path.moveTo(0, 0);
138        path.lineTo(10, 0);
139        path.lineTo(10, 10);
140        path.close();
141        return path;
142    }
143
144    it('has multiple overloads of addPath', function(done) {
145        LoadPathKit.then(catchException(done, () => {
146            let basePath = PathKit.NewPath();
147            let otherPath = drawTriangle();
148            // These add path call can be chained.
149            // add it unchanged
150            basePath.addPath(otherPath)
151            // providing the 6 params of an SVG matrix to make it appear 20.5 px down
152                    .addPath(otherPath, 1, 0, 0, 1, 0, 20.5)
153            // provide the full 9 matrix params to make it appear 30 px to the right
154            // and be 3 times as big.
155                    .addPath(otherPath, 3, 0, 30,
156                                        0, 3, 0,
157                                        0, 0, 1);
158
159            reportPath(basePath, 'add_path_3x', done);
160            basePath.delete();
161            otherPath.delete();
162        }));
163    });
164
165    it('approximates arcs (conics) with quads', function(done) {
166        LoadPathKit.then(catchException(done, () => {
167            let path = PathKit.NewPath();
168            path.moveTo(50, 120);
169            path.arc(50, 120, 45, 0, 1.75 * Math.PI);
170            path.lineTo(50, 120);
171
172            let canvas = document.createElement('canvas');
173            let canvasCtx = canvas.getContext('2d');
174            standardizedCanvasSize(canvasCtx);
175            // The and.callThrough is important to make it actually
176            // draw the quadratics
177            spyOn(canvasCtx, 'quadraticCurveTo').and.callThrough();
178
179            canvasCtx.beginPath();
180            path.toCanvas(canvasCtx);
181            canvasCtx.stroke();
182            // No need to check the whole path, as that's more what the
183            // gold correctness tests are for (can account for changes we make
184            // to the approximation algorithms).
185            expect(canvasCtx.quadraticCurveTo).toHaveBeenCalled();
186            path.delete();
187            reportCanvas(canvas, 'conics_quads_approx').then(() => {
188                done();
189            }).catch(reportError(done));
190        }));
191    });
192
193});
194