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