tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

keyframe-tests.js (32977B)


      1 'use strict';
      2 
      3 // ==============================
      4 //
      5 // Common keyframe test data
      6 //
      7 // ==============================
      8 
      9 
     10 // ------------------------------
     11 //  Composite values
     12 // ------------------------------
     13 
     14 const gGoodKeyframeCompositeValueTests = [
     15  'replace', 'add', 'accumulate', 'auto'
     16 ];
     17 
     18 const gBadKeyframeCompositeValueTests = [
     19  'unrecognised', 'replace ', 'Replace', null
     20 ];
     21 
     22 const gGoodOptionsCompositeValueTests = [
     23  'replace', 'add', 'accumulate'
     24 ];
     25 
     26 const gBadOptionsCompositeValueTests = [
     27  'unrecognised', 'replace ', 'Replace', null
     28 ];
     29 
     30 // ------------------------------
     31 //  Keyframes
     32 // ------------------------------
     33 
     34 const gEmptyKeyframeListTests = [
     35  [],
     36  null,
     37  undefined,
     38 ];
     39 
     40 // Helper methods to make defining computed keyframes more readable.
     41 
     42 const offset = offset => ({
     43  offset,
     44  computedOffset: offset,
     45 });
     46 
     47 const computedOffset = computedOffset => ({
     48  offset: null,
     49  computedOffset,
     50 });
     51 
     52 const keyframe = (offset, props, easing='linear', composite) => {
     53  // The object spread operator is not yet available in all browsers so we use
     54  // Object.assign instead.
     55  const result = {};
     56  Object.assign(result, offset, props, { easing });
     57  result.composite = composite || 'auto';
     58  return result;
     59 };
     60 
     61 const gKeyframesTests = [
     62 
     63  // ----------- Property-indexed keyframes: property handling -----------
     64 
     65  {
     66    desc:   'a one property two value property-indexed keyframes specification',
     67    input:  { left: ['10px', '20px'] },
     68    output: [keyframe(computedOffset(0), { left: '10px' }),
     69             keyframe(computedOffset(1), { left: '20px' })],
     70  },
     71  {
     72    desc:   'a one shorthand property two value property-indexed keyframes'
     73            + ' specification',
     74    input:  { margin: ['10px', '10px 20px 30px 40px'] },
     75    output: [keyframe(computedOffset(0), { margin: '10px' }),
     76             keyframe(computedOffset(1), { margin: '10px 20px 30px 40px' })],
     77  },
     78  {
     79    desc:   'a two property (one shorthand and one of its longhand components)'
     80            + ' two value property-indexed keyframes specification',
     81    input:  { marginTop: ['50px', '60px'],
     82              margin: ['10px', '10px 20px 30px 40px'] },
     83    output: [keyframe(computedOffset(0),
     84                      { marginTop: '50px', margin: '10px' }),
     85             keyframe(computedOffset(1),
     86                      { marginTop: '60px', margin: '10px 20px 30px 40px' })],
     87  },
     88  {
     89    desc:   'a two property (one shorthand and one of its shorthand components)'
     90            + ' two value property-indexed keyframes specification',
     91    input:  { border: ['pink', '2px'],
     92              borderColor: ['green', 'blue'] },
     93    output: [keyframe(computedOffset(0),
     94                      { border: 'pink', borderColor: 'green' }),
     95             keyframe(computedOffset(1),
     96                      { border: '2px', borderColor: 'blue' })],
     97  },
     98  {
     99    desc:   'a two property two value property-indexed keyframes specification',
    100    input:  { left: ['10px', '20px'],
    101              top: ['30px', '40px'] },
    102    output: [keyframe(computedOffset(0), { left: '10px', top: '30px' }),
    103             keyframe(computedOffset(1), { left: '20px', top: '40px' })],
    104  },
    105  {
    106    desc:   'a two property property-indexed keyframes specification with'
    107            + ' different numbers of values',
    108    input:  { left: ['10px', '20px', '30px'],
    109              top: ['40px', '50px'] },
    110    output: [keyframe(computedOffset(0),   { left: '10px', top: '40px' }),
    111             keyframe(computedOffset(0.5), { left: '20px' }),
    112             keyframe(computedOffset(1),   { left: '30px', top: '50px' })],
    113  },
    114  {
    115    desc:   'a property-indexed keyframes specification with an invalid value',
    116    input:  { left: ['10px', '20px', '30px', '40px', '50px'],
    117              top:  ['15px', '25px', 'invalid', '45px', '55px'] },
    118    output: [keyframe(computedOffset(0),    { left: '10px', top: '15px' }),
    119             keyframe(computedOffset(0.25), { left: '20px', top: '25px' }),
    120             keyframe(computedOffset(0.5),  { left: '30px' }),
    121             keyframe(computedOffset(0.75), { left: '40px', top: '45px' }),
    122             keyframe(computedOffset(1),    { left: '50px', top: '55px' })],
    123  },
    124  {
    125    desc:   'a one property two value property-indexed keyframes specification'
    126            + ' that needs to stringify its values',
    127    input:  { opacity: [0, 1] },
    128    output: [keyframe(computedOffset(0), { opacity: '0' }),
    129             keyframe(computedOffset(1), { opacity: '1' })],
    130  },
    131  {
    132    desc:   'a property-indexed keyframes specification with a CSS variable'
    133            + ' reference',
    134    input:  { left: [ 'var(--dist)', 'calc(var(--dist) + 100px)' ] },
    135    output: [keyframe(computedOffset(0), { left: 'var(--dist)' }),
    136             keyframe(computedOffset(1), { left: 'calc(var(--dist) + 100px)' })]
    137  },
    138  {
    139    desc:   'a property-indexed keyframes specification with a CSS variable'
    140            + ' reference in a shorthand property',
    141    input:  { margin: [ 'var(--dist)', 'calc(var(--dist) + 100px)' ] },
    142    output: [keyframe(computedOffset(0),
    143                      { margin: 'var(--dist)' }),
    144             keyframe(computedOffset(1),
    145                      { margin: 'calc(var(--dist) + 100px)' })],
    146  },
    147  {
    148    desc:   'a one property one value property-indexed keyframes specification',
    149    input:  { left: ['10px'] },
    150    output: [keyframe(computedOffset(1), { left: '10px' })],
    151  },
    152  {
    153    desc:   'a one property one non-array value property-indexed keyframes'
    154            + ' specification',
    155    input:  { left: '10px' },
    156    output: [keyframe(computedOffset(1), { left: '10px' })],
    157  },
    158  {
    159    desc:   'a one property two value property-indexed keyframes specification'
    160            + ' where the first value is invalid',
    161    input:  { left: ['invalid', '10px'] },
    162    output: [keyframe(computedOffset(0), {}),
    163             keyframe(computedOffset(1), { left: '10px' })]
    164  },
    165  {
    166    desc:   'a one property two value property-indexed keyframes specification'
    167            + ' where the second value is invalid',
    168    input:  { left: ['10px', 'invalid'] },
    169    output: [keyframe(computedOffset(0), { left: '10px' }),
    170             keyframe(computedOffset(1), {})]
    171  },
    172  {
    173    desc:   'a property-indexed keyframes specification with a CSS variable as'
    174            + ' the property',
    175    input:  { '--custom': ['1', '2'] },
    176    output: [keyframe(computedOffset(0), { '--custom': '1' }),
    177             keyframe(computedOffset(1), { '--custom': '2' })]
    178  },
    179 
    180  // ----------- Property-indexed keyframes: offset handling -----------
    181 
    182  {
    183    desc:   'a property-indexed keyframe with a single offset',
    184    input:  { left: ['10px', '20px', '30px'], offset: 0.5 },
    185    output: [keyframe(offset(0.5),          { left: '10px' }),
    186             keyframe(computedOffset(0.75), { left: '20px' }),
    187             keyframe(computedOffset(1),    { left: '30px' })],
    188  },
    189  {
    190    desc:   'a property-indexed keyframe with an array of offsets',
    191    input:  { left: ['10px', '20px', '30px'], offset: [ 0.1, 0.25, 0.8 ] },
    192    output: [keyframe(offset(0.1),  { left: '10px' }),
    193             keyframe(offset(0.25), { left: '20px' }),
    194             keyframe(offset(0.8),  { left: '30px' })],
    195  },
    196  {
    197    desc:   'a property-indexed keyframe with an array of offsets that is too'
    198            + ' short',
    199    input:  { left: ['10px', '20px', '30px'], offset: [ 0, 0.25 ] },
    200    output: [keyframe(offset(0),         { left: '10px' }),
    201             keyframe(offset(0.25),      { left: '20px' }),
    202             keyframe(computedOffset(1), { left: '30px' })],
    203  },
    204  {
    205    desc:   'a property-indexed keyframe with an array of offsets that is too'
    206            + ' long',
    207    input:  { left: ['10px', '20px', '30px'],
    208              offset: [ 0, 0.25, 0.5, 0.75, 1 ] },
    209    output: [keyframe(offset(0),    { left: '10px' }),
    210             keyframe(offset(0.25), { left: '20px' }),
    211             keyframe(offset(0.5),  { left: '30px' })],
    212  },
    213  {
    214    desc:   'a property-indexed keyframe with an empty array of offsets',
    215    input:  { left: ['10px', '20px', '30px'], offset: [] },
    216    output: [keyframe(computedOffset(0),   { left: '10px' }),
    217             keyframe(computedOffset(0.5), { left: '20px' }),
    218             keyframe(computedOffset(1),   { left: '30px' })],
    219  },
    220  {
    221    desc:   'a property-indexed keyframe with an array of offsets with an'
    222            + ' embedded null value',
    223    input:  { left: ['10px', '20px', '30px'],
    224              offset: [ 0, null, 0.5 ] },
    225    output: [keyframe(offset(0),            { left: '10px' }),
    226             keyframe(computedOffset(0.25), { left: '20px' }),
    227             keyframe(offset(0.5),          { left: '30px' })],
    228  },
    229  {
    230    desc:   'a property-indexed keyframe with an array of offsets with a'
    231            + ' trailing null value',
    232    input:  { left: ['10px', '20px', '30px'],
    233              offset: [ 0, 0.25, null ] },
    234    output: [keyframe(offset(0),           { left: '10px' }),
    235             keyframe(offset(0.25),        { left: '20px' }),
    236             keyframe(computedOffset(1), { left: '30px' })],
    237  },
    238  {
    239    desc:   'a property-indexed keyframe with an array of offsets with leading'
    240            + ' and trailing null values',
    241    input:  { left: ['10px', '20px', '30px'],
    242              offset: [ null, 0.25, null ] },
    243    output: [keyframe(computedOffset(0), { left: '10px' }),
    244             keyframe(offset(0.25),      { left: '20px' }),
    245             keyframe(computedOffset(1), { left: '30px' })],
    246  },
    247  {
    248    desc:   'a property-indexed keyframe with an array of offsets with'
    249            + ' adjacent null values',
    250    input:  { left: ['10px', '20px', '30px'],
    251              offset: [ null, null, 0.5 ] },
    252    output: [keyframe(computedOffset(0),    { left: '10px' }),
    253             keyframe(computedOffset(0.25), { left: '20px' }),
    254             keyframe(offset(0.5),          { left: '30px' })],
    255  },
    256  {
    257    desc:   'a property-indexed keyframe with an array of offsets with'
    258            + ' all null values (and too many at that)',
    259    input:  { left: ['10px', '20px', '30px'],
    260              offset: [ null, null, null, null, null ] },
    261    output: [keyframe(computedOffset(0),   { left: '10px' }),
    262             keyframe(computedOffset(0.5), { left: '20px' }),
    263             keyframe(computedOffset(1),   { left: '30px' })],
    264  },
    265  {
    266    desc:   'a property-indexed keyframe with a single null offset',
    267    input:  { left: ['10px', '20px', '30px'], offset: null },
    268    output: [keyframe(computedOffset(0),   { left: '10px' }),
    269             keyframe(computedOffset(0.5), { left: '20px' }),
    270             keyframe(computedOffset(1),   { left: '30px' })],
    271  },
    272  {
    273    desc:   'a property-indexed keyframe with an array of offsets that is not'
    274            + ' strictly ascending in the unused part of the array',
    275    input:  { left: ['10px', '20px', '30px'],
    276              offset: [ 0, 0.2, 0.8, 0.6 ] },
    277    output: [keyframe(offset(0),   { left: '10px' }),
    278             keyframe(offset(0.2), { left: '20px' }),
    279             keyframe(offset(0.8), { left: '30px' })],
    280  },
    281 
    282  // ----------- Property-indexed keyframes: easing handling -----------
    283 
    284  {
    285    desc:   'a property-indexed keyframe without any specified easing',
    286    input:  { left: ['10px', '20px', '30px'] },
    287    output: [keyframe(computedOffset(0),   { left: '10px' }, 'linear'),
    288             keyframe(computedOffset(0.5), { left: '20px' }, 'linear'),
    289             keyframe(computedOffset(1),   { left: '30px' }, 'linear')],
    290  },
    291  {
    292    desc:   'a property-indexed keyframe with a single easing',
    293    input:  { left: ['10px', '20px', '30px'], easing: 'ease-in' },
    294    output: [keyframe(computedOffset(0),   { left: '10px' }, 'ease-in'),
    295             keyframe(computedOffset(0.5), { left: '20px' }, 'ease-in'),
    296             keyframe(computedOffset(1),   { left: '30px' }, 'ease-in')],
    297  },
    298  {
    299    desc:   'a property-indexed keyframe with an array of easings',
    300    input:  { left: ['10px', '20px', '30px'],
    301              easing: ['ease-in', 'ease-out', 'ease-in-out'] },
    302    output: [keyframe(computedOffset(0),   { left: '10px' }, 'ease-in'),
    303             keyframe(computedOffset(0.5), { left: '20px' }, 'ease-out'),
    304             keyframe(computedOffset(1),   { left: '30px' }, 'ease-in-out')],
    305  },
    306  {
    307    desc:   'a property-indexed keyframe with an array of easings that is too'
    308            + ' short',
    309    input:  { left: ['10px', '20px', '30px'],
    310              easing: ['ease-in', 'ease-out'] },
    311    output: [keyframe(computedOffset(0),   { left: '10px' }, 'ease-in'),
    312             keyframe(computedOffset(0.5), { left: '20px' }, 'ease-out'),
    313             keyframe(computedOffset(1),   { left: '30px' }, 'ease-in')],
    314  },
    315  {
    316    desc:   'a property-indexed keyframe with a single-element array of'
    317            + ' easings',
    318    input:  { left: ['10px', '20px', '30px'], easing: ['ease-in'] },
    319    output: [keyframe(computedOffset(0),   { left: '10px' }, 'ease-in'),
    320             keyframe(computedOffset(0.5), { left: '20px' }, 'ease-in'),
    321             keyframe(computedOffset(1),   { left: '30px' }, 'ease-in')],
    322  },
    323  {
    324    desc:   'a property-indexed keyframe with an empty array of easings',
    325    input:  { left: ['10px', '20px', '30px'], easing: [] },
    326    output: [keyframe(computedOffset(0),   { left: '10px' }, 'linear'),
    327             keyframe(computedOffset(0.5), { left: '20px' }, 'linear'),
    328             keyframe(computedOffset(1),   { left: '30px' }, 'linear')],
    329  },
    330  {
    331    desc:   'a property-indexed keyframe with an array of easings that is too'
    332            + ' long',
    333    input:  { left: ['10px', '20px', '30px'],
    334              easing: ['steps(1)', 'steps(2)', 'steps(3)', 'steps(4)'] },
    335    output: [keyframe(computedOffset(0),   { left: '10px' }, 'steps(1)'),
    336             keyframe(computedOffset(0.5), { left: '20px' }, 'steps(2)'),
    337             keyframe(computedOffset(1),   { left: '30px' }, 'steps(3)')],
    338  },
    339 
    340  // ----------- Property-indexed keyframes: composite handling -----------
    341 
    342  {
    343    desc:   'a property-indexed keyframe with a single composite operation',
    344    input:  { left: ['10px', '20px', '30px'], composite: 'add' },
    345    output: [keyframe(computedOffset(0),   { left: '10px' }, 'linear', 'add'),
    346             keyframe(computedOffset(0.5), { left: '20px' }, 'linear', 'add'),
    347             keyframe(computedOffset(1),   { left: '30px' }, 'linear', 'add')],
    348  },
    349  {
    350    desc:   'a property-indexed keyframe with a composite array',
    351    input:  { left: ['10px', '20px', '30px'],
    352              composite: ['add', 'replace', 'accumulate'] },
    353    output: [keyframe(computedOffset(0),   { left: '10px' },
    354                      'linear', 'add'),
    355             keyframe(computedOffset(0.5), { left: '20px' },
    356                      'linear', 'replace'),
    357             keyframe(computedOffset(1),   { left: '30px' },
    358                      'linear', 'accumulate')],
    359  },
    360  {
    361    desc:   'a property-indexed keyframe with a composite array that is too'
    362            + ' short',
    363    input:  { left: ['10px', '20px', '30px', '40px', '50px'],
    364              composite: ['add', 'replace'] },
    365    output: [keyframe(computedOffset(0),    { left: '10px' },
    366                      'linear', 'add'),
    367             keyframe(computedOffset(0.25), { left: '20px' },
    368                      'linear', 'replace'),
    369             keyframe(computedOffset(0.5),  { left: '30px' },
    370                      'linear', 'add'),
    371             keyframe(computedOffset(0.75), { left: '40px' },
    372                      'linear', 'replace'),
    373             keyframe(computedOffset(1),    { left: '50px' },
    374                      'linear', 'add')],
    375  },
    376  {
    377    desc:   'a property-indexed keyframe with a composite array that is too'
    378            + ' long',
    379    input:  { left: ['10px', '20px'],
    380              composite: ['add', 'replace', 'accumulate'] },
    381    output: [keyframe(computedOffset(0), { left: '10px' },
    382                      'linear', 'add'),
    383             keyframe(computedOffset(1), { left: '20px' },
    384                      'linear', 'replace')],
    385  },
    386  {
    387    desc:   'a property-indexed keyframe with a single-element composite array',
    388    input:  { left: ['10px', '20px', '30px'],
    389              composite: ['add'] },
    390    output: [keyframe(computedOffset(0),   { left: '10px' }, 'linear', 'add'),
    391             keyframe(computedOffset(0.5), { left: '20px' }, 'linear', 'add'),
    392             keyframe(computedOffset(1),   { left: '30px' }, 'linear', 'add')],
    393  },
    394 
    395  // ----------- Keyframe sequence: property handling -----------
    396 
    397  {
    398    desc:   'a one property one keyframe sequence',
    399    input:  [{ offset: 1, left: '10px' }],
    400    output: [keyframe(offset(1), { left: '10px' })],
    401  },
    402  {
    403    desc:   'a one property two keyframe sequence',
    404    input:  [{ offset: 0, left: '10px' },
    405             { offset: 1, left: '20px' }],
    406    output: [keyframe(offset(0), { left: '10px' }),
    407             keyframe(offset(1), { left: '20px' })],
    408  },
    409  {
    410    desc:   'a two property two keyframe sequence',
    411    input:  [{ offset: 0, left: '10px', top: '30px' },
    412             { offset: 1, left: '20px', top: '40px' }],
    413    output: [keyframe(offset(0), { left: '10px', top: '30px' }),
    414             keyframe(offset(1), { left: '20px', top: '40px' })],
    415  },
    416  {
    417    desc:   'a one shorthand property two keyframe sequence',
    418    input:  [{ offset: 0, margin: '10px' },
    419             { offset: 1, margin: '20px 30px 40px 50px' }],
    420    output: [keyframe(offset(0), { margin: '10px' }),
    421             keyframe(offset(1), { margin: '20px 30px 40px 50px' })],
    422  },
    423  {
    424    desc:   'a two property (a shorthand and one of its component longhands)'
    425            + ' two keyframe sequence',
    426    input:  [{ offset: 0, margin: '10px', marginTop: '20px' },
    427             { offset: 1, marginTop: '70px', margin: '30px 40px 50px 60px' }],
    428    output: [keyframe(offset(0), { margin: '10px', marginTop: '20px' }),
    429             keyframe(offset(1), { marginTop: '70px',
    430                                   margin: '30px 40px 50px 60px' })],
    431  },
    432  {
    433    desc:   'a two property keyframe sequence where one property is missing'
    434            + ' from the first keyframe',
    435    input:  [{ offset: 0, left: '10px' },
    436             { offset: 1, left: '20px', top: '30px' }],
    437    output: [keyframe(offset(0), { left: '10px' }),
    438             keyframe(offset(1), { left: '20px', top: '30px' })],
    439  },
    440  {
    441    desc:   'a two property keyframe sequence where one property is missing'
    442            + ' from the last keyframe',
    443    input:  [{ offset: 0, left: '10px', top: '20px' },
    444             { offset: 1, left: '30px' }],
    445    output: [keyframe(offset(0), { left: '10px', top: '20px' }),
    446             keyframe(offset(1), { left: '30px' })],
    447  },
    448  {
    449    desc:   'a one property two keyframe sequence that needs to stringify'
    450            + ' its values',
    451    input:  [{ offset: 0, opacity: 0 },
    452             { offset: 1, opacity: 1 }],
    453    output: [keyframe(offset(0), { opacity: '0' }),
    454             keyframe(offset(1), { opacity: '1' })],
    455  },
    456  {
    457    desc:   'a keyframe sequence with a CSS variable reference',
    458    input:  [{ left: 'var(--dist)' },
    459             { left: 'calc(var(--dist) + 100px)' }],
    460    output: [keyframe(computedOffset(0), { left: 'var(--dist)' }),
    461             keyframe(computedOffset(1), { left: 'calc(var(--dist) + 100px)' })]
    462  },
    463  {
    464    desc:   'a keyframe sequence with a CSS variable reference in a shorthand'
    465            + ' property',
    466    input:  [{ margin: 'var(--dist)' },
    467             { margin: 'calc(var(--dist) + 100px)' }],
    468    output: [keyframe(computedOffset(0),
    469                      { margin: 'var(--dist)' }),
    470             keyframe(computedOffset(1),
    471                      { margin: 'calc(var(--dist) + 100px)' })],
    472  },
    473  {
    474    desc:   'a keyframe sequence with a CSS variable as its property',
    475    input:  [{ '--custom': 'a' },
    476             { '--custom': 'b' }],
    477    output: [keyframe(computedOffset(0), { '--custom': 'a' }),
    478             keyframe(computedOffset(1), { '--custom': 'b' })]
    479  },
    480 
    481  // ----------- Keyframe sequence: offset handling -----------
    482 
    483  {
    484    desc:   'a keyframe sequence with duplicate values for a given interior'
    485            + ' offset',
    486    input:  [{ offset: 0.0, left: '10px' },
    487             { offset: 0.5, left: '20px' },
    488             { offset: 0.5, left: '30px' },
    489             { offset: 0.5, left: '40px' },
    490             { offset: 1.0, left: '50px' }],
    491    output: [keyframe(offset(0),   { left: '10px' }),
    492             keyframe(offset(0.5), { left: '20px' }),
    493             keyframe(offset(0.5), { left: '30px' }),
    494             keyframe(offset(0.5), { left: '40px' }),
    495             keyframe(offset(1),   { left: '50px' })],
    496  },
    497  {
    498    desc:   'a keyframe sequence with duplicate values for offsets 0 and 1',
    499    input:  [{ offset: 0, left: '10px' },
    500             { offset: 0, left: '20px' },
    501             { offset: 0, left: '30px' },
    502             { offset: 1, left: '40px' },
    503             { offset: 1, left: '50px' },
    504             { offset: 1, left: '60px' }],
    505    output: [keyframe(offset(0), { left: '10px' }),
    506             keyframe(offset(0), { left: '20px' }),
    507             keyframe(offset(0), { left: '30px' }),
    508             keyframe(offset(1), { left: '40px' }),
    509             keyframe(offset(1), { left: '50px' }),
    510             keyframe(offset(1), { left: '60px' })],
    511  },
    512  {
    513    desc:   'a two property four keyframe sequence',
    514    input:  [{ offset: 0, left: '10px' },
    515             { offset: 0, top: '20px' },
    516             { offset: 1, top: '30px' },
    517             { offset: 1, left: '40px' }],
    518    output: [keyframe(offset(0), { left: '10px' }),
    519             keyframe(offset(0), { top:  '20px' }),
    520             keyframe(offset(1), { top:  '30px' }),
    521             keyframe(offset(1), { left: '40px' })],
    522  },
    523  {
    524    desc:   'a single keyframe sequence with omitted offset',
    525    input:  [{ left: '10px' }],
    526    output: [keyframe(computedOffset(1), { left: '10px' })],
    527  },
    528  {
    529    desc:   'a single keyframe sequence with null offset',
    530    input:  [{ offset: null, left: '10px' }],
    531    output: [keyframe(computedOffset(1), { left: '10px' })],
    532  },
    533  {
    534    desc:   'a single keyframe sequence with string offset',
    535    input:  [{ offset: '0.5', left: '10px' }],
    536    output: [keyframe(offset(0.5), { left: '10px' })],
    537  },
    538  {
    539    desc:   'a single keyframe sequence with a single calc() offset',
    540    input:  [{ offset: 'calc(0.5)', left: '10px' }],
    541    output: [keyframe(offset(0.5), { left: '10px' })],
    542  },
    543  {
    544    desc:   'a one property keyframe sequence with some omitted offsets',
    545    input:  [{ offset: 0.00, left: '10px' },
    546             { offset: 0.25, left: '20px' },
    547             { left: '30px' },
    548             { left: '40px' },
    549             { offset: 1.00, left: '50px' }],
    550    output: [keyframe(offset(0),            { left: '10px' }),
    551             keyframe(offset(0.25),         { left: '20px' }),
    552             keyframe(computedOffset(0.5),  { left: '30px' }),
    553             keyframe(computedOffset(0.75), { left: '40px' }),
    554             keyframe(offset(1),            { left: '50px' })],
    555  },
    556  {
    557    desc:   'a one property keyframe sequence with some null offsets',
    558    input:  [{ offset: 0.00, left: '10px' },
    559             { offset: 0.25, left: '20px' },
    560             { offset: null, left: '30px' },
    561             { offset: null, left: '40px' },
    562             { offset: 1.00, left: '50px' }],
    563    output: [keyframe(offset(0),            { left: '10px' }),
    564             keyframe(offset(0.25),         { left: '20px' }),
    565             keyframe(computedOffset(0.5),  { left: '30px' }),
    566             keyframe(computedOffset(0.75), { left: '40px' }),
    567             keyframe(offset(1),            { left: '50px' })],
    568  },
    569  {
    570    desc:   'a two property keyframe sequence with some omitted offsets',
    571    input:  [{ offset: 0.00, left: '10px', top: '20px' },
    572             { offset: 0.25, left: '30px' },
    573             { left: '40px' },
    574             { left: '50px', top: '60px' },
    575             { offset: 1.00, left: '70px', top: '80px' }],
    576    output: [keyframe(offset(0),            { left: '10px', top: '20px' }),
    577             keyframe(offset(0.25),         { left: '30px' }),
    578             keyframe(computedOffset(0.5),  { left: '40px' }),
    579             keyframe(computedOffset(0.75), { left: '50px', top: '60px' }),
    580             keyframe(offset(1),            { left: '70px', top: '80px' })],
    581  },
    582  {
    583    desc:   'a one property keyframe sequence with all omitted offsets',
    584    input:  [{ left: '10px' },
    585             { left: '20px' },
    586             { left: '30px' },
    587             { left: '40px' },
    588             { left: '50px' }],
    589    output: [keyframe(computedOffset(0),    { left: '10px' }),
    590             keyframe(computedOffset(0.25), { left: '20px' }),
    591             keyframe(computedOffset(0.5),  { left: '30px' }),
    592             keyframe(computedOffset(0.75), { left: '40px' }),
    593             keyframe(computedOffset(1),    { left: '50px' })],
    594  },
    595 
    596  // ----------- Keyframe sequence: easing handling -----------
    597 
    598  {
    599    desc:   'a keyframe sequence with different easing values, but the same'
    600            + ' easing value for a given offset',
    601    input:  [{ offset: 0.0, easing: 'ease',     left: '10px'},
    602             { offset: 0.0, easing: 'ease',     top: '20px'},
    603             { offset: 0.5, easing: 'linear',   left: '30px' },
    604             { offset: 0.5, easing: 'linear',   top: '40px' },
    605             { offset: 1.0, easing: 'step-end', left: '50px' },
    606             { offset: 1.0, easing: 'step-end', top: '60px' }],
    607    output: [keyframe(offset(0),   { left: '10px' }, 'ease'),
    608             keyframe(offset(0),   { top:  '20px' }, 'ease'),
    609             keyframe(offset(0.5), { left: '30px' }, 'linear'),
    610             keyframe(offset(0.5), { top:  '40px' }, 'linear'),
    611             keyframe(offset(1),   { left: '50px' }, 'steps(1)'),
    612             keyframe(offset(1),   { top:  '60px' }, 'steps(1)')],
    613  },
    614 
    615  // ----------- Keyframe sequence: composite handling -----------
    616 
    617  {
    618    desc:   'a keyframe sequence with different composite values, but the'
    619            + ' same composite value for a given offset',
    620    input:  [{ offset: 0.0, composite: 'replace', left: '10px' },
    621             { offset: 0.0, composite: 'replace', top: '20px' },
    622             { offset: 0.5, composite: 'add',     left: '30px' },
    623             { offset: 0.5, composite: 'add',     top: '40px' },
    624             { offset: 1.0, composite: 'replace', left: '50px' },
    625             { offset: 1.0, composite: 'replace', top: '60px' }],
    626    output: [keyframe(offset(0),   { left: '10px' }, 'linear', 'replace'),
    627             keyframe(offset(0),   { top:  '20px' }, 'linear', 'replace'),
    628             keyframe(offset(0.5), { left: '30px' }, 'linear', 'add'),
    629             keyframe(offset(0.5), { top:  '40px' }, 'linear', 'add'),
    630             keyframe(offset(1),   { left: '50px' }, 'linear', 'replace'),
    631             keyframe(offset(1),   { top:  '60px' }, 'linear', 'replace')],
    632  },
    633 ];
    634 
    635 const gInvalidKeyframesTests = [
    636  {
    637    desc:  'keyframes with an out-of-bounded positive offset',
    638    input: [ { opacity: 0 },
    639             { opacity: 0.5, offset: 2 },
    640             { opacity: 1 } ],
    641  },
    642  {
    643    desc:  'keyframes with an out-of-bounded negative offset',
    644    input: [ { opacity: 0 },
    645             { opacity: 0.5, offset: -1 },
    646             { opacity: 1 } ],
    647  },
    648  {
    649    desc:  'property-indexed keyframes not loosely sorted by offset',
    650    input: { opacity: [ 0, 1 ], offset: [ 1, 0 ] },
    651  },
    652  {
    653    desc:  'property-indexed keyframes not loosely sorted by offset even'
    654           + ' though not all offsets are specified',
    655    input: { opacity: [ 0, 0.5, 1 ], offset: [ 0.5, 0 ] },
    656  },
    657  {
    658    desc:  'property-indexed keyframes with offsets out of range',
    659    input: { opacity: [ 0, 0.5, 1 ], offset: [ 0, 1.1 ] },
    660  },
    661  {
    662    desc:  'keyframes not loosely sorted by offset',
    663    input: [ { opacity: 0, offset: 1 },
    664             { opacity: 1, offset: 0 } ],
    665  },
    666  {
    667    desc:  'property-indexed keyframes with an invalid easing value',
    668    input: { opacity: [ 0, 0.5, 1 ],
    669             easing: 'inherit' },
    670  },
    671  {
    672    desc:  'property-indexed keyframes with an invalid easing value as one of'
    673           + ' the array values',
    674    input: { opacity: [ 0, 0.5, 1 ],
    675             easing: [ 'ease-in', 'inherit' ] },
    676  },
    677  {
    678    desc:   'property-indexed keyframe with an invalid easing in the unused'
    679            + ' part of the array of easings',
    680    input:  { left: ['10px', '20px', '30px'],
    681              easing: ['steps(1)', 'steps(2)', 'steps(3)', 'invalid'] },
    682  },
    683  {
    684    desc:   'empty property-indexed keyframe with an invalid easing',
    685    input:  { easing: 'invalid' },
    686  },
    687  {
    688    desc:   'empty property-indexed keyframe with an invalid easings array',
    689    input:  { easing: ['invalid'] },
    690  },
    691  {
    692    desc:  'a keyframe sequence with an invalid easing value',
    693    input: [ { opacity: 0, easing: 'jumpy' },
    694             { opacity: 1 } ],
    695  },
    696  {
    697    desc:  'property-indexed keyframes with an invalid composite value',
    698    input: { opacity: [ 0, 0.5, 1 ],
    699             composite: 'alternate' },
    700  },
    701  {
    702    desc:  'property-indexed keyframes with an invalid composite value as one'
    703           + ' of the array values',
    704    input: { opacity: [ 0, 0.5, 1 ],
    705             composite: [ 'add', 'alternate' ] },
    706  },
    707  {
    708    desc:  'keyframes with an invalid composite value',
    709    input: [ { opacity: 0, composite: 'alternate' },
    710             { opacity: 1 } ],
    711  },
    712 ];
    713 
    714 
    715 const gKeyframeSerializationTests = [
    716  {
    717    desc:   'a on keyframe sequence which requires value serilaization of its'
    718            + ' values',
    719    input:  [{offset: 0, backgroundColor: 'rgb(1,2,3)' }],
    720    output: [keyframe(offset(0), { backgroundColor: 'rgb(1, 2, 3)' })],
    721  },
    722 ];
    723 
    724 
    725 
    726 // ------------------------------
    727 //  KeyframeEffectOptions
    728 // ------------------------------
    729 
    730 const gKeyframeEffectOptionTests = [
    731  {
    732    desc:     'an empty KeyframeEffectOptions object',
    733    input:    { },
    734    expected: { },
    735  },
    736  {
    737    desc:     'a normal KeyframeEffectOptions object',
    738    input:    { delay: 1000,
    739                fill: 'auto',
    740                iterations: 5.5,
    741                duration: 'auto',
    742                direction: 'alternate' },
    743    expected: { delay: 1000,
    744                fill: 'auto',
    745                iterations: 5.5,
    746                duration: 'auto',
    747                direction: 'alternate' },
    748  },
    749  {
    750    desc:     'a double value',
    751    input:    3000,
    752    expected: { duration: 3000 },
    753  },
    754  {
    755    desc:     '+Infinity',
    756    input:    Infinity,
    757    expected: { duration: Infinity },
    758  },
    759  {
    760    desc:     'an Infinity duration',
    761    input:    { duration: Infinity },
    762    expected: { duration: Infinity },
    763  },
    764  {
    765    desc:     'an auto duration',
    766    input:    { duration: 'auto' },
    767    expected: { duration: 'auto' },
    768  },
    769  {
    770    desc:     'an Infinity iterations',
    771    input:    { iterations: Infinity },
    772    expected: { iterations: Infinity },
    773  },
    774  {
    775    desc:     'an auto fill',
    776    input:    { fill: 'auto' },
    777    expected: { fill: 'auto' },
    778  },
    779  {
    780    desc:     'a forwards fill',
    781    input:    { fill: 'forwards' },
    782    expected: { fill: 'forwards' },
    783  }
    784 ];
    785 
    786 const gInvalidKeyframeEffectOptionTests = [
    787  { desc: '-Infinity', input: -Infinity },
    788  { desc: 'NaN', input: NaN },
    789  { desc: 'a negative value', input: -1 },
    790  { desc: 'a negative Infinity duration', input: { duration: -Infinity } },
    791  { desc: 'a NaN duration', input: { duration: NaN } },
    792  { desc: 'a negative duration', input: { duration: -1 } },
    793  { desc: 'a string duration', input: { duration: 'merrychristmas' } },
    794  { desc: 'a negative Infinity iterations', input: { iterations: -Infinity} },
    795  { desc: 'a NaN iterations', input: { iterations: NaN } },
    796  { desc: 'a negative iterations', input: { iterations: -1 } },
    797  { desc: 'a blank easing', input: { easing: '' } },
    798  { desc: 'an unrecognized easing', input: { easing: 'unrecognised' } },
    799  { desc: 'an \'initial\' easing', input: { easing: 'initial' } },
    800  { desc: 'an \'inherit\' easing', input: { easing: 'inherit' } },
    801  { desc: 'a variable easing', input: { easing: 'var(--x)' } },
    802  { desc: 'a multi-value easing', input: { easing: 'ease-in-out, ease-out' } },
    803 ];
    804 
    805 // There is currently only ScrollTimeline that can be constructed and used here
    806 // beyond document timeline. Given that ScrollTimeline is not stable as of yet
    807 // it's tested in scroll-animations/animation-with-animatable-interface.html.
    808 const gAnimationTimelineTests = [
    809  {
    810    expectedTimeline: document.timeline,
    811    expectedTimelineDescription: 'document.timeline',
    812    description: 'with no timeline parameter'
    813  },
    814  {
    815    timeline: undefined,
    816    expectedTimeline: document.timeline,
    817    expectedTimelineDescription: 'document.timeline',
    818    description: 'with undefined timeline'
    819  },
    820  {
    821    timeline: null,
    822    expectedTimeline: null,
    823    expectedTimelineDescription: 'null',
    824    description: 'with null timeline'
    825  },
    826  {
    827    timeline: document.timeline,
    828    expectedTimeline: document.timeline,
    829    expectedTimelineDescription: 'document.timeline',
    830    description: 'with DocumentTimeline'
    831  },
    832 ];