| <!DOCTYPE html> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="/css/support/parsing-testcommon.js"></script> |
| <div id="target"></div> |
| <script> |
| |
| // Runs a function while a stylesheet is temporarily inserted into the |
| // document. |
| function with_stylesheet(text, func) { |
| let s = document.createElement('style'); |
| try { |
| s.textContent = text; |
| document.documentElement.append(s); |
| func(s.sheet.rules); |
| } finally { |
| s.remove(); |
| } |
| } |
| |
| // Runs a test while a stylesheet is temporarily inserted into the |
| // document. |
| function test_stylesheet(text, func, description) { |
| test(() => { |
| with_stylesheet(text, func); |
| }, description); |
| } |
| |
| function test_valid_rule(text, description) { |
| test_stylesheet(text, (rules) => { |
| assert_equals(rules.length, 1); |
| assert_equals(rules[0].constructor.name, 'CSSScrollTimelineRule'); |
| }, description); |
| } |
| |
| function test_invalid_rule(text, description) { |
| test_stylesheet(text, (rules) => { |
| assert_equals(rules.length, 0); |
| }, description); |
| } |
| |
| // Verify that for the _specifed_ value for a given _descriptor_, the _expected_ |
| // string can be observed via the equivalent attribute on CSSScrollTimelineRule. |
| function test_descriptor(descriptor, specified, expected) { |
| if (typeof(expected) == 'undefined') |
| expected = specified; |
| let attribute = descriptor.replaceAll(/\-./g, x => x[1].toUpperCase()); |
| test_stylesheet(`@scroll-timeline test { ${descriptor}:${specified}; }`, (rules) => { |
| assert_equals(rules.length, 1); |
| assert_equals(rules[0].constructor.name, 'CSSScrollTimelineRule'); |
| if (Array.isArray(expected)) { |
| assert_in_array(rules[0][attribute], expected, "serialization should be sound"); |
| } else { |
| assert_equals(rules[0][attribute], expected, "serialization should be canonical"); |
| } |
| }, `CSSScrollTimelineRule.${attribute} ${specified}`); |
| } |
| |
| test_valid_rule('@scroll-timeline foo {}', 'Empty block'); |
| test_valid_rule('@scroll-timeline foo {', 'EOF ends block'); |
| test_valid_rule('@scroll-timeline "foo" {}', 'Timeline name can be a <string>'); |
| |
| test_invalid_rule('@scroll-timeline', 'Missing prelude'); |
| test_invalid_rule('@scroll-timeline foo', 'Missing block'); |
| test_invalid_rule('@scroll-timeline {}', 'Missing timeline name'); |
| test_invalid_rule('@scroll-timeline 123 {}', 'Timeline name must be an identifier'); |
| test_invalid_rule('@scroll-timeline none {}', 'Timeline name must match <custom-ident>'); |
| test_invalid_rule('@scroll-timeline NONE {}', 'Timeline name must match <custom-ident> (caps)'); |
| test_invalid_rule('@scroll-timeline NoNe {}', 'Timeline name must match <custom-ident> (mixed)'); |
| test_invalid_rule('@scroll-timeline initial {}', 'Timeline name may not be initial'); |
| test_invalid_rule('@scroll-timeline inherit {}', 'Timeline name may not be inherit'); |
| test_invalid_rule('@scroll-timeline unset {}', 'Timeline name may not be unset'); |
| test_invalid_rule('@scroll-timeline revert {}', 'Timeline name may not be revert'); |
| test_invalid_rule('@scroll-timeline default {}', 'Timeline name may not be default'); |
| test_invalid_rule('@scroll-timeline foo bar {}', 'Extra timeline name'); |
| |
| // CSSRule.type |
| |
| test(() => { |
| with_stylesheet(`@scroll-timeline valid { }`, (rules) => { |
| assert_equals(rules.length, 1); |
| let rule = rules[0]; |
| assert_equals(rule.constructor.name, 'CSSScrollTimelineRule'); |
| assert_equals(rule.type, 0); |
| }); |
| }, 'CSSRule.type returns 0'); |
| |
| // CSSScrollTimelineRule.name |
| |
| function test_name(specified, expected) { |
| if (typeof(expected) == 'undefined') |
| expected = specified; |
| test_stylesheet(`@scroll-timeline ${specified} { }`, (rules) => { |
| assert_equals(rules.length, 1); |
| assert_equals(rules[0].constructor.name, 'CSSScrollTimelineRule'); |
| assert_equals(rules[0].name, expected); |
| }, `CSSScrollTimelineRule.name ${specified}`); |
| } |
| |
| test_name('foo'); |
| test_name('Foo'); |
| test_name('f___123'); |
| test_name('a\\9 b', 'a\tb'); // U+0009 CHARACTER TABULATION |
| test_name('"foo"', 'foo'); |
| test_name('"none"', 'none'); |
| |
| // CSSScrollTimelineRule.cssText |
| |
| function test_csstext(description, specified, expected) { |
| if (typeof(expected) == 'undefined') |
| expected = specified; |
| test_stylesheet(specified, (rules) => { |
| assert_equals(rules.length, 1); |
| assert_equals(rules[0].constructor.name, 'CSSScrollTimelineRule'); |
| assert_equals(rules[0].cssText, expected); |
| }, `CSSScrollTimelineRule.cssText: ${description}`); |
| } |
| |
| test_csstext( |
| 'empty rule', |
| `@scroll-timeline timeline {}`, |
| `@scroll-timeline timeline { }`); |
| |
| // U+0009 CHARACTER TABULATION |
| test_csstext( |
| 'escaped name', |
| `@scroll-timeline tab\\9 tab {}`, |
| `@scroll-timeline tab\\9 tab { }`); |
| |
| test_csstext( |
| 'source descriptor', |
| `@scroll-timeline timeline { source: selector(#foo); }`); |
| |
| test_csstext( |
| 'orientation descriptor', |
| `@scroll-timeline timeline { orientation: inline; }`); |
| |
| // https://github.com/w3c/csswg-drafts/issues/6617 |
| test_csstext( |
| 'scroll-offsets descriptor (none)', |
| `@scroll-timeline timeline { scroll-offsets: none; }`, |
| `@scroll-timeline timeline { scroll-offsets: none; }`,); |
| |
| test_csstext( |
| 'scroll-offsets descriptor (auto)', |
| `@scroll-timeline timeline { scroll-offsets: auto; }`); |
| |
| test_csstext( |
| 'scroll-offsets descriptor (container-based offset, px)', |
| `@scroll-timeline timeline { scroll-offsets: 100px; }`); |
| |
| test_csstext( |
| 'scroll-offsets descriptor (container-based offset, percentage)', |
| `@scroll-timeline timeline { scroll-offsets: 100%; }`); |
| |
| test_csstext( |
| 'scroll-offsets descriptor (element offset)', |
| `@scroll-timeline timeline { scroll-offsets: selector(#bar); }`); |
| |
| test_csstext( |
| 'scroll-offsets descriptor (element offset with edge)', |
| `@scroll-timeline timeline { scroll-offsets: selector(#bar) start; }`); |
| |
| test_csstext( |
| 'scroll-offsets descriptor (element offset with threshold)', |
| `@scroll-timeline timeline { scroll-offsets: selector(#bar) 1; }`); |
| |
| test_csstext( |
| 'scroll-offsets descriptor (element offset with edge and threshold)', |
| `@scroll-timeline timeline { scroll-offsets: selector(#bar) start 1; }`); |
| |
| test_csstext( |
| 'scroll-offsets descriptor (element offset with threshold and edge)', |
| `@scroll-timeline timeline { scroll-offsets: selector(#bar) 1 start; }`, |
| `@scroll-timeline timeline { scroll-offsets: selector(#bar) start 1; }`); |
| |
| test_csstext( |
| 'scroll-offsets descriptor (multiple offsets)', |
| `@scroll-timeline timeline { scroll-offsets: auto, selector(#bar) start 1; }`); |
| |
| test_csstext( |
| 'defaults', |
| `@scroll-timeline timeline { source: auto; orientation: auto; scroll-offsets: none; }`); |
| |
| test_csstext( |
| 'order', |
| `@scroll-timeline timeline { orientation: auto; source: auto; scroll-offsets: none; }`, |
| `@scroll-timeline timeline { source: auto; orientation: auto; scroll-offsets: none; }`); |
| |
| // CSSScrollTimelineRule.source |
| |
| function test_source(specified, expected) { |
| test_descriptor('source', specified, expected); |
| } |
| |
| test_source('selector(#foo)'); |
| test_source('selector( #foo )', 'selector(#foo)'); |
| test_source(' selector(#foo) ', 'selector(#foo)'); |
| test_source('none'); |
| test_source(' none ', 'none'); |
| test_source('selector(#a\\9 b)'); |
| test_source('auto'); |
| |
| test_source('#foo', 'auto'); |
| test_source('', 'auto'); |
| test_source('element(#foo)', 'auto'); |
| test_source('selector(#foo more)', 'auto'); |
| test_source('selector(html)', 'auto'); |
| test_source('selector(foo)', 'auto'); |
| test_source('selector(:before)', 'auto'); |
| test_source('selector(*)', 'auto'); |
| test_source('selector(.a)', 'auto'); |
| test_source('selector(.a, .b)', 'auto'); |
| |
| // CSSScrollTimelineRule.orientation |
| |
| function test_orientation(specified, expected) { |
| test_descriptor('orientation', specified, expected); |
| } |
| |
| test_orientation('auto'); |
| test_orientation('block'); |
| test_orientation('inline'); |
| test_orientation('horizontal'); |
| test_orientation('vertical'); |
| test_orientation(' vertical ', 'vertical'); |
| |
| test_orientation('', 'auto'); |
| test_orientation('foo', 'auto'); |
| test_orientation('10px', 'auto'); |
| test_orientation('red', 'auto'); |
| |
| // CSSScrollTimelineRule.scrollOffsets |
| |
| function test_offsets(specified, expected) { |
| test_descriptor('scroll-offsets', specified, expected); |
| } |
| |
| test_offsets('none'); |
| test_offsets(' none ', 'none'); |
| test_offsets('', 'none'); |
| test_offsets('red', 'none'); |
| test_offsets('#fff', 'none'); |
| test_offsets('unset', 'none'); |
| test_offsets('unknown(#foo)', 'none'); |
| test_offsets('start', 'none'); |
| test_offsets('end', 'none'); |
| test_offsets('3', 'none'); |
| test_offsets('selector(#foo) 3 start 10', 'none'); |
| test_offsets('0%, 100%, selector(.a)', 'none'); |
| test_offsets('selector(#foo) 3 end, start', 'none'); |
| |
| test_offsets('auto'); |
| test_offsets('10em'); |
| test_offsets('10%'); |
| test_offsets('calc(1px + 1%)', ['calc(1px + 1%)', 'calc(1% + 1px)']); |
| test_offsets('selector(#foo)'); |
| test_offsets(' selector(#foo)', 'selector(#foo)'); |
| test_offsets('selector(#foo) start'); |
| test_offsets('selector(#foo) start 3'); |
| test_offsets('selector(#foo) 3'); |
| test_offsets('selector(#foo) 3.5'); |
| test_offsets('selector(#foo) end'); |
| test_offsets('selector(#foo) end 3'); |
| test_offsets('selector(#foo) 3 end', 'selector(#foo) end 3'); |
| test_offsets(' auto , auto ', 'auto, auto'); |
| test_offsets('10%, 100px'); |
| test_offsets('auto, 100%'); |
| test_offsets('0%, selector( #foo) 3 end ', "0%, selector(#foo) end 3"); |
| test_offsets('selector(#foo) end 3, selector(#bar)'); |
| test_offsets('0%, auto, selector(#foo) start, auto, selector(#bar) 12.3'); |
| |
| </script> |