describe('Skottie behavior', () => { let container; beforeEach(async () => { await LoadCanvasKit; container = document.createElement('div'); container.innerHTML = ` `; document.body.appendChild(container); }); afterEach(() => { document.body.removeChild(container); }); const expectArrayCloseTo = (a, b, precision) => { precision = precision || 14; // digits of precision in base 10 expect(a.length).toEqual(b.length); for (let i=0; i response.arrayBuffer()); const jsonPromise = fetch('/assets/animated_gif.json') .then((response) => response.text()); const washPromise = fetch('/assets/map-shield.json') .then((response) => response.text()); gm('skottie_animgif', (canvas, promises) => { if (!CanvasKit.skottie || !CanvasKit.managed_skottie) { console.warn('Skipping test because not compiled with skottie'); return; } expect(promises[1]).not.toBe('NOT FOUND'); const animation = CanvasKit.MakeManagedAnimation(promises[1], { 'flightAnim.gif': promises[0], }); expect(animation).toBeTruthy(); const bounds = CanvasKit.LTRBRect(0, 0, 500, 500); const size = animation.size(); expectArrayCloseTo(size, Float32Array.of(800, 600), 4); canvas.clear(CanvasKit.WHITE); animation.render(canvas, bounds); // We intentionally make the length of this array 5 and add a sentinel value // of 999 so we can make sure the bounds are copied into this rect and a new // one is not allocated. const damageRect = Float32Array.of(0, 0, 0, 0, 999); // There was a bug, fixed in https://skia-review.googlesource.com/c/skia/+/241757 // that seeking again and drawing again revealed. animation.seek(0.5, damageRect); expectArrayCloseTo(damageRect, Float32Array.of(0, 0, 800, 600, 999), 4); canvas.clear(CanvasKit.WHITE); animation.render(canvas, bounds); animation.delete(); }, imgPromise, jsonPromise); gm('skottie_setcolor', (canvas, promises) => { if (!CanvasKit.skottie || !CanvasKit.managed_skottie) { console.warn('Skipping test because not compiled with skottie'); return; } expect(promises[0]).not.toBe('NOT FOUND'); const bounds = CanvasKit.LTRBRect(0, 0, 500, 500); canvas.clear(CanvasKit.WHITE); const animation = CanvasKit.MakeManagedAnimation(promises[0]); expect(animation).toBeTruthy(); animation.setColor('$Icon Fill', CanvasKit.RED); animation.seek(0.5); animation.render(canvas, bounds); animation.delete(); }, washPromise); it('can load audio assets', (done) => { if (!CanvasKit.skottie || !CanvasKit.managed_skottie) { console.warn('Skipping test because not compiled with skottie'); return; } const mockSoundMap = { map : new Map(), getPlayer : function(name) {return this.map.get(name)}, setPlayer : function(name, player) {this.map.set(name, player)}, }; function mockPlayer(name) { this.name = name; this.wasPlayed = false, this.seek = function(t) { this.wasPlayed = true; } } for (let i = 0; i < 20; i++) { var name = 'audio_' + i; mockSoundMap.setPlayer(name, new mockPlayer(name)); } fetch('/assets/audio_external.json') .then((response) => response.text()) .then((lottie) => { const animation = CanvasKit.MakeManagedAnimation(lottie, null, null, mockSoundMap); expect(animation).toBeTruthy(); // 190 frames in sample lottie for (let t = 0; t < 190; t++) { animation.seekFrame(t); } animation.delete(); for(const player of mockSoundMap.map.values()) { expect(player.wasPlayed).toBeTrue(player.name + " was not played"); } done(); }); }); it('can get logs', (done) => { if (!CanvasKit.skottie || !CanvasKit.managed_skottie) { console.warn('Skipping test because not compiled with skottie'); return; } const logger = { errors: [], warnings: [], reset: function() { this.errors = []; this.warnings = []; }, // Logger API onError: function(err) { this.errors.push(err) }, onWarning: function(wrn) { this.warnings.push(wrn) } }; { const json = `{ "v": "5.2.1", "w": 100, "h": 100, "fr": 10, "ip": 0, "op": 100, "layers": [{ "ty": 3, "nm": "null", "ind": 0, "ip": 0 }] }`; const animation = CanvasKit.MakeManagedAnimation(json, null, null, null, logger); expect(animation).toBeTruthy(); expect(logger.errors.length).toEqual(0); expect(logger.warnings.length).toEqual(0); } { const json = `{ "v": "5.2.1", "w": 100, "h": 100, "fr": 10, "ip": 0, "op": 100, "layers": [{ "ty": 2, "nm": "image", "ind": 0, "ip": 0 }] }`; const animation = CanvasKit.MakeManagedAnimation(json, null, null, null, logger); expect(animation).toBeTruthy(); expect(logger.errors.length).toEqual(1); expect(logger.warnings.length).toEqual(0); // Image layer missing refID expect(logger.errors[0].includes('missing ref')); logger.reset(); } { const json = `{ "v": "5.2.1", "w": 100, "h": 100, "fr": 10, "ip": 0, "op": 100, "layers": [{ "ty": 1, "nm": "solid", "sw": 100, "sh": 100, "sc": "#aabbcc", "ind": 0, "ip": 0, "ef": [{ "mn": "FOO" }] }] }`; const animation = CanvasKit.MakeManagedAnimation(json, null, null, null, logger); expect(animation).toBeTruthy(); expect(logger.errors.length).toEqual(0); expect(logger.warnings.length).toEqual(1); // Unsupported effect FOO expect(logger.warnings[0].includes('FOO')); logger.reset(); } done(); }); it('can access dynamic props', () => { if (!CanvasKit.skottie || !CanvasKit.managed_skottie) { console.warn('Skipping test because not compiled with skottie'); return; } const json = `{ "v": "5.2.1", "w": 100, "h": 100, "fr": 10, "ip": 0, "op": 100, "fonts": { "list": [{ "fName": "test_font", "fFamily": "test-family", "fStyle": "TestFontStyle" }] }, "layers": [ { "ty": 4, "nm": "__shape_layer", "ind": 0, "ip": 0, "shapes": [ { "ty": "el", "p": { "a": 0, "k": [ 50, 50 ] }, "s": { "a": 0, "k": [ 50, 50 ] } },{ "ty": "fl", "nm": "__shape_fill", "c": { "a": 0, "k": [ 1, 0, 0] } },{ "ty": "tr", "nm": "__shape_opacity", "o": { "a": 0, "k": 50 } } ] },{ "ty": 5, "nm": "__text_layer", "ip": 0, "t": { "d": { "k": [{ "t": 0, "s": { "f": "test_font", "s": 100, "t": "Foo Bar Baz", "lh": 120, "ls": 12 } }] } } } ] }`; const animation = CanvasKit.MakeManagedAnimation(json, null, '__'); expect(animation).toBeTruthy(); { const colors = animation.getColorProps(); expect(colors.length).toEqual(1); expect(colors[0].key).toEqual('__shape_fill'); expect(colors[0].value).toEqual(CanvasKit.ColorAsInt(255,0,0,255)); const opacities = animation.getOpacityProps(); expect(opacities.length).toEqual(1); expect(opacities[0].key).toEqual('__shape_opacity'); expect(opacities[0].value).toEqual(50); const texts = animation.getTextProps(); expect(texts.length).toEqual(1); expect(texts[0].key).toEqual('__text_layer'); expect(texts[0].value.text).toEqual('Foo Bar Baz'); expect(texts[0].value.size).toEqual(100); } expect(animation.setColor('__shape_fill', [0,1,0,1])).toEqual(true); expect(animation.setOpacity('__shape_opacity', 100)).toEqual(true); expect(animation.setText('__text_layer', 'baz bar foo', 10)).toEqual(true); { const colors = animation.getColorProps(); expect(colors.length).toEqual(1); expect(colors[0].key).toEqual('__shape_fill'); expect(colors[0].value).toEqual(CanvasKit.ColorAsInt(0,255,0,255)); const opacities = animation.getOpacityProps(); expect(opacities.length).toEqual(1); expect(opacities[0].key).toEqual('__shape_opacity'); expect(opacities[0].value).toEqual(100); const texts = animation.getTextProps(); expect(texts.length).toEqual(1); expect(texts[0].key).toEqual('__text_layer'); expect(texts[0].value.text).toEqual('baz bar foo'); expect(texts[0].value.size).toEqual(10); } expect(animation.setColor('INVALID_KEY', [0,1,0,1])).toEqual(false); expect(animation.setOpacity('INVALID_KEY', 100)).toEqual(false); expect(animation.setText('INVALID KEY', '', 10)).toEqual(false); }); });