# encoding: UTF-8

require File.expand_path('../../test_helper', __FILE__)


class VisibilityTest < MiniTest::Spec


  def setup
    @site = setup_site
  end

  def teardown
    teardown_site
  end

  context "Content" do
    setup do
      Content.delete
      class ::R < Page; end
      R.box :pages
      class ::P < Page; end
      P.box :things
      class ::E < Piece; end
      E.box :pages

      @root = R.new(:uid => 'root')
      2.times do |i|
        c = P.new(:uid => i, :slug => "#{i}")
        @root.pages << c
        4.times do |j|
          d = E.new(:uid => "#{i}.#{j}")
          c.things << d
          2.times do |k|
            e = P.new(:uid => "#{i}.#{j}.#{k}", :slug => "#{i}-#{j}-#{k}")
            d.pages << e
            2.times do |l|
              e.things << E.new(:uid => "#{i}.#{j}.#{k}.#{l}")
            end
            e.save
          end
        end
      end
      @root.save
      @root.reload
      @child = Page.uid("0")
    end

    teardown do
      [:R, :P, :E].each do |k|
        Object.send(:remove_const, k)
      end
      Spontaneous.database.logger = nil
      Content.delete
    end

    should "be visible by default" do
      @child.visible?.should be_true
    end

    should "be hidable using #hide!" do
      @child.hide!
      @child.visible?.should be_false
      @child.hidden?.should be_true
    end

    should "be hidable using #visible=" do
      @child.visible = false
      @child.visible?.should be_false
      @child.hidden?.should be_true
    end

    should "hide child pages" do
      @child.page?.should be_true
      @child.hide!
      @child.children.each do |child1|
        child1.visible?.should be_false
        child1.hidden_origin.should == @child.id
        child1.children.each do |child2|
          child2.visible?.should be_false
          child2.hidden_origin.should == @child.id
        end
      end
    end

    should "hide page content" do
      @child.hide!
      @child.reload
      Piece.all.select { |f| f.visible? }.length.should == 20
      Piece.all.select do |f|
        f.page.ancestors.include?(@child) || f.page == @child
      end.each do |f|
        f.visible?.should be_false
        f.hidden_origin.should == @child.id
      end
      Piece.all.select do | f |
        !f.page.ancestors.include?(@child) && f.page != @child
      end.each do |f|
        f.visible?.should be_true
        f.hidden_origin.should be_nil
      end
    end

    should "re-show all page content" do
      @child.hide!
      @child.show!
      @child.reload
      Content.all.each do |c|
        c.visible?.should be_true
        c.hidden_origin.should be_nil
      end
    end

    should "hide all descendents of page content" do
      piece = Content.first(:uid => "0.0")
      f = E.new(:uid => "0.0.X")
      piece.pages << f
      piece.save
      piece.reload
      piece.hide!

      Content.all.each do |c|
        if c.uid =~ /^0\.0/
          c.hidden?.should be_true
          if c.uid == "0.0"
            c.visible?.should be_false
            c.hidden_origin.should be_nil
          else
            c.visible?.should be_false
            c.hidden_origin.should == piece.id
          end
        else
          c.hidden?.should be_false
          c.hidden_origin.should be_nil
        end
      end

    end

    should "re-show all descendents of page content" do
      piece = Content.first(:uid => "0.0")
      piece.hide!
      piece.show!
      Content.all.each do |c|
        c.visible?.should be_true
        c.hidden_origin.should be_nil
      end
    end

    should "know if something is hidden because its ancestor is hidden" do
      piece = Content.first(:uid => "0.0")
      piece.hide!
      piece.showable?.should be_true
      child = Content.first(:uid => "0.0.0.0")
      child.visible?.should be_false
      child.showable?.should be_false
    end

    # showing something that is hidden because its ancestor is hidden shouldn't be possible
    should "stop hidden child content from being hidden" do
      piece = Content.first(:uid => "0.0")
      piece.hide!
      child = Content.first(:uid => "0.0.0.0")
      child.visible?.should be_false
      lambda { child.show! }.must_raise(Spontaneous::NotShowable)
    end

    should "not reveal child content that was hidden before its parent" do
      piece1 = Content.first(:uid => "0.0.0.0")
      piece2 = Content.first(:uid => "0.0.0.1")
      parent = Content.first(:uid => "0.0.0")
      piece1.owner.should == parent
      piece2.owner.should == parent
      piece1.container.should == parent.things
      piece2.container.should == parent.things
      piece1.hide!
      parent.hide!
      parent.reload.visible?.should be_false
      piece1.reload.visible?.should be_false
      piece2.reload.visible?.should be_false
      piece1.hidden_origin.should be_blank
      piece2.hidden_origin.should == parent.id
      parent.show!
      parent.reload.visible?.should be_true
      piece2.reload.visible?.should be_true
      piece1.reload.visible?.should be_false
    end


    should "add child content with a visibility inherited from their parent" do
      page = P.first
      page.hide!
      page.reload
      piece = E.new
      page.things << piece
      page.save
      piece.reload
      piece.hidden?.should be_true
      page.show!
      piece.reload.hidden?.should be_false
    end

    context "root" do
      should "should not be hidable" do
        @root.is_root?.should be_true
        lambda { @root.visible = false }.must_raise(RuntimeError)
        lambda { @root.hide! }.must_raise(RuntimeError)
        @root.visible?.should be_true
      end
    end

    context "top-level searches" do
      should "return a list of visible pages" do
        @root.pages.first.hide!
        P.visible.count.should == 9
      end
    end
    context "visibility scoping" do
      setup do
        # S.database.logger = ::Logger.new($stdout)
      end

      teardown do
        # S.database.logger = nil
      end

      should "prevent inclusion of hidden content" do
        @uid = '0'
        @page = Page.uid(@uid)
        @page.hide!
        @page.reload
        # Spontaneous.database.logger = ::Logger.new($stdout)
        Page.path("/0").should == @page
        Content.with_visible do
          Content.visible_only?.should be_true
          Page.uid(@uid).should be_blank
          Page.path("/0").should be_blank
          Page.uid('0.0.0').should be_blank
        end
      end

      should "only show visible pieces" do
        page = Content.first(:uid => "1")
        page.contents.length.should == 4
        page.things.contents.length.should == 4
        page.things.count.should == 4
        page.contents.first.hide!
        page.reload
        Content.with_visible do
          page.contents.length.should == 3
          page.contents.first.id.should_not == page.id
          page.contents.first.first?.should be_true
        end
      end

      should "stop modification of pieces" do
        page = Content.first(:uid => "1")
        Content.with_visible do
          # would like to make sure we're raising a predictable error
          # but 1.9 changes the typeerror to a runtime error
          lambda { page.things << Piece.new }.must_raise(TypeError, RuntimeError)
        end
      end

      should "ensure that no hidden content can be returned" do
        @root.reload
        @root.children.first.children.length.should == 8
        @root.children.first.contents.first.hide!
        @root.reload
        Content.with_visible do
          @root.children.first.children.length.should == 6
        end
      end

      should "hide pages without error" do
        @root.pages.first.hide!
        @root.reload
        Content.with_visible do
          pieces = @root.pages.contents.map { |p| p }
        end
      end
    end

    context "aliases" do
      setup do
        class ::MyAlias < Piece; end
        MyAlias.alias_of ::E
      end

      teardown do
        Object.send(:remove_const, :MyAlias)
      end

      should "be initalized as invisible if their target is invisible" do
        target = E.find(:uid => "1.1")
        target.hide!
        al = MyAlias.create(:target => target)
        al.visible?.should be_false
      end


      should "be made visible along with their target if added when target is hidden" do
        target = E.find(:uid => "1.1")
        target.hide!
        al = MyAlias.create(:target => target)
        al.reload.visible?.should be_false
        target.show!
        al.reload.visible?.should be_true
      end

      should "be filtered by visibility when doing reverse lookup" do
        page = P.first(:uid => "1")
        target = E.find(:uid => "1.1")
        al1 = MyAlias.create(:target => target)
        page.things << al1
        al2 = MyAlias.create(:target => target).reload
        page.things << al2
        al1.hide!
        al1.reload
        al2.reload
        Set.new(target.aliases).should == Set.new([al1, al2])
        target.reload
        Content.with_visible do
          target.aliases.should == [al2]
        end
      end

      should "show as 'hidden' if their target is deleted" do
        parent = E.find(:uid => "1.1")
        target = P.new
        parent.pages << target
        parent.save
        al1 = MyAlias.create(:target => target)
        P.filter(:id => target.id).delete
        al1.reload.visible?.should be_false
      end
    end
  end
end