| <!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)); |
| } |
| |
| // TODO(smcgruer): In many of the tests below, timeRange is specified when it |
| // should not need to be. This is an artifact of the initial Chrome |
| // implementation which doesn't support timeRange: 'auto'. These should be |
| // removed in the future. |
| |
| // scrollSource |
| |
| test(t => { |
| const scroller = document.querySelector('.scroller'); |
| assert_equals( |
| new ScrollTimeline({scrollSource: scroller, timeRange: 100}).scrollSource, |
| scroller); |
| }, 'A ScrollTimeline can be created with a scrollSource'); |
| |
| test(t => { |
| const div = document.createElement('div'); |
| assert_equals( |
| new ScrollTimeline({scrollSource: div, timeRange: 100}).scrollSource, |
| div); |
| }, 'A ScrollTimeline can be created with a non-scrolling scrollSource'); |
| |
| test(t => { |
| assert_equals( |
| new ScrollTimeline({scrollSource: null, timeRange: 100}).scrollSource, |
| null); |
| }, 'A ScrollTimeline created with a null scrollSource should have no scrollSource'); |
| |
| test(t => { |
| assert_equals( |
| new ScrollTimeline({timeRange: 100}).scrollSource, |
| document.scrollingElement); |
| }, 'A ScrollTimeline created without a scrollSource should use the document.scrollingElement'); |
| |
| // orientation |
| |
| test(t => { |
| assert_equals(new ScrollTimeline({timeRange: 100}).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, timeRange: 100}); |
| assert_equals(scrollTimeline.orientation, orientation); |
| }, '\'' + orientation + '\' is a valid orientation value'); |
| } |
| |
| test(t => { |
| let constructorFunc = function() { |
| new ScrollTimeline({orientation: 'nonsense', timeRange: 100}) |
| }; |
| 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', timeRange: 100}) |
| }; |
| 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({timeRange: 100}).scrollOffsets, []); |
| }, 'A ScrollTimeline created with the default scrollOffsets should default to []'); |
| |
| test(t => { |
| assert_array_equals(new ScrollTimeline({timeRange: 100, scrollOffsets: []}).scrollOffsets, []); |
| }, 'A ScrollTimeline created with empty scrollOffsets should resolve to []'); |
| |
| test(t => { |
| let offsets = new ScrollTimeline({timeRange: 100, 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({timeRange: 100, scrollOffsets: null}) |
| }; |
| assert_throws_js(TypeError, constructorFunc); |
| }, 'Creating a ScrollTimeline with an invalid scrollOffsets value should throw'); |
| |
| test(t => { |
| let constructorFunc = function() { |
| new ScrollTimeline({timeRange: 100, 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( |
| {timeRange: 100, 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( |
| {timeRange: 100, 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( |
| {timeRange: 100, 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( |
| {timeRange: 100, 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( |
| {timeRange: 100, 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( |
| {timeRange: 100, scrollOffsets: [offset]}) |
| }; |
| assert_throws_js(TypeError, constructorFunc); |
| }, '\'' + JSON.stringify(offset) + '\' is an invalid scroll offset value in scrollOffsets'); |
| } |
| |
| |
| |
| // timeRange |
| |
| test(function() { |
| assert_equals(new ScrollTimeline().timeRange, 'auto'); |
| }, 'A ScrollTimeline created with the default timeRange should default to \'auto\''); |
| |
| const gValidTimeRangeValues = [ |
| 'auto', |
| 0, |
| -100, |
| 100, |
| 1234.5678, |
| ]; |
| |
| for (let timeRange of gValidTimeRangeValues) { |
| test(function() { |
| const scrollTimeline = new ScrollTimeline({timeRange: timeRange}); |
| assert_equals(scrollTimeline.timeRange, timeRange); |
| }, '\'' + timeRange + '\' is a valid timeRange value'); |
| } |
| |
| const gInvalidTimeRangeValues = [ |
| 'invalid', |
| Infinity, |
| -Infinity, |
| NaN, |
| ]; |
| |
| for (let timeRange of gInvalidTimeRangeValues) { |
| test(function() { |
| const constructorFunc = function() { |
| new ScrollTimeline({timeRange: timeRange}); |
| }; |
| assert_throws_js(TypeError, constructorFunc); |
| }, '\'' + timeRange + '\' is an invalid timeRange value'); |
| } |
| </script> |