| <!DOCTYPE HTML> |
| <meta charset="UTF-8"> |
| <title>CSS Toggles: CSSToggle and CSSToggleMap API</title> |
| <link rel="author" title="L. David Baron" href="https://dbaron.org/"> |
| <link rel="author" title="Google" href="http://www.google.com/"> |
| <link rel="help" href="https://tabatkins.github.io/css-toggle/#dom"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="support/toggle-helpers.js"></script> |
| |
| <div id="test"></div> |
| |
| <script> |
| |
| let container = document.getElementById("test"); |
| |
| promise_test(async function() { |
| container.innerHTML = ` |
| <style> |
| :toggle(mytoggle) { --t:4; } |
| :toggle(newtoggle) { --t:7; } |
| :toggle(newname) { --t:9; } |
| </style> |
| <div> |
| <div id="a" style="toggle-root: mytoggle 2 at 1 self"></div> |
| </div> |
| <div> |
| <div id="b"></div> |
| </div> |
| `; |
| let a = document.getElementById("a"); |
| let b = document.getElementById("b"); |
| assert_equals(a.toggles.size, 0, "a.toggles.size before creation"); |
| assert_equals(b.toggles.size, 0, "b.toggles.size before creation"); |
| await wait_for_toggle_creation(a); |
| assert_equals(a.toggles.size, 1, "a.toggles.size after creation"); |
| assert_equals(b.toggles.size, 0, "b.toggles.size after creation"); |
| let t = a.toggles.get("mytoggle"); |
| for (let item of a.toggles) { |
| assert_equals(item[0], "mytoggle", "iteration of a.toggles"); |
| assert_equals(item[1], t, "iteration of a.toggles"); |
| } |
| for (let item of a.toggles.entries()) { |
| assert_equals(item[0], "mytoggle", "iteration of a.toggles.entries()"); |
| assert_equals(item[1], t, "iteration of a.toggles.entries()"); |
| } |
| for (let item of a.toggles.keys()) { |
| assert_equals(item, "mytoggle", "iteration of a.toggles.keys()"); |
| } |
| for (let item of a.toggles.values()) { |
| assert_equals(item, t, "iteration of a.toggles.values()"); |
| } |
| assert_equals(Object.getPrototypeOf(a.toggles), CSSToggleMap.prototype); |
| assert_equals(Object.getPrototypeOf(t), CSSToggle.prototype); |
| let computed = (element) => getComputedStyle(element).getPropertyValue("--t"); |
| assert_equals(computed(a), "4", "computed style before move"); |
| assert_equals(computed(b), "", "computed style before move"); |
| |
| b.toggles.set("newtoggle", t); |
| assert_equals(a.toggles.size, 0, "a.toggles.size after move"); |
| assert_equals(b.toggles.size, 1, "b.toggles.size after move"); |
| assert_equals(computed(a), "", "computed style after move"); |
| assert_equals(computed(b), "7", "computed style after move"); |
| |
| assert_equals(a.toggles.get("mytoggle"), undefined, 'a.toggles.get("mytoggle") after move'); |
| assert_equals(a.toggles.get("newtoggle"), undefined, 'a.toggles.get("newtoggle") after move'); |
| assert_equals(b.toggles.get("mytoggle"), undefined, 'b.toggles.get("mytoggle") after move'); |
| assert_equals(b.toggles.get("newtoggle"), t, 'b.toggles.get("newtoggle") after move'); |
| |
| b.toggles.set("newname", t); |
| assert_equals(a.toggles.size, 0, "a.toggles.size after rename"); |
| assert_equals(b.toggles.size, 1, "b.toggles.size after rename"); |
| assert_equals(computed(a), "", "computed style after rename"); |
| assert_equals(computed(b), "9", "computed style after rename"); |
| assert_equals(a.toggles.get("mytoggle"), undefined, 'a.toggles.get("mytoggle") after rename'); |
| assert_equals(a.toggles.get("newtoggle"), undefined, 'a.toggles.get("newtoggle") after rename'); |
| assert_equals(a.toggles.get("newname"), undefined, 'a.toggles.get("newname") after rename'); |
| assert_equals(b.toggles.get("mytoggle"), undefined, 'b.toggles.get("mytoggle") after rename'); |
| assert_equals(b.toggles.get("newtoggle"), undefined, 'b.toggles.get("newtoggle") after rename'); |
| assert_equals(b.toggles.get("newname"), t, 'b.toggles.get("newname") after rename'); |
| |
| assert_throws_dom("SyntaxError", () => { a.toggles.set("none", t); }, |
| "setting toggle_name to 'none'"); |
| assert_equals(a.toggles.size, 0, "a.toggles.size after failed set"); |
| assert_equals(b.toggles.size, 1, "b.toggles.size after failed set"); |
| assert_equals(b.toggles.get("newname"), t, "b.toggles.get after failed set"); |
| |
| assert_throws_dom("SyntaxError", () => { let t = new CSSToggle({ "states": [] }); }, |
| "toggle constructor with empty list of states"); |
| assert_throws_dom("SyntaxError", () => { let t = new CSSToggle({ "states": ["one"] }); }, |
| "toggle constructor with only one state"); |
| assert_throws_dom("SyntaxError", () => { let t = new CSSToggle({ "states": ["one", "two", "one"] }); }, |
| "toggle constructor with duplicate states"); |
| let c = new CSSToggle({ "states": ["one", "two", "three"] }); |
| assert_throws_dom("SyntaxError", () => { c.states = []; }, |
| "toggle states setter with empty list of states"); |
| assert_throws_dom("SyntaxError", () => { c.states = ["one"]; }, |
| "toggle states setter with only one state"); |
| assert_throws_dom("SyntaxError", () => { c.states = ["one", "two", "one"]; }, |
| "toggle states setter with duplicate states"); |
| |
| // TODO(https://crbug.com/1250716): Should the toggle on a be |
| // re-created at some point? If so, when? |
| |
| }, "CSSToggleMap basic API usage and moving toggle"); |
| |
| promise_test(async function() { |
| container.innerHTML = ` |
| <style> |
| :toggle(mytoggle 0) { --t:0; } |
| :toggle(mytoggle 1) { --t:1; } |
| :toggle(mytoggle 2) { --t:2; } |
| :toggle(mytoggle 3) { --t:3; } |
| :toggle(mytoggle 4) { --t:4; } |
| </style> |
| <div id="a" style="toggle: mytoggle 2 at 1 self"></div> |
| <div id="b"></div> |
| `; |
| let a = document.getElementById("a"); |
| let b = document.getElementById("b"); |
| let computed = (elt) => getComputedStyle(elt).getPropertyValue("--t"); |
| |
| await wait_for_toggle_creation(a); |
| let t = a.toggles.get("mytoggle"); |
| |
| assert_equals(computed(a), "1", "initial state of toggle"); |
| assert_equals(t.value, 1, "CSSToggle.value in initial state"); |
| assert_equals(t.valueAsNumber, 1, "CSSToggle.valueAsNumber in initial state"); |
| assert_equals(t.valueAsString, null, "CSSToggle.valueAsString in initial state"); |
| assert_equals(t.states, 2, "CSSToggle.states in initial state"); |
| assert_equals(t.group, false, "CSSToggle.group in initial state"); |
| assert_equals(t.scope, "narrow", "CSSToggle.scope in initial state"); |
| assert_equals(t.cycle, "cycle", "CSSToggle.cycle in initial state"); |
| a.click(); |
| assert_equals(computed(a), "2", "state of toggle after click"); |
| assert_equals(t.value, 2, "CSSToggle.value after click"); |
| assert_equals(t.valueAsNumber, 2, "CSSToggle.valueAsNumber after click"); |
| assert_equals(t.valueAsString, null, "CSSToggle.valueAsString after click"); |
| |
| t.value = 1; |
| assert_equals(computed(a), "1", "state after setting value"); |
| assert_equals(t.value, 1, "CSSToggle.value after setting value"); |
| assert_equals(t.valueAsNumber, 1, "CSSToggle.valueAsNumber after setting value"); |
| assert_equals(t.valueAsString, null, "CSSToggle.valueAsString after setting value"); |
| |
| t.states = ["zero", "one", "two", "three", "four"] |
| assert_equals(computed(a), "1", "state after setting states"); |
| assert_equals(t.value, 1, "CSSToggle.value after setting states"); |
| assert_equals(t.valueAsNumber, 1, "CSSToggle.valueAsNumber after setting states"); |
| assert_equals(t.valueAsString, "one", "CSSToggle.valueAsString after setting states"); |
| |
| t.value = "three"; |
| assert_equals(computed(a), "3", "state after changing value with new states"); |
| assert_equals(t.value, "three", "CSSToggle.value after changing value with new states"); |
| assert_equals(t.valueAsNumber, 3, "CSSToggle.valueAsNumber after changing value with new states"); |
| assert_equals(t.valueAsString, "three", "CSSToggle.valueAsString after changing value with new states"); |
| |
| // dynamic changes to group are tested in a separate test below |
| |
| assert_equals(computed(a), "3", "a state before changing scope"); |
| assert_equals(computed(b), "", "b state before changing scope"); |
| t.scope = "wide"; |
| assert_equals(computed(a), "3", "a state after changing scope"); |
| assert_equals(computed(b), "3", "b state after changing scope"); |
| t.scope = "narrow"; |
| assert_equals(computed(a), "3", "a state after changing scope again"); |
| assert_equals(computed(b), "", "b state after changing scope again"); |
| |
| t.value = 4; |
| assert_equals(computed(a), "4", "state after changing value again") |
| a.click(); |
| assert_equals(computed(a), "0", "state after cycling with initial cycle") |
| t.value = 4; |
| t.cycle = "cycle-on"; |
| assert_equals(computed(a), "4", "state after changing cycle to cycle-on, with toggle-root still set") |
| a.click(); |
| assert_equals(computed(a), "0", "state after cycling with cycle-on, with toggle-root still set") |
| // now remove the toggle-root property so that it no longer overrides the |
| // toggle when changing a toggle. |
| a.style.toggleRoot = ""; |
| t.value = 4; |
| t.cycle = "cycle-on"; |
| assert_equals(computed(a), "4", "state after changing cycle to cycle-on") |
| a.click(); |
| assert_equals(computed(a), "1", "state after cycling with cycle-on") |
| t.value = 4; |
| t.cycle = "sticky"; |
| assert_equals(computed(a), "4", "state after changing cycle to sticky") |
| a.click(); |
| assert_equals(computed(a), "4", "state after cycling with sticky") |
| t.value = 4; |
| t.cycle = "cycle"; |
| assert_equals(computed(a), "4", "state after changing cycle to cycle") |
| a.click(); |
| assert_equals(computed(a), "0", "state after cycling with cycle") |
| }, "CSSToggle basic API usage on existing toggle"); |
| |
| |
| promise_test(async function() { |
| container.innerHTML = ` |
| <style> |
| :toggle(grouptoggle 0) { --g:0; } |
| :toggle(grouptoggle 1) { --g:1; } |
| :toggle(grouptoggle 2) { --g:2; } |
| </style> |
| <!-- use the document-wide implicit toggle group --> |
| <div id="a" style="toggle: grouptoggle 2 at 1 self cycle-on"></div> |
| <div id="b" style="toggle: grouptoggle 2 at 1 self cycle-on"></div> |
| `; |
| let a = document.getElementById("a"); |
| let b = document.getElementById("b"); |
| let computed = (elt) => getComputedStyle(elt).getPropertyValue("--g"); |
| |
| await wait_for_toggle_creation(a); |
| await wait_for_toggle_creation(b); |
| let ta = a.toggles.get("grouptoggle"); |
| let tb = b.toggles.get("grouptoggle"); |
| |
| assert_equals(computed(a), "1", "initial state of a"); |
| assert_equals(computed(b), "1", "initial state of b"); |
| assert_equals(ta.group, false, "initial group of a"); |
| assert_equals(tb.group, false, "initial group of b"); |
| |
| // Remove the toggle-root property so it doesn't override the group |
| // on the toggle. |
| a.style.toggleRoot = ""; |
| b.style.toggleRoot = ""; |
| |
| ta.group = true; |
| a.click(); |
| assert_equals(computed(a), "2", "state of a after first click"); |
| assert_equals(computed(b), "1", "state of b after first click"); |
| |
| tb.group = true; |
| assert_equals(computed(a), "2", "state of a after both in group"); |
| assert_equals(computed(b), "1", "state of b after both in group"); |
| a.click(); |
| assert_equals(computed(a), "1", "state of a after second click"); |
| assert_equals(computed(b), "0", "state of b after second click"); |
| b.click(); |
| assert_equals(computed(a), "0", "state of a after third click"); |
| assert_equals(computed(b), "1", "state of b after third click"); |
| ta.group = false; |
| a.click(); |
| assert_equals(computed(a), "1", "state of a after fourth click"); |
| assert_equals(computed(b), "1", "state of b after fourth click"); |
| |
| ta.group = true; |
| tb.group = true; |
| a.click(); |
| assert_equals(computed(a), "2", "state of a after fifth click"); |
| assert_equals(computed(b), "0", "state of b after fifth click"); |
| // Put the toggle-root property back so that it overrides the group |
| // on the toggle, but only when changing *that* toggle. |
| b.style.toggleRoot = "grouptoggle 2 at 1 self cycle-on"; |
| b.click(); |
| assert_equals(computed(a), "2", "state of a after sixth click"); |
| assert_equals(computed(b), "1", "state of b after sixth click"); |
| a.click(); |
| assert_equals(computed(a), "1", "state of a after seventh click"); |
| assert_equals(computed(b), "0", "state of b after seventh click"); |
| }, "CSSToggle usage of group setter on existing toggle"); |
| |
| promise_test(async function() { |
| container.innerHTML = ` |
| <style> |
| :toggle(d 0) { --d:0; } |
| :toggle(d 1) { --d:1; } |
| :toggle(d 2) { --d:2; } |
| :toggle(e 0) { --e:0; } |
| :toggle(e 1) { --e:1; } |
| :toggle(e 2) { --e:2; } |
| :toggle(f 0) { --f:0; } |
| :toggle(f 1) { --f:1; } |
| :toggle(f 2) { --f:2; } |
| </style> |
| <div id="a"></div> |
| <!-- TODO(https://crbug.com/1250716): This toggle-trigger should probably |
| also work if I set it dynamically right before the first click. --> |
| <div id="b" style="toggle-trigger: d next, e prev"></div> |
| `; |
| let a = document.getElementById("a"); |
| let b = document.getElementById("b"); |
| assert_equals(a.toggles.size, 0, "a.toggles.size before creation"); |
| assert_equals(b.toggles.size, 0, "b.toggles.size before creation"); |
| |
| let computed = (elt, prop) => getComputedStyle(elt).getPropertyValue(`--${prop}`); |
| |
| assert_equals(computed(a, "d"), "", "initial computed(a, d)"); |
| assert_equals(computed(a, "e"), "", "initial computed(a, e)"); |
| assert_equals(computed(b, "d"), "", "initial computed(b, d)"); |
| assert_equals(computed(b, "e"), "", "initial computed(b, e)"); |
| |
| let t1 = new CSSToggle({states: 2, cycle: "cycle-on"}); |
| let t2 = new CSSToggle({states: 2, value: 1, scope: "narrow"}); |
| |
| a.toggles.set("d", t1); |
| assert_equals(a.toggles.size, 1, "step 2 a.toggles.size"); |
| assert_equals(b.toggles.size, 0, "step 2 b.toggles.size"); |
| assert_equals(computed(a, "d"), "0", "step 2 computed(a, d)"); |
| assert_equals(computed(b, "d"), "0", "step 2 computed(b, d)"); |
| assert_equals(computed(a, "e"), "", "step 2 computed(a, e)"); |
| assert_equals(computed(b, "e"), "", "step 2 computed(b, e)"); |
| |
| b.toggles.set("e", t2); |
| assert_equals(a.toggles.size, 1, "step 3 a.toggles.size"); |
| assert_equals(b.toggles.size, 1, "step 3 b.toggles.size"); |
| assert_equals(computed(a, "d"), "0", "step 3 computed(a, d)"); |
| assert_equals(computed(b, "d"), "0", "step 3 computed(b, d)"); |
| assert_equals(computed(a, "e"), "", "step 3 computed(a, e)"); |
| assert_equals(computed(b, "e"), "1", "step 3 computed(b, e)"); |
| |
| b.click(); |
| assert_equals(computed(a, "d"), "1", "step 4 computed(a, d)"); |
| assert_equals(computed(b, "d"), "1", "step 4 computed(b, d)"); |
| assert_equals(computed(a, "e"), "", "step 4 computed(a, e)"); |
| assert_equals(computed(b, "e"), "0", "step 4 computed(b, e)"); |
| |
| a.toggles.set("f", t2); |
| assert_equals(a.toggles.size, 2, "step 5 a.toggles.size"); |
| assert_equals(b.toggles.size, 0, "step 5 b.toggles.size"); |
| assert_equals(computed(a, "d"), "1", "step 5 computed(a, d)"); |
| assert_equals(computed(b, "d"), "1", "step 5 computed(b, d)"); |
| assert_equals(computed(a, "e"), "", "step 5 computed(a, e)"); |
| assert_equals(computed(b, "e"), "", "step 5 computed(b, e)"); |
| assert_equals(computed(a, "f"), "0", "step 5 computed(a, f)"); |
| assert_equals(computed(b, "f"), "", "step 5 computed(b, f)"); |
| |
| b.click(); |
| assert_equals(computed(a, "d"), "2", "step 6 computed(a, d)"); |
| assert_equals(computed(b, "d"), "2", "step 6 computed(b, d)"); |
| assert_equals(computed(a, "e"), "", "step 6 computed(a, e)"); |
| assert_equals(computed(b, "e"), "", "step 6 computed(b, e)"); |
| assert_equals(computed(a, "f"), "0", "step 6 computed(a, f)"); |
| assert_equals(computed(b, "f"), "", "step 6 computed(b, f)"); |
| }, "dynamic creation of CSSToggle and their use"); |
| |
| </script> |