# frozen_string_literal: true

require "rails_helper"

describe MagicLamp::DefaultsManager do
  subject { MagicLamp::DefaultsManager.new(MagicLamp::Configuration.new, {}) }

  context "attr_accessor" do
    it { is_expected.to attr_accessorize(:configuration) }
    it { is_expected.to attr_accessorize(:parent) }
    it { is_expected.to attr_accessorize(:defaults) }
  end

  context "aliases" do
    MagicLamp::REGISTER_FIXTURE_ALIASES.each do |method_name|
      it { is_expected.to alias_the_method(:register_fixture).to(method_name) }
    end
  end

  describe "#initialize" do
    let(:configuration) { double }
    let(:defaults) { double }
    let(:parent) { double }

    it "sets configuration to the given argument" do
      subject = MagicLamp::DefaultsManager.new(configuration, {})
      expect(subject.configuration).to eq(configuration)
    end

    it "sets defaults" do
      subject = MagicLamp::DefaultsManager.new(configuration, defaults)
      expect(subject.defaults).to eq(defaults)
    end

    it "defaults parent to nil" do
      expect(subject.parent).to be_nil
    end

    it "sets parent when given" do
      subject = MagicLamp::DefaultsManager.new(configuration, {}, parent)
      expect(subject.parent).to eq(parent)
    end
  end

  describe "#branch" do
    let(:configuration) { MagicLamp::Configuration.new }
    let(:great_grandparent) { MagicLamp::DefaultsManager.new(configuration, {}) }
    let(:grandparent) { MagicLamp::DefaultsManager.new(configuration, {}, great_grandparent) }
    let(:parent) { MagicLamp::DefaultsManager.new(configuration, {}, grandparent) }
    subject { MagicLamp::DefaultsManager.new(configuration, {}, parent) }
    let(:actual) { subject.branch }
    let(:branch) { [great_grandparent, grandparent, parent, subject] }

    it "returns all of the manager's parents and itself in hierarchical order" do
      expect(actual).to match_array(branch)
      expect(actual).to eq(branch)
    end
  end

  describe "#all_defaults" do
    let(:global_defaults) { { global: :defaults } }
    let(:grandparent_defaults) { { grandparent: :defaults } }
    let(:parent_defaults) { { parent: :defaults } }
    let(:subject_defaults) { { subject: :defaults } }
    let(:configuration) { MagicLamp::Configuration.new.tap { |config| config.global_defaults = global_defaults } }
    let(:grandparent) { MagicLamp::DefaultsManager.new(configuration, grandparent_defaults) }
    let(:parent) { MagicLamp::DefaultsManager.new(configuration, parent_defaults, grandparent) }
    let(:passed_in_defaults) { { passed: :in } }
    subject { MagicLamp::DefaultsManager.new(configuration, subject_defaults, parent) }
    let(:actual) { subject.all_defaults(passed_in_defaults) }
    let(:expected_defaults) { [global_defaults, grandparent_defaults, parent_defaults, subject_defaults, passed_in_defaults] }

    it "returns the global defaults, all parent defaults, the manager's defaults, and the passed in defaults" do
      expect(actual).to match_array(expected_defaults)
      expect(actual).to eq(expected_defaults)
    end
  end

  describe "#merge_with_defaults" do
    let(:extension) { Module.new }
    let(:other_extension) { Module.new }
    let(:global_defaults) { { global: :defaults, namespace: :global } }
    let(:grandparent_defaults) { { grandparent: :defaults, that: :is_ignored, namespace: "", extend: [extension] } }
    let(:parent_defaults) { { parent: :defaults, this: :is_ignored, controller: OrdersController, extend: other_extension } }
    let(:subject_defaults) { { subject: :defaults, this: :is_there, namespace: :subject } }
    let(:configuration) { MagicLamp::Configuration.new.tap { |config| config.global_defaults = global_defaults } }
    let(:grandparent) { MagicLamp::DefaultsManager.new(configuration, grandparent_defaults) }
    let(:parent) { MagicLamp::DefaultsManager.new(configuration, parent_defaults, grandparent) }
    subject { MagicLamp::DefaultsManager.new(configuration, subject_defaults, parent) }
    let(:actual) { subject.merge_with_defaults(that: :is_there, namespace: :passed, controller: OrdersController) }
    let(:expected_defaults) do
      {
        controller: OrdersController,
        global: :defaults,
        grandparent: :defaults,
        parent: :defaults,
        subject: :defaults,
        this: :is_there,
        that: :is_there,
        namespace: "global/orders/subject/passed",
        extend: [extension, other_extension]
      }
    end

    it "merges all of the branch's defaults into one hash where the children take precedence" do
      expect(actual).to eq(expected_defaults)
    end
  end

  describe "#define" do
    let(:new_defaults) { { very_new: :defaults } }

    it "creates a new defaults manager and evaluates the block in its context" do
      suspect = subject.define(new_defaults) do
        @foo = "foo"
      end
      expect(suspect).to be_a(MagicLamp::DefaultsManager)
      expect(suspect).to_not eq(subject)
      expect(suspect.instance_variable_get(:@foo)).to eq("foo")
      expect(suspect.defaults).to eq(new_defaults)
      expect(suspect.configuration).to eq(subject.configuration)
      expect(suspect.parent).to eq(subject)
    end

    it "does not require a defaults hash argument" do
      expect { subject.define {} }.to_not raise_error
    end

    it "raises an error if called without a block" do
      expect do
        subject.define(foo: :bar)
      end.to raise_error(MagicLamp::ArgumentError, "`define` requires a block")
    end
  end

  describe "#register_fixture" do
    context "calling MagicLamp.register_fixture" do
      let(:configuration) { MagicLamp::Configuration.new }
      let(:block) { proc { "foo" } }
      let(:given_options) { { this: :is_there } }
      let(:parent_defaults) { { parent: :defaults, this: :is_ignored } }
      let(:subject_defaults) { { subject: :defaults, this: :is_not_there_either } }
      let(:parent) { MagicLamp::DefaultsManager.new(configuration, parent_defaults) }
      subject { MagicLamp::DefaultsManager.new(configuration, subject_defaults, parent) }
      let(:expected_options) { { this: :is_there, subject: :defaults, parent: :defaults, extend: [] } }

      it "passes the block and the given options (merged with defaults) through" do
        expect(MagicLamp).to receive(:register_fixture) do |options, &passed_block|
          expect(options).to eq(expected_options)
          expect(passed_block).to eq(block)
        end
        subject.register_fixture(given_options, &block)
      end

      it "does not require options to be passed in" do
        expect do
          subject.register_fixture { render :foo }
        end.to_not raise_error
      end
    end
  end
end
