require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")

describe "Sequel::Plugins::Dirty" do
  before do
    @db = Sequel.mock(:fetch=>{:initial=>'i'.dup, :initial_changed=>'ic'.dup}, :numrows=>1)
    @c = Class.new(Sequel::Model(@db[:c]))
    @c.plugin :dirty
    @c.columns :initial, :initial_changed, :missing, :missing_changed
  end

  dirty_plugin_specs = shared_description do
    it "initial_value should be the current value if value has not changed" do
      @o.initial_value(:initial).must_equal 'i'
      @o.initial_value(:missing).must_be_nil
    end

    it "initial_value should be the intial value if value has changed" do
      @o.initial_value(:initial_changed).must_equal 'ic'
      @o.initial_value(:missing_changed).must_be_nil
    end

    it "initial_value should handle case where initial value is reassigned later" do
      @o.initial_changed = 'ic'
      @o.initial_value(:initial_changed).must_equal 'ic'
      @o.missing_changed = nil
      @o.initial_value(:missing_changed).must_be_nil
    end

    it "changed_columns should handle case where initial value is reassigned later" do
      @o.changed_columns.must_equal [:initial_changed, :missing_changed]
      @o.initial_changed = 'ic'
      @o.changed_columns.must_equal [:missing_changed]
      @o.missing_changed = nil
      @o.changed_columns.must_equal [:missing_changed]
    end

    it "column_change should give initial and current values if there has been a change made" do
      @o.column_change(:initial_changed).must_equal ['ic', 'ic2']
      @o.column_change(:missing_changed).must_equal [nil, 'mc2']
    end

    it "column_change should be nil if no change has been made" do
      @o.column_change(:initial).must_be_nil
      @o.column_change(:missing).must_be_nil
    end

    it "column_changed? should return whether the column has changed" do
      @o.column_changed?(:initial).must_equal false
      @o.column_changed?(:initial_changed).must_equal true
      @o.column_changed?(:missing).must_equal false
      @o.column_changed?(:missing_changed).must_equal true
    end

    it "column_changed? should handle case where initial value is reassigned later" do
      @o.initial_changed = 'ic'
      @o.column_changed?(:initial_changed).must_equal false
      @o.missing_changed = nil
      @o.column_changed?(:missing_changed).must_equal false
    end

    it "changed_columns should handle case where initial value is reassigned later" do
      @o.changed_columns.must_equal [:initial_changed, :missing_changed]
      @o.initial_changed = 'ic'
      @o.changed_columns.must_equal [:missing_changed]
      @o.missing_changed = nil
      @o.changed_columns.must_equal [:missing_changed]
    end

    it "column_changes should give initial and current values" do
      @o.column_changes.must_equal(:initial_changed=>['ic', 'ic2'], :missing_changed=>[nil, 'mc2'])
    end

    it "reset_column should reset the column to its initial value" do
      @o.reset_column(:initial)
      @o.initial.must_equal 'i'
      @o.reset_column(:initial_changed)
      @o.initial_changed.must_equal 'ic'
      @o.reset_column(:missing)
      @o.missing.must_be_nil
      @o.reset_column(:missing_changed)
      @o.missing_changed.must_be_nil
    end

    it "reset_column should remove missing values from the values" do
      @o.reset_column(:missing)
      @o.values.has_key?(:missing).must_equal false
      @o.reset_column(:missing_changed)
      @o.values.has_key?(:missing_changed).must_equal false
    end
    
    it "refresh should clear the cached initial values" do
      @o.refresh
      @o.column_changes.must_equal({})
    end
    
    it "will_change_column should be used to signal in-place modification to column" do
      @o.will_change_column(:initial)
      @o.initial << 'b'
      @o.column_change(:initial).must_equal ['i', 'ib']
      @o.will_change_column(:initial_changed)
      @o.initial_changed << 'b'
      @o.column_change(:initial_changed).must_equal ['ic', 'ic2b']
      @o.will_change_column(:missing)
      @o.values[:missing] = 'b'
      @o.column_change(:missing).must_equal [nil, 'b']
      @o.will_change_column(:missing_changed)
      @o.missing_changed << 'b'
      @o.column_change(:missing_changed).must_equal [nil, 'mc2b']
    end

    it "will_change_column should different types of existing objects" do
      [nil, true, false, Class.new{undef_method :clone}.new, Class.new{def clone; raise TypeError; end}.new].each do |v|
        o = @c.new(:initial=>v)
        o.will_change_column(:initial)
        o.initial = 'a'
        o.column_change(:initial).must_equal [v, 'a']
      end
    end

    it "should work when freezing objects" do
      @o.freeze
      @o.initial_value(:initial).must_equal 'i'
      proc{@o.initial = 'b'}.must_raise
    end

    it "should have #dup duplicate structures" do
      was_new = @o.new?
      @o.update(:missing=>'m2')
      @o.dup.initial_values.must_equal @o.initial_values
      @o.dup.initial_values.wont_be_same_as(@o.initial_values)
      @o.dup.instance_variable_get(:@missing_initial_values).must_equal @o.instance_variable_get(:@missing_initial_values)
      @o.dup.instance_variable_get(:@missing_initial_values).wont_be_same_as(@o.instance_variable_get(:@missing_initial_values))
      if was_new
        @o.previous_changes.must_be_nil
        @o.dup.previous_changes.must_be_nil
      else
        @o.dup.previous_changes.must_equal @o.previous_changes
      end
      @o.dup.previous_changes.wont_be_same_as(@o.previous_changes) if @o.previous_changes
    end
  end

  describe "with new instance" do
    before do
      @o = @c.new(:initial=>'i'.dup, :initial_changed=>'ic'.dup)
      @o.initial_changed = 'ic2'.dup
      @o.missing_changed = 'mc2'.dup
    end

    include dirty_plugin_specs

    it "save should clear the cached initial values" do
      @o.save
      @o.column_changes.must_equal({})
    end

    it "save_changes should clear the cached initial values" do
      @c.dataset = @c.dataset.with_extend do
        def supports_insert_select?; true end
        def insert_select(*) {:id=>1} end
      end
      @o.save
      @o.column_changes.must_equal({})
    end
  end

  describe "with existing instance" do
    before do
      @o = @c[1]
      @o.initial_changed = 'ic2'.dup
      @o.missing_changed = 'mc2'.dup
    end

    include dirty_plugin_specs

    it "previous_changes should be the previous changes after saving" do
      @o.save
      @o.previous_changes.must_equal(:initial_changed=>['ic', 'ic2'], :missing_changed=>[nil, 'mc2'])
    end

    it "should work when freezing objects after saving" do
      @o.initial = 'a'
      @o.save
      @o.freeze
      @o.previous_changes[:initial].must_equal ['i', 'a']
      proc{@o.initial = 'b'}.must_raise
    end
  end
end
