#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../test_helper'

class SuperselectorTest < MiniTest::Test
  def test_superselector_reflexivity
    assert_superselector 'h1', 'h1'
    assert_superselector '.foo', '.foo'
    assert_superselector '#foo > .bar, baz', '#foo > .bar, baz'
  end

  def test_smaller_compound_superselector
    assert_strict_superselector '.foo', '.foo.bar'
    assert_strict_superselector '.bar', '.foo.bar'
    assert_strict_superselector 'a', 'a#b'
    assert_strict_superselector '#b', 'a#b'
  end

  def test_smaller_complex_superselector
    assert_strict_superselector '.bar', '.foo .bar'
    assert_strict_superselector '.bar', '.foo > .bar'
    assert_strict_superselector '.bar', '.foo + .bar'
    assert_strict_superselector '.bar', '.foo ~ .bar'
  end

  def test_selector_list_subset_superselector
    assert_strict_superselector '.foo, .bar', '.foo'
    assert_strict_superselector '.foo, .bar, .baz', '.foo, .baz'
    assert_strict_superselector '.foo, .baz, .qux', '.foo.bar, .baz.bang'
  end

  def test_leading_combinator_superselector
    refute_superselector '+ .foo', '.foo'
    refute_superselector '+ .foo', '.bar + .foo'
  end

  def test_trailing_combinator_superselector
    refute_superselector '.foo +', '.foo'
    refute_superselector '.foo +', '.foo + .bar'
  end

  def test_matching_combinator_superselector
    assert_strict_superselector '.foo + .bar', '.foo + .bar.baz'
    assert_strict_superselector '.foo + .bar', '.foo.baz + .bar'
    assert_strict_superselector '.foo > .bar', '.foo > .bar.baz'
    assert_strict_superselector '.foo > .bar', '.foo.baz > .bar'
    assert_strict_superselector '.foo ~ .bar', '.foo ~ .bar.baz'
    assert_strict_superselector '.foo ~ .bar', '.foo.baz ~ .bar'
  end

  def test_following_sibling_is_superselector_of_next_sibling
    assert_strict_superselector '.foo ~ .bar', '.foo + .bar.baz'
    assert_strict_superselector '.foo ~ .bar', '.foo.baz + .bar'
  end

  def test_descendant_is_superselector_of_child
    assert_strict_superselector '.foo .bar', '.foo > .bar.baz'
    assert_strict_superselector '.foo .bar', '.foo.baz > .bar'
    assert_strict_superselector '.foo .baz', '.foo > .bar > .baz'
  end

  def test_child_isnt_superselector_of_longer_child
    refute_superselector '.foo > .baz', '.foo > .bar > .baz'
    refute_superselector '.foo > .baz', '.foo > .bar .baz'
  end

  def test_following_sibling_isnt_superselector_of_longer_following_sibling
    refute_superselector '.foo + .baz', '.foo + .bar + .baz'
    refute_superselector '.foo + .baz', '.foo + .bar .baz'
  end

  def test_sibling_isnt_superselector_of_longer_sibling
    # This actually is a superselector, but it's a very narrow edge case and
    # detecting it is very difficult and may be exponential in the worst case.
    refute_superselector '.foo ~ .baz', '.foo ~ .bar ~ .baz'

    refute_superselector '.foo ~ .baz', '.foo ~ .bar .baz'
  end

  def test_matches_is_superselector_of_constituent_selectors
    %w[matches -moz-any].each do |name|
      assert_strict_superselector ":#{name}(.foo, .bar)", '.foo.baz'
      assert_strict_superselector ":#{name}(.foo, .bar)", '.bar.baz'
      assert_strict_superselector ":#{name}(.foo .bar, .baz)", '.x .foo .bar'
    end
  end

  def test_matches_is_superselector_of_subset_matches
    assert_strict_superselector ':matches(.foo, .bar, .baz)', '#x:matches(.foo.bip, .baz.bang)'
    assert_strict_superselector ':-moz-any(.foo, .bar, .baz)', '#x:-moz-any(.foo.bip, .baz.bang)'
  end

  def test_matches_is_not_superselector_of_any
    refute_superselector ':matches(.foo, .bar)', ':-moz-any(.foo, .bar)'
    refute_superselector ':-moz-any(.foo, .bar)', ':matches(.foo, .bar)'
  end

  def test_matches_can_be_subselector
    %w[matches -moz-any].each do |name|
      assert_superselector '.foo', ":#{name}(.foo.bar)"
      assert_superselector '.foo.bar', ":#{name}(.foo.bar.baz)"
      assert_superselector '.foo', ":#{name}(.foo.bar, .foo.baz)"
    end
  end

  def test_any_is_not_superselector_of_different_prefix
    refute_superselector ':-moz-any(.foo, .bar)', ':-s-any(.foo, .bar)'
  end

  def test_not_is_superselector_of_less_complex_not
    assert_strict_superselector ':not(.foo.bar)', ':not(.foo)'
    assert_strict_superselector ':not(.foo .bar)', ':not(.bar)'
  end

  def test_not_is_superselector_of_superset
    assert_strict_superselector ':not(.foo.bip, .baz.bang)', ':not(.foo, .bar, .baz)'
    assert_strict_superselector ':not(.foo.bip, .baz.bang)', ':not(.foo):not(.bar):not(.baz)'
  end

  def test_not_is_superselector_of_unique_selectors
    assert_strict_superselector ':not(h1.foo)', 'a'
    assert_strict_superselector ':not(.baz #foo)', '#bar'
  end

  def test_not_is_not_superselector_of_non_unique_selectors
    refute_superselector ':not(.foo)', '.bar'
    refute_superselector ':not(:hover)', ':visited'
  end

  def test_current_is_superselector_with_identical_innards
    assert_superselector ':current(.foo)', ':current(.foo)'
  end

  def test_current_is_superselector_with_subselector_innards
    refute_superselector ':current(.foo)', ':current(.foo.bar)'
    refute_superselector ':current(.foo.bar)', ':current(.foo)'
  end

  def test_nth_match_is_superselector_of_subset_nth_match
    assert_strict_superselector(
      ':nth-child(2n of .foo, .bar, .baz)', '#x:nth-child(2n of .foo.bip, .baz.bang)')
    assert_strict_superselector(
      ':nth-last-child(2n of .foo, .bar, .baz)', '#x:nth-last-child(2n of .foo.bip, .baz.bang)')
  end

  def test_nth_match_is_not_superselector_of_nth_match_with_different_arg
    refute_superselector(
      ':nth-child(2n of .foo, .bar, .baz)', '#x:nth-child(2n + 1 of .foo.bip, .baz.bang)')
    refute_superselector(
      ':nth-last-child(2n of .foo, .bar, .baz)', '#x:nth-last-child(2n + 1 of .foo.bip, .baz.bang)')
  end

  def test_nth_match_is_not_superselector_of_nth_last_match
    refute_superselector ':nth-child(2n of .foo, .bar)', ':nth-last-child(2n of .foo, .bar)'
    refute_superselector ':nth-last-child(2n of .foo, .bar)', ':nth-child(2n of .foo, .bar)'
  end

  def test_nth_match_can_be_subselector
    %w[nth-child nth-last-child].each do |name|
      assert_superselector '.foo', ":#{name}(2n of .foo.bar)"
      assert_superselector '.foo.bar', ":#{name}(2n of .foo.bar.baz)"
      assert_superselector '.foo', ":#{name}(2n of .foo.bar, .foo.baz)"
    end
  end

  def has_is_superselector_of_subset_host
    assert_strict_superselector ':has(.foo, .bar, .baz)', ':has(.foo.bip, .baz.bang)'
  end

  def has_isnt_superselector_of_contained_selector
    assert_strict_superselector ':has(.foo, .bar, .baz)', '.foo'
  end

  def host_is_superselector_of_subset_host
    assert_strict_superselector ':host(.foo, .bar, .baz)', ':host(.foo.bip, .baz.bang)'
  end

  def host_isnt_superselector_of_contained_selector
    assert_strict_superselector ':host(.foo, .bar, .baz)', '.foo'
  end

  def host_context_is_superselector_of_subset_host
    assert_strict_superselector(
      ':host-context(.foo, .bar, .baz)', ':host-context(.foo.bip, .baz.bang)')
  end

  def host_context_isnt_superselector_of_contained_selector
    assert_strict_superselector ':host-context(.foo, .bar, .baz)', '.foo'
  end

  private

  def assert_superselector(superselector, subselector)
    assert(parse_selector(superselector).superselector?(parse_selector(subselector)),
      "Expected #{superselector} to be a superselector of #{subselector}.")
  end

  def refute_superselector(superselector, subselector)
    assert(!parse_selector(superselector).superselector?(parse_selector(subselector)),
      "Expected #{superselector} not to be a superselector of #{subselector}.")
  end

  def assert_strict_superselector(superselector, subselector)
    assert_superselector(superselector, subselector)
    refute_superselector(subselector, superselector)
  end

  def parse_selector(selector)
    Sass::SCSS::CssParser.new(selector, filename_for_test, nil).parse_selector
  end
end
