1describe('Skottie behavior', () => { 2 let container; 3 4 beforeEach(async () => { 5 await EverythingLoaded; 6 container = document.createElement('div'); 7 container.innerHTML = ` 8 <canvas width=600 height=600 id=test></canvas> 9 <canvas width=600 height=600 id=report></canvas>`; 10 document.body.appendChild(container); 11 }); 12 13 afterEach(() => { 14 document.body.removeChild(container); 15 }); 16 17 const expectArrayCloseTo = (a, b, precision) => { 18 precision = precision || 14; // digits of precision in base 10 19 expect(a.length).toEqual(b.length); 20 for (let i=0; i<a.length; i++) { 21 expect(a[i]).toBeCloseTo(b[i], precision); 22 } 23 }; 24 25 const imgPromise = fetch('/assets/flightAnim.gif') 26 .then((response) => response.arrayBuffer()); 27 const jsonPromise = fetch('/assets/animated_gif.json') 28 .then((response) => response.text()); 29 const washPromise = fetch('/assets/map-shield.json') 30 .then((response) => response.text()); 31 32 gm('skottie_animgif', (canvas, promises) => { 33 if (!CanvasKit.skottie || !CanvasKit.managed_skottie) { 34 console.warn('Skipping test because not compiled with skottie'); 35 return; 36 } 37 expect(promises[1]).not.toBe('NOT FOUND'); 38 const animation = CanvasKit.MakeManagedAnimation(promises[1], { 39 'flightAnim.gif': promises[0], 40 }); 41 expect(animation).toBeTruthy(); 42 const bounds = CanvasKit.LTRBRect(0, 0, 500, 500); 43 44 const size = animation.size(); 45 expectArrayCloseTo(size, Float32Array.of(800, 600), 4); 46 47 animation.render(canvas, bounds); 48 49 // We intentionally make the length of this array 5 and add a sentinel value 50 // of 999 so we can make sure the bounds are copied into this rect and a new 51 // one is not allocated. 52 const damageRect = Float32Array.of(0, 0, 0, 0, 999); 53 54 // There was a bug, fixed in https://skia-review.googlesource.com/c/skia/+/241757 55 // that seeking again and drawing again revealed. 56 animation.seek(0.5, damageRect); 57 expectArrayCloseTo(damageRect, Float32Array.of(0, 0, 800, 600, 999), 4); 58 59 canvas.clear(CanvasKit.WHITE); 60 animation.render(canvas, bounds); 61 animation.delete(); 62 }, imgPromise, jsonPromise); 63 64 gm('skottie_setcolor', (canvas, promises) => { 65 if (!CanvasKit.skottie || !CanvasKit.managed_skottie) { 66 console.warn('Skipping test because not compiled with skottie'); 67 return; 68 } 69 expect(promises[0]).not.toBe('NOT FOUND'); 70 const bounds = CanvasKit.LTRBRect(0, 0, 500, 500); 71 72 const animation = CanvasKit.MakeManagedAnimation(promises[0]); 73 expect(animation).toBeTruthy(); 74 animation.setColor('$Icon Fill', CanvasKit.RED); 75 animation.seek(0.5); 76 animation.render(canvas, bounds); 77 animation.delete(); 78 }, washPromise); 79 80 it('can load audio assets', (done) => { 81 if (!CanvasKit.skottie || !CanvasKit.managed_skottie) { 82 console.warn('Skipping test because not compiled with skottie'); 83 return; 84 } 85 const mockSoundMap = { 86 map : new Map(), 87 getPlayer : function(name) {return this.map.get(name)}, 88 setPlayer : function(name, player) {this.map.set(name, player)}, 89 }; 90 function mockPlayer(name) { 91 this.name = name; 92 this.wasPlayed = false, 93 this.seek = function(t) { 94 this.wasPlayed = true; 95 } 96 } 97 for (let i = 0; i < 20; i++) { 98 var name = 'audio_' + i; 99 mockSoundMap.setPlayer(name, new mockPlayer(name)); 100 } 101 fetch('/assets/audio_external.json') 102 .then((response) => response.text()) 103 .then((lottie) => { 104 const animation = CanvasKit.MakeManagedAnimation(lottie, null, null, mockSoundMap); 105 expect(animation).toBeTruthy(); 106 // 190 frames in sample lottie 107 for (let t = 0; t < 190; t++) { 108 animation.seekFrame(t); 109 } 110 animation.delete(); 111 for(const player of mockSoundMap.map.values()) { 112 expect(player.wasPlayed).toBeTrue(player.name + " was not played"); 113 } 114 done(); 115 }); 116 }); 117 118 it('can get logs', (done) => { 119 if (!CanvasKit.skottie || !CanvasKit.managed_skottie) { 120 console.warn('Skipping test because not compiled with skottie'); 121 return; 122 } 123 124 const logger = { 125 errors: [], 126 warnings: [], 127 128 reset: function() { this.errors = []; this.warnings = []; }, 129 130 // Logger API 131 onError: function(err) { this.errors.push(err) }, 132 onWarning: function(wrn) { this.warnings.push(wrn) } 133 }; 134 135 { 136 const json = `{ 137 "v": "5.2.1", 138 "w": 100, 139 "h": 100, 140 "fr": 10, 141 "ip": 0, 142 "op": 100, 143 "layers": [{ 144 "ty": 3, 145 "nm": "null", 146 "ind": 0, 147 "ip": 0 148 }] 149 }`; 150 const animation = CanvasKit.MakeManagedAnimation(json, null, null, null, logger); 151 expect(animation).toBeTruthy(); 152 expect(logger.errors.length).toEqual(0); 153 expect(logger.warnings.length).toEqual(0); 154 } 155 156 { 157 const json = `{ 158 "v": "5.2.1", 159 "w": 100, 160 "h": 100, 161 "fr": 10, 162 "ip": 0, 163 "op": 100, 164 "layers": [{ 165 "ty": 2, 166 "nm": "image", 167 "ind": 0, 168 "ip": 0 169 }] 170 }`; 171 const animation = CanvasKit.MakeManagedAnimation(json, null, null, null, logger); 172 expect(animation).toBeTruthy(); 173 expect(logger.errors.length).toEqual(1); 174 expect(logger.warnings.length).toEqual(0); 175 176 // Image layer missing refID 177 expect(logger.errors[0].includes('missing ref')); 178 logger.reset(); 179 } 180 181 { 182 const json = `{ 183 "v": "5.2.1", 184 "w": 100, 185 "h": 100, 186 "fr": 10, 187 "ip": 0, 188 "op": 100, 189 "layers": [{ 190 "ty": 1, 191 "nm": "solid", 192 "sw": 100, 193 "sh": 100, 194 "sc": "#aabbcc", 195 "ind": 0, 196 "ip": 0, 197 "ef": [{ 198 "mn": "FOO" 199 }] 200 }] 201 }`; 202 const animation = CanvasKit.MakeManagedAnimation(json, null, null, null, logger); 203 expect(animation).toBeTruthy(); 204 expect(logger.errors.length).toEqual(0); 205 expect(logger.warnings.length).toEqual(1); 206 207 // Unsupported effect FOO 208 expect(logger.warnings[0].includes('FOO')); 209 logger.reset(); 210 } 211 212 done(); 213 }); 214 215 it('can access dynamic props', () => { 216 if (!CanvasKit.skottie || !CanvasKit.managed_skottie) { 217 console.warn('Skipping test because not compiled with skottie'); 218 return; 219 } 220 221 const json = `{ 222 "v": "5.2.1", 223 "w": 100, 224 "h": 100, 225 "fr": 10, 226 "ip": 0, 227 "op": 100, 228 "fonts": { 229 "list": [{ 230 "fName": "test_font", 231 "fFamily": "test-family", 232 "fStyle": "TestFontStyle" 233 }] 234 }, 235 "layers": [ 236 { 237 "ty": 4, 238 "nm": "__shape_layer", 239 "ind": 0, 240 "ip": 0, 241 "shapes": [ 242 { 243 "ty": "el", 244 "p": { "a": 0, "k": [ 50, 50 ] }, 245 "s": { "a": 0, "k": [ 50, 50 ] } 246 },{ 247 "ty": "fl", 248 "nm": "__shape_fill", 249 "c": { "a": 0, "k": [ 1, 0, 0] } 250 },{ 251 "ty": "tr", 252 "nm": "__shape_opacity", 253 "o": { "a": 0, "k": 50 } 254 } 255 ] 256 },{ 257 "ty": 5, 258 "nm": "__text_layer", 259 "ip": 0, 260 "t": { 261 "d": { 262 "k": [{ 263 "t": 0, 264 "s": { 265 "f": "test_font", 266 "s": 100, 267 "t": "Foo Bar Baz", 268 "lh": 120, 269 "ls": 12 270 } 271 }] 272 } 273 } 274 } 275 ] 276 }`; 277 278 const animation = CanvasKit.MakeManagedAnimation(json, null, '__'); 279 expect(animation).toBeTruthy(); 280 281 { 282 const colors = animation.getColorProps(); 283 expect(colors.length).toEqual(1); 284 expect(colors[0].key).toEqual('__shape_fill'); 285 expect(colors[0].value).toEqual(CanvasKit.ColorAsInt(255,0,0,255)); 286 287 const opacities = animation.getOpacityProps(); 288 expect(opacities.length).toEqual(1); 289 expect(opacities[0].key).toEqual('__shape_opacity'); 290 expect(opacities[0].value).toEqual(50); 291 292 const texts = animation.getTextProps(); 293 expect(texts.length).toEqual(1); 294 expect(texts[0].key).toEqual('__text_layer'); 295 expect(texts[0].value.text).toEqual('Foo Bar Baz'); 296 expect(texts[0].value.size).toEqual(100); 297 } 298 299 expect(animation.setColor('__shape_fill', [0,1,0,1])).toEqual(true); 300 expect(animation.setOpacity('__shape_opacity', 100)).toEqual(true); 301 expect(animation.setText('__text_layer', 'baz bar foo', 10)).toEqual(true); 302 303 { 304 const colors = animation.getColorProps(); 305 expect(colors.length).toEqual(1); 306 expect(colors[0].key).toEqual('__shape_fill'); 307 expect(colors[0].value).toEqual(CanvasKit.ColorAsInt(0,255,0,255)); 308 309 const opacities = animation.getOpacityProps(); 310 expect(opacities.length).toEqual(1); 311 expect(opacities[0].key).toEqual('__shape_opacity'); 312 expect(opacities[0].value).toEqual(100); 313 314 const texts = animation.getTextProps(); 315 expect(texts.length).toEqual(1); 316 expect(texts[0].key).toEqual('__text_layer'); 317 expect(texts[0].value.text).toEqual('baz bar foo'); 318 expect(texts[0].value.size).toEqual(10); 319 } 320 321 expect(animation.setColor('INVALID_KEY', [0,1,0,1])).toEqual(false); 322 expect(animation.setOpacity('INVALID_KEY', 100)).toEqual(false); 323 expect(animation.setText('INVALID KEY', '', 10)).toEqual(false); 324 }); 325}); 326