<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
#state-and-part::part(inner) {
  opacity: 0;
}
#state-and-part::part(inner):state(innerFoo) {
  opacity: 0.5;
}
#state-and-part:state(outerFoo)::part(inner) {
  opacity: 0.25;
}
:state( \(escaped\ state  ) {}
</style>
<body>
<script>
class TestElement extends HTMLElement {
  constructor() {
    super();
    this._internals = this.attachInternals();
  }

  get i() {
    return this._internals;
  }
}
customElements.define('test-element', TestElement);

class ContainerElement extends HTMLElement {
  constructor() {
    super();
    this._internals = this.attachInternals();
    this._shadow = this.attachShadow({mode:'open'});
    this._shadow.innerHTML = `
<style>
:host {
  border-style: solid;
}
:host(:state(dotted)) {
  border-style: dotted;
}
</style>
<test-element part="inner"></test-element>`;
  }

  get i() {
    return this._internals;
  }
  get innerElement() {
    return this._shadow.querySelector('test-element');
  }
}
customElements.define('container-element', ContainerElement);

test(() => {
  assert_throws_dom('SyntaxError', () => { document.querySelector(':state'); });
  assert_throws_dom('SyntaxError', () => { document.querySelector(':state('); });
  assert_throws_dom('SyntaxError', () => { document.querySelector(':state()'); });
  assert_throws_dom('SyntaxError', () => { document.querySelector(':state(=)'); });
  assert_throws_dom('SyntaxError', () => { document.querySelector(':state(name=value)'); });
  assert_throws_dom('SyntaxError', () => { document.querySelector(':state( foo bar)'); });
  assert_throws_dom('SyntaxError', () => { document.querySelector(':state(16px)'); });
}, ':state() parsing failures');

test(() => {
  assert_equals(document.styleSheets[0].cssRules[1].cssText,
      '#state-and-part::part(inner):state(innerFoo) { opacity: 0.5; }');
  assert_equals(document.styleSheets[0].cssRules[3].selectorText,
      ':state(\\(escaped\\ state)');
}, ':state() serialization');

test(() => {
  let element = new TestElement();
  let states = element.i.states;

  assert_false(element.matches(':state(foo)'));
  assert_true(element.matches(':not(:state(foo))'));
  states.add('foo');
  assert_true(element.matches(':state(foo)'));
  assert_true(element.matches(':is(:state(foo))'));
  element.classList.add('c1', 'c2');
  assert_true(element.matches('.c1:state(foo)'));
  assert_true(element.matches(':state(foo).c1'));
  assert_true(element.matches('.c2:state(foo).c1'));
}, ':state() in simple cases');

test(() => {
  let element = new TestElement();
  element.tabIndex = 0;
  document.body.appendChild(element);
  element.focus();
  let states = element.i.states;

  states.value = 'foo';
  assert_true(element.matches(':focus:state(foo)'));
  assert_true(element.matches(':state(foo):focus'));
}, ':state() and other pseudo classes');

test(() => {
  let outer = new ContainerElement();
  outer.id = 'state-and-part';
  document.body.appendChild(outer);
  let inner = outer.innerElement;
  let innerStates = inner.i.states;

  innerStates.add('innerFoo');
  assert_equals(getComputedStyle(inner).opacity, '0.5',
      '::part() followed by :state()');
  innerStates.replace('innerFoo', 'innerfoo');
  assert_equals(getComputedStyle(inner).opacity, '0',
      ':state() matching should be case-sensitive');
  innerStates.remove('innerfoo');

  outer.i.states.add('outerFoo');
  assert_equals(getComputedStyle(inner).opacity, '0.25',
      ':state() followed by ::part()');
}, ':state() and ::part()');

test(() => {
  let outer = new ContainerElement();
  document.body.appendChild(outer);

  assert_equals(getComputedStyle(outer).borderStyle, 'solid');
  outer.i.states.toggle('dotted');
  assert_equals(getComputedStyle(outer).borderStyle, 'dotted');
}, ':state() and :host()');
</script>
</body>
