| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <title>ScrollTimeline constructor</title> | |
| <link rel="help" href="https://wicg.github.io/scroll-animations/#scrolltimeline-interface"> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <style> | |
| .scroller { | |
| height: 100px; | |
| width: 100px; | |
| overflow: scroll; | |
| } | |
| .content { | |
| height: 500px; | |
| width: 500px; | |
| } | |
| </style> | |
| <div class="scroller"> | |
| <div class="content"></div> | |
| </div> | |
| <script> | |
| 'use strict'; | |
| function formatOffset(v) { | |
| if (typeof(v) == 'object') | |
| return `${v.constructor.name}(${v.toString()})`; | |
| return `'${v.toString()}'`; | |
| } | |
| function assert_offsets_equal(a, b) { | |
| assert_equals(formatOffset(a), formatOffset(b)); | |
| } | |
| // source | |
| test(t => { | |
| const scroller = document.querySelector('.scroller'); | |
| assert_equals( | |
| new ScrollTimeline({source: scroller}).source, scroller); | |
| }, 'A ScrollTimeline can be created with a source'); | |
| test(t => { | |
| const div = document.createElement('div'); | |
| assert_equals(new ScrollTimeline({source: div}).source, div); | |
| }, 'A ScrollTimeline can be created with a non-scrolling source'); | |
| test(t => { | |
| assert_equals(new ScrollTimeline({source: null}).source, null); | |
| }, 'A ScrollTimeline created with a null source should have no source'); | |
| test(t => { | |
| assert_equals(new ScrollTimeline().source, document.scrollingElement); | |
| }, 'A ScrollTimeline created without a source should use the ' + | |
| 'document.scrollingElement'); | |
| // orientation | |
| test(t => { | |
| assert_equals(new ScrollTimeline().orientation, 'block'); | |
| }, 'A ScrollTimeline created with the default orientation should default to ' + | |
| `'block'`); | |
| const gValidOrientationValues = [ | |
| 'block', | |
| 'inline', | |
| 'horizontal', | |
| 'vertical', | |
| ]; | |
| for (let orientation of gValidOrientationValues) { | |
| test(function() { | |
| const scrollTimeline = | |
| new ScrollTimeline({orientation: orientation}); | |
| assert_equals(scrollTimeline.orientation, orientation); | |
| }, `'${orientation}' is a valid orientation value`); | |
| } | |
| test(t => { | |
| let constructorFunc = function() { | |
| new ScrollTimeline({orientation: 'nonsense'}) | |
| }; | |
| assert_throws_js(TypeError, constructorFunc); | |
| // 'auto' for orientation was previously in the spec, but was removed. Make | |
| // sure that implementations do not support it. | |
| constructorFunc = function() { | |
| new ScrollTimeline({orientation: 'auto'}) | |
| }; | |
| assert_throws_js(TypeError, constructorFunc); | |
| }, 'Creating a ScrollTimeline with an invalid orientation value should throw'); | |
| // scrollOffsets | |
| function formatOffset(v) { | |
| if (typeof(v) == 'object') | |
| return `${v.constructor.name}(${v.toString()})`; | |
| return `'${v.toString()}'`; | |
| } | |
| function assert_offsets_equal(a, b) { | |
| assert_equals(formatOffset(a), formatOffset(b)); | |
| } | |
| test(t => { | |
| assert_array_equals(new ScrollTimeline().scrollOffsets, []); | |
| }, 'A ScrollTimeline created with the default scrollOffsets should default ' + | |
| 'to []'); | |
| test(t => { | |
| assert_array_equals( | |
| new ScrollTimeline({scrollOffsets: []}).scrollOffsets, []); | |
| }, 'A ScrollTimeline created with empty scrollOffsets should resolve to []'); | |
| test(t => { | |
| let offsets = | |
| new ScrollTimeline({scrollOffsets: [CSS.percent(20), 'auto']}) | |
| .scrollOffsets; | |
| assert_offsets_equal(offsets[0], CSS.percent(20)); | |
| assert_offsets_equal(offsets[1], new CSSKeywordValue('auto')); | |
| }, 'A ScrollTimeline created with last \'auto\' offset in scrollOffsets ' + | |
| 'should be allowed.'); | |
| test(t => { | |
| let constructorFunc = function() { | |
| new ScrollTimeline({scrollOffsets: null}) | |
| }; | |
| assert_throws_js(TypeError, constructorFunc); | |
| }, 'Creating a ScrollTimeline with an invalid scrollOffsets value should ' + | |
| 'throw.'); | |
| test(t => { | |
| let constructorFunc = function() { | |
| new ScrollTimeline( | |
| {scrollOffsets: [CSS.percent(20), 'auto', CSS.percent(50)]}) | |
| }; | |
| assert_throws_js(TypeError, constructorFunc); | |
| }, 'Creating a ScrollTimeline with an scrollOffsets value of ' + | |
| '[CSS.percent(20), \'auto\', CSS.percent(50)] should throw'); | |
| const gValidScrollOffsetValues = [ | |
| CSS.px(0), | |
| CSS.percent(100).sub(CSS.px(80)), | |
| ]; | |
| const gValidScrollOffsetSuffixes = [ | |
| // Relative lengths. | |
| 'em', | |
| 'ex', | |
| 'ch', | |
| 'rem', | |
| 'vw', | |
| 'vh', | |
| 'vmin', | |
| 'vmax', | |
| // Absolute lengths. | |
| 'cm', | |
| 'mm', | |
| 'q', | |
| 'in', | |
| 'pc', | |
| 'pt', | |
| 'px', | |
| // Percentage. | |
| '%', | |
| ]; | |
| for (let offset of gValidScrollOffsetValues) { | |
| test(function() { | |
| const scrollTimeline = | |
| new ScrollTimeline({scrollOffsets: [offset, offset]}); | |
| // Special case for 'auto'. This is valid input because of CSSKeywordish, | |
| // but when read back we expect a real CSSKeywordValue. | |
| if (offset === 'auto') | |
| offset = new CSSKeywordValue('auto'); | |
| assert_offsets_equal(scrollTimeline.scrollOffsets[0], offset); | |
| assert_offsets_equal(scrollTimeline.scrollOffsets[1], offset); | |
| }, '\'' + offset + '\' is a valid scroll offset value'); | |
| } | |
| for (const suffix of gValidScrollOffsetSuffixes) { | |
| test(function() { | |
| const offset = new CSSUnitValue(75, suffix); | |
| const scrollTimeline = | |
| new ScrollTimeline({scrollOffsets: [offset, offset]}); | |
| assert_offsets_equal(scrollTimeline.scrollOffsets[0], offset); | |
| assert_offsets_equal(scrollTimeline.scrollOffsets[1], offset); | |
| }, '\'' + suffix + '\' is a valid scroll offset unit'); | |
| } | |
| // These are deliberately incomplete, just a random sampling of invalid | |
| // values/units. | |
| const gInvalidScrollOffsetValues = [ | |
| '', | |
| 'calc(360deg / 4)', | |
| 'left', | |
| '#ff0000', | |
| 'rgb(0, 128, 0)', | |
| 'url("http://www.example.com/pinkish.gif")', | |
| 'this_is_garbage', | |
| CSS.number(0), | |
| // Multiple valid values. | |
| '100px 5%', | |
| // Values that would be valid if represented with CSS Typed OM: | |
| 0, | |
| '10px', | |
| '10%', | |
| 'calc(100% - 80px)', | |
| ]; | |
| const gInvalidScrollOffsetSuffixes = [ | |
| 'deg', | |
| 's', | |
| 'Hz', | |
| 'dpi', | |
| ]; | |
| for (const offset of gInvalidScrollOffsetValues) { | |
| test(function() { | |
| const constructorFunc = function() { | |
| new ScrollTimeline({scrollOffsets: ['0px', offset]}) | |
| }; | |
| assert_throws_js(TypeError, constructorFunc); | |
| }, formatOffset(offset) + ' is an invalid scroll offset value in scrollOffsets'); | |
| } | |
| for (const suffix of gInvalidScrollOffsetSuffixes) { | |
| test(function() { | |
| const offset = '75' + suffix; | |
| const constructorFunc = function() { | |
| new ScrollTimeline({scrollOffsets: ['0px', offset]}); | |
| }; | |
| assert_throws_js(TypeError, constructorFunc); | |
| }, '\'' + suffix + '\' is an invalid scroll offset unit in scrollOffsets'); | |
| } | |
| const offset_target = document.createElement('div'); | |
| const gValidElementBasedScrollOffsetValues = [ | |
| {target: offset_target}, | |
| {target: offset_target, threshold: 0}, | |
| {target: offset_target, threshold: 0.5}, | |
| {target: offset_target, threshold: 1}, | |
| ]; | |
| for (let offset of gValidElementBasedScrollOffsetValues) { | |
| test(function() { | |
| const scrollTimeline = | |
| new ScrollTimeline({scrollOffsets: [offset, offset]}); | |
| // Special case unspecified threshold since it gets initialized to 0. | |
| if (!offset.hasOwnProperty('threshold')) | |
| offset.threshold = 0; | |
| assert_equals(scrollTimeline.scrollOffsets[0].target, offset.target); | |
| assert_equals(scrollTimeline.scrollOffsets[0].threshold, offset.threshold); | |
| assert_equals(scrollTimeline.scrollOffsets[1].target, offset.target); | |
| assert_equals(scrollTimeline.scrollOffsets[1].threshold, offset.threshold); | |
| }, '\'' + JSON.stringify(offset) + '\' is a valid scroll offset value'); | |
| } | |
| const gInvalidElementBasedScrollOffsetValues = [ | |
| {}, // empty | |
| {target: offset_target, threshold: "test"}, | |
| {target: offset_target, threshold: 2}, | |
| {target: offset_target, threshold: -0.2}, | |
| ]; | |
| for (let offset of gInvalidElementBasedScrollOffsetValues) { | |
| test(function() { | |
| const constructorFunc = function() { | |
| new ScrollTimeline({scrollOffsets: [offset]}) | |
| }; | |
| assert_throws_js(TypeError, constructorFunc); | |
| }, `'${JSON.stringify(offset)}' is an invalid scroll offset value in ` + | |
| `scrollOffsets`); | |
| } | |
| </script> |