require 'spec_helper'

describe Listen::Listener do
  let(:adapter)           { mock(Listen::Adapter, :start => true).as_null_object }
  let(:watched_directory) { File.dirname(__FILE__) }

  subject { described_class.new(watched_directory) }

  before do
    Listen::Adapter.stub(:select_and_initialize) { adapter }
    # Don't build a record of the files inside the base directory.
    subject.directory_record.stub(:build)
  end

  it_should_behave_like 'a listener to changes on a file-system'

  describe '#initialize' do
    context 'with no options' do
      it 'sets the directory' do
        subject.directory.should eq watched_directory
      end

      it 'converts the passed path into an absolute path - #21' do
        described_class.new(File.join(watched_directory, '..')).directory.should eq File.expand_path('..', watched_directory)
      end

      it 'sets the option for using relative paths in the callback to the default one' do
        subject.instance_variable_get(:@use_relative_paths).should eq described_class::DEFAULT_TO_RELATIVE_PATHS
      end
    end

    context 'with custom options' do
      subject { described_class.new(watched_directory, :ignore => /\.ssh/, :filter => [/.*\.rb/,/.*\.md/],
                                    :latency => 0.5, :force_polling => true, :relative_paths => true) }

      it 'passes the custom ignored paths to the directory record' do
        subject.directory_record.ignoring_patterns.should include /\.ssh/
      end

      it 'passes the custom filters to the directory record' do
        subject.directory_record.filtering_patterns.should =~  [/.*\.rb/,/.*\.md/]
      end

      it 'sets the cutom option for using relative paths in the callback' do
        subject.instance_variable_get(:@use_relative_paths).should be_true
      end

      it 'sets adapter_options' do
        subject.instance_variable_get(:@adapter_options).should eq(:latency => 0.5, :force_polling => true)
      end
    end
  end

  describe '#start' do
    it 'selects and initializes an adapter' do
      Listen::Adapter.should_receive(:select_and_initialize).with(watched_directory, {}) { adapter }
      subject.start
    end

    it 'builds the directory record' do
      subject.directory_record.should_receive(:build)
      subject.start
    end
  end

  context 'with a started listener' do
    before do
      subject.stub(:initialize_adapter) { adapter }
      subject.start
    end

    describe '#unpause' do
      it 'rebuilds the directory record' do
        subject.directory_record.should_receive(:build)
        subject.unpause
      end
    end
  end

  describe '#ignore'do
    it 'delegates the work to the directory record' do
      subject.directory_record.should_receive(:ignore).with 'some_directory'
      subject.ignore 'some_directory'
    end
  end

  describe '#filter' do
    it 'delegates the work to the directory record' do
      subject.directory_record.should_receive(:filter).with /\.txt$/
      subject.filter /\.txt$/
    end
  end


  describe '#on_change' do
    let(:directories) { %w{dir1 dir2 dir3} }
    let(:changes)     { {:modified => [], :added => [], :removed => []} }
    let(:callback)    { Proc.new { @called = true } }

    before do
      @called = false
      subject.directory_record.stub(:fetch_changes => changes)
    end

    it 'fetches the changes of the directory record' do
      subject.directory_record.should_receive(:fetch_changes).with(
        directories, hash_including(:relative_paths => described_class::DEFAULT_TO_RELATIVE_PATHS)
      )
      subject.on_change(directories)
    end

    context 'with relative paths option set to true' do
      subject { described_class.new(watched_directory, :relative_paths => true) }

      it 'fetches the changes of the directory record' do
        subject.directory_record.should_receive(:fetch_changes).with(directories, hash_including(:relative_paths => true))
        subject.on_change(directories)
      end
    end

    context 'with no changes to report' do
      if RUBY_VERSION[/^1.8/]
        it 'does not run the callback' do
            subject.change(&callback)
            subject.on_change(directories)
            @called.should be_false
        end
      else
        it 'does not run the callback' do
          callback.should_not_receive(:call)
          subject.change(&callback)
          subject.on_change(directories)
        end
      end
    end

    context 'with changes to report' do
      let(:changes)     { {:modified => %w{path1}, :added => [], :removed => %w{path2}} }

      if RUBY_VERSION[/^1.8/]
        it 'runs the callback passing it the changes' do
          subject.change(&callback)
          subject.on_change(directories)
          @called.should be_true
        end
      else
        it 'runs the callback passing it the changes' do
          callback.should_receive(:call).with(changes[:modified], changes[:added], changes[:removed])
          subject.change(&callback)
          subject.on_change(directories)
        end
      end
    end
  end
end
