<!DOCTYPE HTML>
<meta charset="UTF-8">
<title>CSS Toggles: activation of toggles</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/#toggle-trigger-property">
<link rel="help" href="https://tabatkins.github.io/css-toggle/#fire-a-toggle-activation">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/toggle-helpers.js"></script>
<style>
:toggle(finding-test 0) { --finding-test:0; }
:toggle(finding-test 1) { --finding-test:1; }
:toggle(test-states 0) { --test-states:0; }
:toggle(test-states 1) { --test-states:1; }
:toggle(test-states 2) { --test-states:2; }
:toggle(test-group 0) { --test-group:0; }
:toggle(test-group 1) { --test-group:1; }
:toggle(test-overflow 0) { --test-overflow:0; }
:toggle(test-overflow 1) { --test-overflow:1; }
:toggle(set-num 0) { --set-num:0; }
:toggle(set-num 1) { --set-num:1; }
:toggle(set-num 2) { --set-num:2; }
:toggle(set-names zero) { --set-names:0; }
:toggle(set-names one) { --set-names:1; }
:toggle(set-names two) { --set-names:2; }
:toggle(set-names three) { --set-names:3; }
</style>

<body>

<div id="container"></div>
<script>

let container = document.getElementById("container");

let toggle_finding_tests = [
  {
    description: "wide toggle on previous sibling",
    markup: `<div>
               <div id="toggle"></div>
               <div id="target"></div>
             </div>`,
    found: true
  },
  {
    description: "narrow toggle on previous sibling",
    markup: `<div>
               <div id="toggle"></div>
               <div id="target"></div>
             </div>`,
    found: false,
    self: true
  },
  {
    description: "wide toggle on previous sibling with intervening narrow toggle",
    markup: `<div>
               <div id="toggle"></div>
               <div style="toggle: finding-test self"></div>
               <div id="target"></div>
             </div>`,
    found: true
  },
  {
    description: "wide toggle on parent with intervening narrow toggle",
    markup: `<div id="toggle">
               <div style="toggle: finding-test self"></div>
               <div id="target"></div>
             </div>`,
    found: true
  },
  {
    description: "wide toggle on parent's prior sibling with intervening narrow toggle",
    markup: `<div id="toggle"></div>
             <div>
               <div style="toggle: finding-test self"></div>
               <div id="target"></div>
             </div>`,
    found: true
  },
  {
    description: "wide toggle on later sibling",
    markup: `<div id="target"></div>
             <div id="toggle"></div>`,
    found: false
  },
  {
    description: "wide toggle on child",
    markup: `<div id="target">
               <div id="toggle"></div>
             </div>`,
    found: false
  }
];

for (let toggle_finding_test of toggle_finding_tests) {
  promise_test(async function() {
    container.innerHTML = toggle_finding_test.markup;
    let toggle = document.getElementById("toggle");
    let toggle_cs = getComputedStyle(toggle);
    let target = document.getElementById("target");
    let toggle_root_style = "finding-test";
    if (toggle_finding_test.self) {
      toggle_root_style += " self";
    }
    toggle.style.toggleRoot = toggle_root_style;
    target.style.toggleTrigger = "finding-test";
    await wait_for_toggle_creation(toggle);
    assert_true(toggle.matches(':toggle(finding-test 0)'),
                "matches before click");
    assert_equals(toggle_cs.getPropertyValue("--finding-test"), "0",
                  "computed style before click");
    target.click();
    if (toggle_finding_test.found) {
      assert_true(toggle.matches(':toggle(finding-test 1)'),
                  "matches after click");
      assert_equals(toggle_cs.getPropertyValue("--finding-test"), "1",
                    "computed style after click");
    } else {
      assert_true(toggle.matches(':toggle(finding-test 0)'),
                  "matches after click");
      assert_equals(toggle_cs.getPropertyValue("--finding-test"), "0",
                    "computed style after click");
    }
  }, `finding toggle: ${toggle_finding_test.description}`);
}

promise_test(async function() {
  let e = await set_up_single_toggle_in(container, "test-states 1 at 0");
  let cs = getComputedStyle(e);
  assert_true(e.matches(":toggle(test-states 0)"));
  assert_equals(cs.getPropertyValue("--test-states"), "0");
  e.click();
  assert_true(e.matches(":toggle(test-states 1)"));
  assert_equals(cs.getPropertyValue("--test-states"), "1");
  e.click();
  assert_true(e.matches(":toggle(test-states 0)"));
  assert_equals(cs.getPropertyValue("--test-states"), "0");
  e.style.toggleRoot = "test-states 2 at 2";
  await wait_for_toggle_creation(e);
  assert_true(e.matches(":toggle(test-states 0)"));
  assert_equals(cs.getPropertyValue("--test-states"), "0");
  e.click();
  e.click();
  assert_true(e.matches(":toggle(test-states 2)"));
  assert_equals(cs.getPropertyValue("--test-states"), "2");
  e.style.toggleRoot = "";
  await wait_for_toggle_creation(e);
  assert_true(e.matches(":toggle(test-states 2)"));
  assert_equals(cs.getPropertyValue("--test-states"), "2");
  e.click();
  assert_true(e.matches(":toggle(test-states 0)"));
  assert_equals(cs.getPropertyValue("--test-states"), "0");
  e.click();
  assert_true(e.matches(":toggle(test-states 1)"));
  assert_equals(cs.getPropertyValue("--test-states"), "1");
  e.click();
  assert_true(e.matches(":toggle(test-states 0)"));
  assert_equals(cs.getPropertyValue("--test-states"), "0");
}, "states used from toggle or toggle specifier as appropriate (integer)");

promise_test(async function() {
  let e = await set_up_single_toggle_in(container, "test-states [one two] at 0");
  let cs = getComputedStyle(e);
  let t = e.toggles.get("test-states");
  assert_equals(t.value, 0);
  assert_true(e.matches(":toggle(test-states 0)"));
  assert_true(e.matches(":toggle(test-states one)"));
  assert_equals(cs.getPropertyValue("--test-states"), "0");
  e.click();
  assert_equals(t.value, "two");
  assert_true(e.matches(":toggle(test-states 1)"));
  assert_true(e.matches(":toggle(test-states two)"));
  assert_equals(cs.getPropertyValue("--test-states"), "1");
  e.click();
  assert_equals(t.value, "one");
  assert_true(e.matches(":toggle(test-states 0)"));
  assert_true(e.matches(":toggle(test-states one)"));
  assert_equals(cs.getPropertyValue("--test-states"), "0");
  e.style.toggleRoot = "test-states [zero one two] at 2";
  await wait_for_toggle_creation(e);
  assert_equals(t.value, "one");
  assert_true(e.matches(":toggle(test-states 0)"));
  assert_true(e.matches(":toggle(test-states one)"));
  assert_equals(cs.getPropertyValue("--test-states"), "0");
  e.click();
  assert_equals(t.value, "two");
  assert_true(e.matches(":toggle(test-states 1)"));
  assert_true(e.matches(":toggle(test-states two)"));
  assert_equals(cs.getPropertyValue("--test-states"), "1");
  e.click();
  assert_equals(t.value, "zero");
  assert_true(e.matches(":toggle(test-states zero)"));
  assert_equals(cs.getPropertyValue("--test-states"), "");
  e.style.toggleRoot = "";
  await wait_for_toggle_creation(e);
  assert_equals(t.value, "zero");
  assert_true(e.matches(":toggle(test-states zero)"));
  assert_equals(cs.getPropertyValue("--test-states"), "");
  e.click();
  assert_equals(t.value, "one");
  assert_true(e.matches(":toggle(test-states 0)"));
  assert_true(e.matches(":toggle(test-states one)"));
  assert_equals(cs.getPropertyValue("--test-states"), "0");
  e.click();
  assert_equals(t.value, "two");
  assert_true(e.matches(":toggle(test-states 1)"));
  assert_true(e.matches(":toggle(test-states two)"));
  assert_equals(cs.getPropertyValue("--test-states"), "1");
  e.click();
  assert_equals(t.value, "one");
  assert_true(e.matches(":toggle(test-states 0)"));
  assert_true(e.matches(":toggle(test-states one)"));
  assert_equals(cs.getPropertyValue("--test-states"), "0");
}, "states used from toggle or toggle specifier as appropriate (names)");

promise_test(async function() {
  container.innerHTML = `
    <div style="toggle-group: test-group">
      <div id="t" style="toggle: test-group 1 at 0 group"></div>
      <div id="other" style="toggle: test-group 1 at 0 group"></div>
    </div>
  `;
  let t = document.getElementById("t");
  let other = document.getElementById("other");
  let t_cs = getComputedStyle(t);
  let other_cs = getComputedStyle(other);
  await wait_for_toggle_creation(t);
  await wait_for_toggle_creation(other);
  other.click();
  assert_true(t.matches(":toggle(test-group 0)"));
  assert_equals(t_cs.getPropertyValue("--test-group"), "0");
  assert_true(other.matches(":toggle(test-group 1)"));
  assert_equals(other_cs.getPropertyValue("--test-group"), "1");
  t.click();
  assert_true(t.matches(":toggle(test-group 1)"));
  assert_equals(t_cs.getPropertyValue("--test-group"), "1");
  assert_true(other.matches(":toggle(test-group 0)"));
  assert_equals(other_cs.getPropertyValue("--test-group"), "0");
  other.click();
  assert_true(t.matches(":toggle(test-group 0)"));
  assert_equals(t_cs.getPropertyValue("--test-group"), "0");
  assert_true(other.matches(":toggle(test-group 1)"));
  assert_equals(other_cs.getPropertyValue("--test-group"), "1");

  // Test that we use the group value from the toggle specifier when it's
  // different from the toggle, but only when that's the toggle we're
  // changing.
  t.style.toggleRoot = "test-group 1 at 0";
  t.click();
  assert_true(t.matches(":toggle(test-group 1)"));
  assert_equals(t_cs.getPropertyValue("--test-group"), "1");
  assert_true(other.matches(":toggle(test-group 1)"));
  assert_equals(other_cs.getPropertyValue("--test-group"), "1");
  other.click();
  assert_true(t.matches(":toggle(test-group 1)"));
  assert_equals(t_cs.getPropertyValue("--test-group"), "1");
  assert_true(other.matches(":toggle(test-group 0)"));
  assert_equals(other_cs.getPropertyValue("--test-group"), "0");
  other.click();
  assert_true(t.matches(":toggle(test-group 0)"));
  assert_equals(t_cs.getPropertyValue("--test-group"), "0");
  assert_true(other.matches(":toggle(test-group 1)"));
  assert_equals(other_cs.getPropertyValue("--test-group"), "1");

  // Test that we use the group value from the toggle when there is no toggle
  // specifier.
  t.style.toggleRoot = "";
  t.click();
  assert_true(t.matches(":toggle(test-group 1)"));
  assert_equals(t_cs.getPropertyValue("--test-group"), "1");
  assert_true(other.matches(":toggle(test-group 0)"));
  assert_equals(other_cs.getPropertyValue("--test-group"), "0");
  other.click();
  assert_true(t.matches(":toggle(test-group 0)"));
  assert_equals(t_cs.getPropertyValue("--test-group"), "0");
  assert_true(other.matches(":toggle(test-group 1)"));
  assert_equals(other_cs.getPropertyValue("--test-group"), "1");
  other.click();
  assert_true(t.matches(":toggle(test-group 0)"));
  assert_equals(t_cs.getPropertyValue("--test-group"), "0");
  assert_true(other.matches(":toggle(test-group 0)"));
  assert_equals(other_cs.getPropertyValue("--test-group"), "0");
}, "group used from toggle or toggle specifier as appropriate");

promise_test(async function() {
  let e = await set_up_single_toggle_in(container, "test-overflow sticky");
  let cs = getComputedStyle(e);
  assert_true(e.matches(":toggle(test-overflow 0)"));
  assert_equals(cs.getPropertyValue("--test-overflow"), "0");
  e.click();
  assert_true(e.matches(":toggle(test-overflow 1)"));
  assert_equals(cs.getPropertyValue("--test-overflow"), "1");
  e.click();
  assert_true(e.matches(":toggle(test-overflow 1)"));
  assert_equals(cs.getPropertyValue("--test-overflow"), "1");
  e.style.toggleRoot = "test-overflow";
  await wait_for_toggle_creation(e);
  assert_true(e.matches(":toggle(test-overflow 1)"));
  assert_equals(cs.getPropertyValue("--test-overflow"), "1");
  e.click();
  assert_true(e.matches(":toggle(test-overflow 0)"));
  assert_equals(cs.getPropertyValue("--test-overflow"), "0");
  e.style.toggleRoot = "";
  await wait_for_toggle_creation(e);
  assert_true(e.matches(":toggle(test-overflow 0)"));
  assert_equals(cs.getPropertyValue("--test-overflow"), "0");
  e.click();
  assert_true(e.matches(":toggle(test-overflow 1)"));
  assert_equals(cs.getPropertyValue("--test-overflow"), "1");
  e.click();
  assert_true(e.matches(":toggle(test-overflow 1)"));
  assert_equals(cs.getPropertyValue("--test-overflow"), "1");
}, "overflow used from toggle or toggle specifier as appropriate");

promise_test(async function() {
  container.innerHTML = `<div id="t" style="toggle-root: set-num 5 at 1"></div>
                         <div id="a" style="toggle-trigger: set-num set 2"></div>
                         <div id="b" style="toggle-trigger: set-num set 0"></div>
                         <div id="c" style="toggle-trigger: set-num set named-state"></div>
                         <div id="m"></div>
                        `;
  let t = document.getElementById("t");
  let a = document.getElementById("a");
  let b = document.getElementById("b");
  let c = document.getElementById("c");
  let m = document.getElementById("m");
  let cs = getComputedStyle(m);
  await wait_for_toggle_creation(t);
  assert_true(m.matches(':toggle(set-num 1)'), "initial state");
  assert_equals(cs.getPropertyValue("--set-num"), "1");
  a.click();
  assert_true(m.matches(':toggle(set-num 2)'), "state after clicking a");
  assert_equals(cs.getPropertyValue("--set-num"), "2");
  b.click();
  assert_true(m.matches(':toggle(set-num 0)'), "state after clicking b");
  assert_equals(cs.getPropertyValue("--set-num"), "0");
  c.click();
  assert_true(m.matches(':toggle(set-num named-state)'), "state after clicking c");
  assert_equals(cs.getPropertyValue("--set-num"), "");
}, "changing with toggle-trigger: set (numbers)");

promise_test(async function() {
  container.innerHTML = `<div id="t" style="toggle-root: set-names [zero one two three four five] at two"></div>
                         <div id="a" style="toggle-trigger: set-names set one"></div>
                         <div id="b" style="toggle-trigger: set-names set 3"></div>
                         <div id="c" style="toggle-trigger: set-names set zero"></div>
                         <div id="d" style="toggle-trigger: set-names set named-state"></div>
                         <div id="m"></div>
                        `;
  let t = document.getElementById("t");
  let a = document.getElementById("a");
  let b = document.getElementById("b");
  let c = document.getElementById("c");
  let d = document.getElementById("d");
  let m = document.getElementById("m");
  let cs = getComputedStyle(m);
  await wait_for_toggle_creation(t);
  assert_true(m.matches(':toggle(set-names two)'));
  assert_true(m.matches(':toggle(set-names 2)'));
  assert_equals(cs.getPropertyValue("--set-names"), "2");
  a.click();
  assert_true(m.matches(':toggle(set-names one)'));
  assert_true(m.matches(':toggle(set-names 1)'));
  assert_equals(cs.getPropertyValue("--set-names"), "1");
  b.click();
  assert_true(m.matches(':toggle(set-names three)'));
  assert_true(m.matches(':toggle(set-names 3)'));
  assert_equals(cs.getPropertyValue("--set-names"), "3");
  c.click();
  assert_true(m.matches(':toggle(set-names zero)'));
  assert_true(m.matches(':toggle(set-names 0)'));
  assert_equals(cs.getPropertyValue("--set-names"), "0");
  d.click();
  assert_true(m.matches(':toggle(set-names named-state)'));
  assert_false(m.matches(':toggle(set-names 0)'));
  assert_equals(cs.getPropertyValue("--set-names"), "");
  b.click();
  assert_true(m.matches(':toggle(set-names 3)'));
  assert_equals(cs.getPropertyValue("--set-names"), "3");
  d.click();
  assert_false(m.matches(':toggle(set-names 3)'));
  assert_equals(cs.getPropertyValue("--set-names"), "");
}, "changing with toggle-trigger: set (named states)");

function test_action_and_cycle(states_and_cycle, start, action, result) {
  promise_test(async function() {
    container.innerHTML = `
      <div id="toggle" style="toggle-root: test ${states_and_cycle}"></div>
      <div id="start" style="toggle-trigger: test set ${start}"></div>
      <div id="action" style="toggle-trigger: test ${action}"></div>
    `;
    let toggle = document.getElementById("toggle");
    await wait_for_toggle_creation(toggle);
    document.getElementById("start").click();
    assert_true(toggle.matches(`:toggle(test ${start})`), "value after set");
    document.getElementById("action").click();
    assert_true(toggle.matches(`:toggle(test ${result})`), "value after action");
  }, `toggle with "${states_and_cycle}" changing from "${start}" with action "${action}"`);
}

function test_action_and_all_cycles(states, start, action, result_cycle, result_cycle_on, result_sticky) {
  test_action_and_cycle(states, start, action, result_cycle);
  test_action_and_cycle(`${states} cycle`, start, action, result_cycle);
  test_action_and_cycle(`${states} cycle-on`, start, action, result_cycle_on);
  test_action_and_cycle(`${states} sticky`, start, action, result_sticky);
}

test_action_and_all_cycles("2", "0", "next", "1", "1", "1");
test_action_and_all_cycles("2", "1", "next", "2", "2", "2");
test_action_and_all_cycles("2", "2", "next", "0", "1", "2");
test_action_and_all_cycles("3", "5", "next", "0", "1", "3");
test_action_and_all_cycles("4", "3", "next", "4", "4", "4");
test_action_and_all_cycles("4", "3", "next 3", "0", "1", "4");
test_action_and_all_cycles("3", "named-value", "next", "0", "1", "3");
test_action_and_all_cycles("[a b c d]", "a", "next", "b", "b", "b");
test_action_and_all_cycles("[a b c d]", "a", "next 5", "a", "b", "d");
test_action_and_all_cycles("[a b c d]", "c", "next", "d", "d", "d");
test_action_and_all_cycles("[a b c d]", "d", "next", "a", "b", "d");
test_action_and_all_cycles("[a b c d]", "extra-state", "next", "a", "b", "d");

test_action_and_all_cycles("2", "0", "prev", "2", "2", "0");
test_action_and_all_cycles("2", "1", "prev", "0", "2", "0");
test_action_and_all_cycles("2", "2", "prev", "1", "1", "1");
test_action_and_all_cycles("2", "5", "prev", "2", "2", "2");
test_action_and_all_cycles("3", "5", "prev 3", "2", "2", "2");
test_action_and_all_cycles("3", "2", "prev 3", "3", "3", "0");
test_action_and_all_cycles("3", "named-value", "prev", "3", "3", "3");
test_action_and_all_cycles("[a b c d]", "a", "prev", "d", "d", "a");
test_action_and_all_cycles("[a b c d]", "b", "prev", "a", "d", "a");
test_action_and_all_cycles("[a b c d]", "d", "prev", "c", "c", "c");
test_action_and_all_cycles("[a b c d]", "c", "prev 5", "d", "d", "a");
test_action_and_all_cycles("[a b c d]", "extra-state", "prev", "d", "d", "d");

// TODO(https://github.com/tabatkins/css-toggle/issues/39): This set of
// tests is testing proposed behavior; the spec currently says something
// that we agree is wrong.
promise_test(async function() {
  container.innerHTML = `
    <button id="toggle" style="toggle: test"></button>
  `;
  let toggle = document.getElementById("toggle");
  await wait_for_toggle_creation(toggle);
  assert_true(!toggle.matches(`:toggle(test)`), "value before click");
  toggle.click();
  assert_true(toggle.matches(`:toggle(test)`), "value after click");
}, "toggle activation on button with toggle-trigger (1)");
promise_test(async function() {
  container.innerHTML = `
    <div id="toggle" style="toggle-root: test">
      <button id="trigger" style="toggle-trigger: test"></button>
    </div>
  `;
  let toggle = document.getElementById("toggle");
  await wait_for_toggle_creation(toggle);
  assert_true(!toggle.matches(`:toggle(test)`), "value before click");
  document.getElementById("trigger").click();
  assert_true(toggle.matches(`:toggle(test)`), "value after click");
}, "toggle activation on button with toggle-trigger (2)");
promise_test(async function() {
  container.innerHTML = `
    <div id="toggle" style="toggle: test">
      <button id="button"></button>
    </div>
  `;
  let toggle = document.getElementById("toggle");
  await wait_for_toggle_creation(toggle);
  assert_true(!toggle.matches(`:toggle(test)`), "value before click");
  document.getElementById("button").click();
  assert_true(!toggle.matches(`:toggle(test)`), "value after button click");
  toggle.click();
  assert_true(toggle.matches(`:toggle(test)`), "value after div click");
}, "toggle activation on button inside element with toggle-trigger");
promise_test(async function() {
  container.innerHTML = `
    <div id="toggle" style="toggle: test">
      <div id="div"></div>
    </div>
  `;
  let toggle = document.getElementById("toggle");
  await wait_for_toggle_creation(toggle);
  assert_true(!toggle.matches(`:toggle(test)`), "value before click");
  document.getElementById("div").click();
  assert_true(toggle.matches(`:toggle(test)`), "value after inner div click");
  toggle.click();
  assert_true(!toggle.matches(`:toggle(test)`), "value after outer div click");
}, "toggle activation on div inside element with toggle-trigger");

</script>
