| <!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> |