| describe('Skottie behavior', () => { | 
 |     let container; | 
 |  | 
 |     beforeEach(async () => { | 
 |         await EverythingLoaded; | 
 |         container = document.createElement('div'); | 
 |         container.innerHTML = ` | 
 |             <canvas width=600 height=600 id=test></canvas> | 
 |             <canvas width=600 height=600 id=report></canvas>`; | 
 |         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<a.length; i++) { | 
 |           expect(a[i]).toBeCloseTo(b[i], precision); | 
 |         } | 
 |     }; | 
 |  | 
 |     const imgPromise = fetch('/assets/flightAnim.gif') | 
 |         .then((response) => response.arrayBuffer()); | 
 |     const jsonPromise = fetch('/assets/animated_gif.json') | 
 |         .then((response) => response.text()); | 
 |     const washPromise = fetch('/assets/map-shield.json') | 
 |         .then((response) => response.text()); | 
 |     const slotPromise = fetch('/assets/skottie_basic_slots.json') | 
 |         .then((response) => response.text()); | 
 |     const editPromise = fetch('/assets/text_edit.json') | 
 |         .then((response) => response.text()); | 
 |     const notoSerifPromise = fetch('/assets/NotoSerif-Regular.ttf').then( | 
 |         (response) => response.arrayBuffer()); | 
 |  | 
 |     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); | 
 |  | 
 |         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); | 
 |  | 
 |         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); | 
 |  | 
 |     gm('skottie_slots', (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); | 
 |  | 
 |         const animation = CanvasKit.MakeManagedAnimation(promises[0], { | 
 |             'flightAnim.gif': promises[1], | 
 |             'NotoSerif': promises[2], | 
 |         }); | 
 |         expect(animation).toBeTruthy(); | 
 |  | 
 |         const slotInfo = animation.getSlotInfo(); | 
 |         expect(slotInfo.colorSlotIDs).toEqual(['FillsGroup', 'StrokeGroup']); | 
 |         expect(slotInfo.scalarSlotIDs).toEqual(['Opacity']); | 
 |         expect(slotInfo.vec2SlotIDs).toEqual(['ScaleGroup']); | 
 |         expect(slotInfo.imageSlotIDs).toEqual(['ImageSource']); | 
 |         expect(slotInfo.textSlotIDs).toEqual(['TextSource']); | 
 |  | 
 |         expect(animation.getScalarSlot('Opacity')).toBe(100); | 
 |         const textProp = animation.getTextSlot('TextSource'); | 
 |         expect(textProp.text).toBe('text slots'); | 
 |  | 
 |         textProp.text = 'new text'; | 
 |         textProp.fillColor = CanvasKit.CYAN; | 
 |         textProp.strokeColor = CanvasKit.MAGENTA; | 
 |  | 
 |         expect(animation.setColorSlot('FillsGroup', CanvasKit.RED)).toBeTruthy(); | 
 |         expect(animation.setScalarSlot('Opacity', 25)).toBeTruthy(); | 
 |         expect(animation.setVec2Slot('ScaleGroup', [25, 50])).toBeTruthy(); | 
 |         expect(animation.setImageSlot('ImageSource', 'flighAnim.gif')).toBeTruthy(); | 
 |         expect(animation.setTextSlot('TextSource', textProp)).toBeTruthy(); | 
 |  | 
 |         expectArrayCloseTo(animation.getColorSlot('FillsGroup'), CanvasKit.RED, 4); | 
 |         expect(animation.getScalarSlot('Opacity')).toBe(25); | 
 |         expectArrayCloseTo(animation.getVec2Slot('ScaleGroup'), [25, 50], 4); | 
 |  | 
 |         const newTextSlot = animation.getTextSlot('TextSource'); | 
 |         expect(newTextSlot.text).toBe('new text'); | 
 |         expectArrayCloseTo(newTextSlot.fillColor, CanvasKit.CYAN, 4); | 
 |         expectArrayCloseTo(newTextSlot.strokeColor, CanvasKit.MAGENTA, 4); | 
 |  | 
 |         expect(animation.getColorSlot('Bad ID')).toBeFalsy(); | 
 |         expect(animation.getScalarSlot('Bad ID')).toBeFalsy(); | 
 |         expect(animation.getVec2Slot('Bad ID')).toBeFalsy(); | 
 |         expect(animation.getTextSlot('Bad ID')).toBeFalsy(); | 
 |  | 
 |         animation.seek(0.5); | 
 |         animation.render(canvas, bounds); | 
 |         animation.delete(); | 
 |     }, slotPromise, imgPromise, notoSerifPromise); | 
 |  | 
 |     gm('skottie_textedit', (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, 600, 600); | 
 |  | 
 |         const animation = CanvasKit.MakeManagedAnimation(promises[0], { | 
 |             // The animation is looking for a font called ArialMT, but we just | 
 |             // provide it the data for an arbitrary typeface. | 
 |             "ArialMT": promises[1], | 
 |         }); | 
 |         expect(animation).toBeTruthy(); | 
 |  | 
 |         // The animation contains two text layers grouped under the "text_layer" ID, and one | 
 |         // descriptive text layer. | 
 |         { | 
 |             const texts = animation.getTextProps(); | 
 |             expect(texts.length).toEqual(2); | 
 |             expect(texts[0].key).toEqual('text_layer'); | 
 |             expect(texts[0].value.text).toEqual('foo'); | 
 |         } | 
 |  | 
 |         expect(animation.attachEditor('txt_layer', 0)).toBeFalse();  // nonexistent layer | 
 |         expect(animation.attachEditor('text_layer', 2)).toBeFalse(); // nonexistent index | 
 |         expect(animation.attachEditor('text_layer', 0)).toBeTrue(); | 
 |         expect(animation.attachEditor('text_layer', 1)).toBeTrue(); | 
 |  | 
 |         { | 
 |             // no effect, editor inactive | 
 |             expect(animation.dispatchEditorKey('Backspace')).toBeFalse(); | 
 |  | 
 |             const texts = animation.getTextProps(); | 
 |             expect(texts.length).toEqual(2); | 
 |             expect(texts[0].key).toEqual('text_layer'); | 
 |             expect(texts[0].value.text).toEqual('foo'); | 
 |         } | 
 |  | 
 |         animation.enableEditor(true); | 
 |         animation.setEditorCursorWeight(1.5); | 
 |  | 
 |         // To be fully functional, the editor requires glyph data issued during rendering callbacks. | 
 |         animation.seek(0); | 
 |         animation.render(canvas, bounds); | 
 |  | 
 |         { | 
 |             expect(animation.dispatchEditorKey('Backspace')).toBeTrue(); | 
 |             expect(animation.dispatchEditorKey('Backspace')).toBeTrue(); | 
 |             expect(animation.dispatchEditorKey('Backspace')).toBeTrue(); | 
 |             expect(animation.dispatchEditorKey('b')).toBeTrue(); | 
 |             expect(animation.dispatchEditorKey('a')).toBeTrue(); | 
 |             expect(animation.dispatchEditorKey('r')).toBeTrue(); | 
 |  | 
 |             const texts = animation.getTextProps(); | 
 |             expect(texts.length).toEqual(2); | 
 |             expect(texts[0].key).toEqual('text_layer'); | 
 |             expect(texts[0].value.text).toEqual('bar'); | 
 |         } | 
 |  | 
 |         // Final render, after edits. | 
 |         animation.seek(0); | 
 |         animation.render(canvas, bounds); | 
 |         animation.delete(); | 
 |     }, editPromise, notoSerifPromise); | 
 |  | 
 |     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); | 
 |     }); | 
 | }); |