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

describe 'XCRes::StringsAnalyzer' do

  def subject
    XCRes::StringsAnalyzer
  end

  before do
    @target = stub('Target', build_configurations: [])
    @project = stub('Project', files: [], path: Pathname('.'))
    @target.stubs(project: @project)

    @analyzer = subject.new(@target)
    @analyzer.logger = stub('Logger', :log)
    @analyzer.expects(:warn).never
    @analyzer.expects(:error).never
  end

  describe "#initialize" do
    it 'should set given target as attribute' do
      @analyzer = subject.new(@target)
      @analyzer.target.should.be.eql?(@target)
      @analyzer.project.should.be.eql?(@project)
    end

    it 'should set option :default_language as attribute' do
      @analyzer = subject.new(@target, default_language: 'en')
      @analyzer.default_language.should.be.eql?('en')
    end
  end

  describe "#analyze" do
    it 'should return the built sections' do
      section = mock()
      @analyzer.expects(:build_section).returns(section)
      @analyzer.analyze.should.be.eql?([section])
    end
  end

  describe "#build_section" do
    it 'should return an empty section if there are no strings files' do
      @analyzer.stubs(:strings_file_refs).returns([])
      @analyzer.build_section.should.be.eql?(XCRes::Section.new 'Strings', {})
    end

    it 'should return a new section if there are strings files' do
      strings_file_ref = stub('FileRef', {
        name:      'en',
        path:      'Localizable.strings',
        real_path: Pathname(File.expand_path('./en.lproj/Localizable.strings'))
      })
      @analyzer.stubs(:strings_file_refs).returns([strings_file_ref])
      @analyzer.stubs(:keys_by_file)
        .with(Pathname('en.lproj/Localizable.strings'))
        .returns({ 'greeting' => { value: 'greeting' }})
      @analyzer.build_section.should.be.eql?(XCRes::Section.new 'Strings', { 'greeting' => { value: 'greeting' }})
    end
  end

  describe "#languages" do
    it 'should return the default language if it is set' do
      @analyzer.default_language = 'en'
      @analyzer.languages.should.be.eql?(['en'])
    end

    it 'should return an empty array if there is no used language' do
      @analyzer.expects(:native_dev_languages).returns(['en'].to_set).at_least_once
      @analyzer.expects(:used_languages).returns([].to_set).at_least_once
      @analyzer.languages.should.be.eql?([].to_set)
    end

    it 'should return the used languages if there is no matching native dev language' do
      @analyzer.expects(:native_dev_languages).returns(['de'].to_set).at_least_once
      @analyzer.expects(:used_languages).returns(['en', 'es'].to_set).at_least_once
      @analyzer.languages.should.be.eql?(['en', 'es'].to_set)
    end

    it 'should return the intersection of languages if there is a common language' do
      @analyzer.expects(:native_dev_languages).returns(['en'].to_set).at_least_once
      @analyzer.expects(:used_languages).returns(['en', 'es'].to_set).at_least_once
      @analyzer.languages.should.be.eql?(['en'].to_set)
    end
  end

  describe "with fixture project" do
    before do
      @target = app_target
      @analyzer = subject.new(@target)
      @analyzer.logger = stub('Logger', :log)
      @analyzer.expects(:warn).never
      @analyzer.expects(:error).never
    end

    describe "#strings_file_refs" do
      it 'should return the strings files of the fixture project' do
        strings_files = @analyzer.strings_file_refs
        strings_files.count.should.be.eql?(3)
        strings_files[0].path.should.be.eql?('en.lproj/InfoPlist.strings')
        strings_files[1].path.should.be.eql?('en.lproj/Localizable.strings')
        strings_files[2].path.should.be.eql?('de.lproj/Localizable.strings')
      end
    end

    describe '#derive_used_languages' do
      it 'should find used languages' do
        languages = @analyzer.derive_used_languages(@analyzer.strings_file_refs)
        languages.should == ['en', 'de'].to_set
      end
    end

    describe '#used_languages' do
      it 'should return english and german as used languages' do
        @analyzer.used_languages.should == ['en', 'de'].to_set
      end
    end

    describe '#info_plist_paths' do
      it 'should return a set with the configured paths of the project' do
        @analyzer.info_plist_paths.should == [Pathname('Example/Example-Info.plist')].to_set
      end
    end

    describe '#absolute_info_plist_paths' do
      it 'should resolve the path if it is relative' do
        @analyzer.absolute_project_file_path('Info.plist')
          .relative_path_from(fixture_path)
          .should == Pathname('Example/Info.plist')
      end

      it 'should resolve the path if $SRCROOT is used' do
        @analyzer.absolute_project_file_path('$SRCROOT/Info.plist')
          .relative_path_from(fixture_path)
          .should == Pathname('Example/Info.plist')
      end
    end

    describe '#native_dev_languages' do
      it 'should return english' do
        @analyzer.native_dev_languages.should == ['en'].to_set
      end

      describe 'with non-configured Info.plist' do
        it 'should warn on missing plists' do
          @target.build_configurations[0].build_settings['INFOPLIST_FILE'] = 'NonExisting.plist'
          @analyzer.expects(:warn).once
          @analyzer.native_dev_languages.should == ['en'].to_set
        end
      end
    end

    describe '#read_plist_key' do
      before do
        @plist_path = fixture_path + 'Example/Example/Example-Info.plist'
      end

      it 'should read and return existing keys' do
        @analyzer.read_plist_key(@plist_path, :CFBundleDevelopmentRegion)
          .should == 'en'
      end

      it 'should raise an ArgumentError on non-existing files' do
        plist_path = Pathname('NonExisting.plist')
        proc do
          @analyzer.read_plist_key(plist_path, :XCResNonExistingKey)
        end.should.raise(ArgumentError).message
          .should == "File 'NonExisting.plist' doesn't exist"
      end

      it 'should raise an ArgumentError on non-existing keys' do
        proc do
          @analyzer.read_plist_key(@plist_path, :XCResNonExistingKey)
        end.should.raise(ArgumentError).message
          .should == 'Error reading plist: Print: Entry, ":XCResNonExistingKey", Does Not Exist'
      end
    end

    describe '#absolute_project_file_path' do
      it 'should treat relative paths correctly' do
        @analyzer.absolute_project_file_path('Info.plist')
          .relative_path_from(fixture_path)
          .should == Pathname('Example/Info.plist')
      end

      it 'should replace $SRCROOT with project path' do
        @analyzer.absolute_project_file_path('$SRCROOT/Info.plist')
          .relative_path_from(fixture_path)
          .should == Pathname('Example/Info.plist')
      end

      it 'should replace ${SRCROOT} with project path' do
        @analyzer.absolute_project_file_path('${SRCROOT}/Info.plist')
          .relative_path_from(fixture_path)
          .should == Pathname('Example/Info.plist')
      end

      it 'should replace $(SRCROOT) with project path' do
        @analyzer.absolute_project_file_path('$(SRCROOT)/Info.plist')
          .relative_path_from(fixture_path)
          .should == Pathname('Example/Info.plist')
      end
    end

    describe '#selected_strings_file_refs' do
      describe 'for english development language' do
        before do
          @analyzer.stubs(:languages).returns ['en']
        end

        it 'should return the selected strings file refs' do
          strings_files = @analyzer.selected_strings_file_refs
          strings_files.count.should.be.eql?(2)
          strings_files[0].path.should.be.eql?('en.lproj/InfoPlist.strings')
          strings_files[1].path.should.be.eql?('en.lproj/Localizable.strings')
        end
      end

      describe 'for german development language' do
        before do
          @analyzer.stubs(:languages).returns ['de']
        end

        it 'should return the selected strings file refs' do
          @analyzer.stubs(:languages).returns ['de']
          strings_files = @analyzer.selected_strings_file_refs
          strings_files.count.should.be.eql?(1)
          strings_files[0].path.should.be.eql?('de.lproj/Localizable.strings')
        end
      end
    end
  end

  describe "#read_strings_file" do
    it 'should read a valid file' do
      @analyzer.read_strings_file(fixture_path + 'Example/Example/en.lproj/Localizable.strings').should == {
        "foo"               => "Foo String",
        "bar"               => "Bar String",
        "en_exclusive"      => "Only in english",
        "example"           => "Lorem Ipsum",
        "123-abc-3e7.text"  => "Hello Storyboards",
      }
    end

    it 'should raise an error for an invalid file' do
      proc do
        @analyzer.read_strings_file(fixture_path + 'StringsFiles/syntax_error_missing_semicolon.strings')
      end.should.raise(StandardError).message.should.include "Old-style plist parser: missing semicolon in dictionary on line 2."
    end
  end

  describe "#keys_by_file" do
    it 'should return the string keys hash' do
      path = fixture_path + 'Example/Example/en.lproj/Localizable.strings'
      @analyzer.keys_by_file(path).should == {
        "foo"          => { value: "foo",          comment: "Foo String"      },
        "bar"          => { value: "bar",          comment: "Bar String"      },
        "en_exclusive" => { value: "en_exclusive", comment: "Only in english" },
        "example"      => { value: "example",      comment: "Lorem Ipsum"     },
      }
    end
  end

end