# frozen_string_literal: true

RSpec.describe Hanami::View::PartBuilder do
  subject(:part_builder) { rendering.part_builder }

  let(:rendering) { view.rendering(format: :html) }
  let(:view) {
    part_namespace = namespace
    Class.new(Hanami::View) {
      config.paths = FIXTURES_PATH
      config.template = "_"
      config.part_namespace = part_namespace
    }.new
  }
  let(:namespace) { nil }

  describe "#call" do
    subject(:part) {
      part_builder.(name, value, rendering: rendering, **options)
    }

    let(:name) { :user }
    let(:value) { double(:user) }
    let(:options) { {} }

    shared_examples "a view part" do
      let(:part_class) { Hanami::View::Part }

      it "returns a part" do
        expect(part).to be_a part_class
      end

      it "wraps the value" do
        expect(part._value).to eq value
      end

      it "retains the rendering" do
        expect(part._rendering).to eql rendering
      end
    end

    shared_examples "a view part collection" do
      let(:collection_part_class) { Hanami::View::Part }
      let(:item_part_class) { Hanami::View::Part }

      it "returns a part wrapping the collection" do
        expect(part).to be_a collection_part_class
      end

      it "wraps the collection and its items" do
        expect(part._value.map(&:_value)).to eq value
      end

      it "returns the collection's items as parts" do
        part.to_a.each do |item|
          expect(item).to be_a item_part_class
        end
      end
    end

    context "without namespace" do
      describe "singular value" do
        let(:value) { double("user") }

        it_behaves_like "a view part"

        describe "alternative name provided via :as option" do
          let(:options) { {as: :admin_user} }

          it_behaves_like "a view part"
        end

        describe "explicit part class provided via as: option" do
          before do
            Test::UserPart = Class.new(Hanami::View::Part)
          end

          let(:options) { {as: Test::UserPart} }

          it_behaves_like "a view part" do
            let(:part_class) { Test::UserPart }
          end
        end
      end

      describe "array-like value" do
        let(:name) { :users }
        let(:value) { [double(:user), double(:user)] }

        it_behaves_like "a view part collection"

        describe "alternative name provided via :as option" do
          let(:options) { {as: :admin_user} }

          it_behaves_like "a view part collection"
        end

        describe "explicit part class provided via as: option" do
          before do
            Test::UserPart = Class.new(Hanami::View::Part)
          end

          let(:options) { {as: Test::UserPart} }

          it_behaves_like "a view part collection" do
            let(:item_part_class) { Test::UserPart }
          end
        end

        describe "explicit collection part class provided via as: option" do
          before do
            Test::UserCollectionPart = Class.new(Hanami::View::Part)
          end

          let(:options) { {as: [Test::UserCollectionPart]} }

          it_behaves_like "a view part collection" do
            let(:collection_part_class) { Test::UserCollectionPart }
          end
        end
      end
    end

    context "with namespace" do
      before do
        module Test
          module Parts
            class Users < Hanami::View::Part
            end

            class UserCollection < Hanami::View::Part
            end

            class User < Hanami::View::Part
              decorate :profile
            end

            class AdminUser < Hanami::View::Part
            end

            module UserModule
            end

            class Profile < Hanami::View::Part
            end
          end
        end
      end

      let(:namespace) { Test::Parts }

      describe "singular value" do
        let(:value) { double("user", profile: "profile") }

        it_behaves_like "a view part" do
          let(:part_class) { Test::Parts::User }
        end

        it "returns decorated attributes in part classes found from the namespace" do
          expect(part.profile).to be_a Test::Parts::Profile
        end

        describe "alternative name provided via :as option" do
          let(:options) { {as: :admin_user} }

          it_behaves_like "a view part" do
            let(:part_class) { Test::Parts::AdminUser }
          end
        end

        describe "alternative name provided via :as option, when matched constant is not a class inheriting from Hanami::View::Part" do
          let(:options) { {as: :user_module} }

          it_behaves_like "a view part" do
            let(:part_class) { Hanami::View::Part }
          end
        end

        describe "explicit part class provided via as: option" do
          let(:options) { {as: Test::Parts::AdminUser} }

          it_behaves_like "a view part" do
            let(:part_class) { Test::Parts::AdminUser }
          end
        end
      end

      describe "array-like value" do
        let(:name) { :users }
        let(:value) { [double(:user), double(:user)] }

        it_behaves_like "a view part collection" do
          let(:collection_part_class) { Test::Parts::Users }
          let(:item_part_class) { Test::Parts::User }
        end

        describe "alternative element name provided via :as option" do
          let(:options) { {as: :admin_user} }

          it_behaves_like "a view part collection" do
            let(:collection_part_class) { Test::Parts::Users }
            let(:item_part_class) { Test::Parts::AdminUser }
          end
        end

        describe "alternative collection name provided via :as option" do
          let(:options) { {as: [:user_collection]} }

          it_behaves_like "a view part collection" do
            let(:collection_part_class) { Test::Parts::UserCollection }
            let(:item_part_class) { Test::Parts::User }
          end
        end

        describe "alternative collection and element names provided via :as option" do
          let(:options) { {as: [:user_collection, :admin_user]} }

          it_behaves_like "a view part collection" do
            let(:collection_part_class) { Test::Parts::UserCollection }
            let(:item_part_class) { Test::Parts::AdminUser }
          end
        end

        describe "explicit part class provided via as: option" do
          let(:options) { {as: Test::Parts::AdminUser} }

          it_behaves_like "a view part collection" do
            let(:collection_part_class) { Test::Parts::Users }
            let(:item_part_class) { Test::Parts::AdminUser }
          end
        end

        describe "explicit collection part class provided via as: option" do
          let(:options) { {as: [Test::Parts::UserCollection]} }

          it_behaves_like "a view part collection" do
            let(:collection_part_class) { Test::Parts::UserCollection }
            let(:item_part_class) { Test::Parts::User }
          end
        end

        describe "explicit collection and element part classes provided via :as option" do
          let(:options) { {as: [Test::Parts::UserCollection, Test::Parts::AdminUser]} }

          it_behaves_like "a view part collection" do
            let(:collection_part_class) { Test::Parts::UserCollection }
            let(:item_part_class) { Test::Parts::AdminUser }
          end
        end
      end
    end
  end
end
