# frozen_string_literal: true

require 'test_helper'
require 'fileutils'

class TestForGem < LoaderTest
  MY_GEM = ['lib/my_gem.rb', <<~EOS]
    $for_gem_test_loader = Zeitwerk::Loader.for_gem
    $for_gem_test_loader.enable_reloading
    $for_gem_test_loader.setup

    module MyGem
    end
  EOS

  def with_my_gem(files = [MY_GEM], rq = true)
    with_files(files) do
      with_load_path('lib') do
        if rq
          assert require('my_gem')
          assert MyGem
        end
        yield
      end
    end
  end

  test 'sets things correctly' do
    files = [
      MY_GEM,
      ['lib/my_gem/foo.rb', 'class MyGem::Foo; end'],
      ['lib/my_gem/foo/bar.rb', 'MyGem::Foo::Bar = true']
    ]
    with_my_gem(files) do
      assert MyGem::Foo::Bar

      $for_gem_test_loader.unload
      assert !Object.const_defined?(:MyGem)

      $for_gem_test_loader.setup
      assert MyGem::Foo::Bar
    end
  end

  test 'is idempotent' do
    $for_gem_test_zs = []
    files = [
      ['lib/my_gem.rb', <<-EOS],
        $for_gem_test_zs << Zeitwerk::Loader.for_gem
        $for_gem_test_zs.last.enable_reloading
        $for_gem_test_zs.last.setup

        module MyGem
        end
      EOS
    ]

    with_my_gem(files) do
      $for_gem_test_zs.first.unload
      assert !Object.const_defined?(:MyGem)

      $for_gem_test_zs.first.setup
      assert MyGem

      assert_equal 2, $for_gem_test_zs.size
      assert_same $for_gem_test_zs.first, $for_gem_test_zs.last
    end
  end

  test 'configures the gem inflector by default' do
    with_my_gem do
      assert_instance_of Zeitwerk::GemInflector, $for_gem_test_loader.inflector
    end
  end

  test 'configures the basename of the root file as loader tag' do
    with_my_gem do
      assert_equal 'my_gem', $for_gem_test_loader.tag
    end
  end

  test 'does not warn if lib only has expected files' do
    with_my_gem([MY_GEM], false) do
      assert_silent do
        assert require('my_gem')
      end
    end
  end

  test 'does not warn if lib only has extra, non-hidden, non-Ruby files' do
    files = [MY_GEM, ['lib/i18n.yml', ''], ['lib/.vscode', '']]
    with_my_gem(files, false) do
      assert_silent do
        assert require('my_gem')
      end
    end
  end

  test 'warns if the lib has an extra Ruby file' do
    files = [MY_GEM, ['lib/foo.rb', '']]
    with_my_gem(files, false) do
      _out, err = capture_io do
        assert require('my_gem')
      end
      assert_includes err, 'Zeitwerk defines the constant Foo after the file'
      assert_includes err, File.expand_path('lib/foo.rb')
      assert_includes err, 'Zeitwerk::Loader.for_gem(warn_on_extra_files: false)'
    end
  end

  test 'does not warn if lib has an extra Ruby file, but it is ignored' do
    files = [['lib/my_gem.rb', <<~EOS], ['lib/foo.rb', '']]
      loader = Zeitwerk::Loader.for_gem
      loader.ignore("\#{__dir__}/foo.rb")
      loader.enable_reloading
      loader.setup

      module MyGem
      end
    EOS
    with_my_gem(files, false) do
      _out, err = capture_io do
        assert require('my_gem')
      end
      assert_empty err
    end
  end

  test 'does not warn if lib has an extra Ruby file, but warnings are disabled' do
    files = [['lib/my_gem.rb', <<~EOS], ['lib/foo.rb', '']]
      loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
      loader.enable_reloading
      loader.setup

      module MyGem
      end
    EOS
    with_my_gem(files, false) do
      _out, err = capture_io do
        assert require('my_gem')
      end
      assert_empty err
    end
  end

  test 'warns if lib has an extra directory' do
    files = [MY_GEM, ['lib/foo/bar.rb', 'Foo::Bar = true']]
    with_my_gem(files, false) do
      _out, err = capture_io do
        assert require('my_gem')
      end
      assert_includes err, 'Zeitwerk defines the constant Foo after the directory'
      assert_includes err, File.expand_path('lib/foo')
      assert_includes err, 'Zeitwerk::Loader.for_gem(warn_on_extra_files: false)'
    end
  end

  test 'does not warn if lib has an extra directory, but it is ignored' do
    files = [['lib/my_gem.rb', <<~EOS], ['lib/foo/bar.rb', 'Foo::Bar = true']]
      loader = Zeitwerk::Loader.for_gem
      loader.ignore("\#{__dir__}/foo")
      loader.enable_reloading
      loader.setup

      module MyGem
      end
    EOS
    with_my_gem(files, false) do
      _out, err = capture_io do
        assert require('my_gem')
      end
      assert_empty err
    end
  end

  test 'does not warn if lib has an extra directory, but it has no Ruby files' do
    files = [['lib/my_gem.rb', <<~EOS], ['lib/tasks/newsletter.rake', '']]
      loader = Zeitwerk::Loader.for_gem
      loader.enable_reloading
      loader.setup

      module MyGem
      end
    EOS
    with_my_gem(files, false) do
      _out, err = capture_io do
        assert require('my_gem')
      end
      assert_empty err
    end
  end

  test 'does not warn if lib has an extra directory, but warnings are disabled' do
    files = [['lib/my_gem.rb', <<~EOS], ['lib/foo/bar.rb', 'Foo::Bar = true']]
      loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
      loader.enable_reloading
      loader.setup

      module MyGem
      end
    EOS
    with_my_gem(files, false) do
      _out, err = capture_io do
        assert require('my_gem')
      end
      assert_empty err
    end
  end

  test 'warnings do not assume the namespace directory is the tag' do
    files = [['lib/my_gem.rb', <<~EOS], ['lib/foo/bar.rb', 'Foo::Bar = true']]
      loader = Zeitwerk::Loader.for_gem
      loader.tag = 'foo'
      loader.enable_reloading
      loader.setup

      module MyGem
      end
    EOS
    with_my_gem(files, false) do
      _out, err = capture_io do
        assert require('my_gem')
      end
      assert_includes err, 'Zeitwerk defines the constant Foo after the directory'
      assert_includes err, File.expand_path('lib/foo')
      assert_includes err, 'Zeitwerk::Loader.for_gem(warn_on_extra_files: false)'
    end
  end

  test 'warnings use the gem inflector' do
    files = [['lib/my_gem.rb', <<~EOS], ['lib/foo/bar.rb', 'Foo::Bar = true']]
      loader = Zeitwerk::Loader.for_gem
      loader.inflector.inflect('foo' => 'BAR')
      loader.enable_reloading
      loader.setup

      module MyGem
      end
    EOS
    with_my_gem(files, false) do
      _out, err = capture_io do
        assert require('my_gem')
      end
      assert_includes err, 'Zeitwerk defines the constant BAR after the directory'
      assert_includes err, File.expand_path('lib/foo')
      assert_includes err, 'Zeitwerk::Loader.for_gem(warn_on_extra_files: false)'
    end
  end

  # Emulates the project setup in https://github.com/fxn/zeitwerk/issues/282.
  test 'version inflection works with symlinked files' do
    files = [
      MY_GEM,
      ['lib/my_gem/version.rb', "MyGem::VERSION = '1.2.3'"]
    ]
    with_files(files) do
      FileUtils.mkdir_p('lib2/my_gem')
      File.symlink(File.expand_path('lib/my_gem.rb'), 'lib2/my_gem.rb')
      File.symlink(File.expand_path('lib/my_gem/version.rb'), 'lib2/my_gem/version.rb')
      with_load_path('lib2') do
        require 'my_gem'
        assert_equal '1.2.3', MyGem::VERSION
      end
    end
  end

  test 'version inflection works with a symlinked lib' do
    files = [
      MY_GEM,
      ['lib/my_gem/version.rb', "MyGem::VERSION = '1.2.3'"]
    ]
    with_files(files) do
      File.symlink(File.expand_path('lib'), 'lib2')
      with_load_path('lib2') do
        require 'my_gem'
        assert_equal '1.2.3', MyGem::VERSION
      end
    end
  end
end
