require 'test_helper'

class TestEagerLoadNamespaceWithObjectRootNamespace < LoaderTest
  test 'eager loads everything' do
    files = [['x.rb', 'X = 1'], ['m/x.rb', 'M::X = 1']]
    with_setup(files) do
      loader.eager_load_namespace(Object)

      assert required?(files)
    end
  end

  test 'shortcircuits if eager loaded' do
    with_setup do
      loader.eager_load

      # Dirty way to prove we shortcircuit.
      def loader.actual_eager_load_dir(*)
        raise
      end

      begin
        loader.eager_load_namespace(Object)
      rescue
        flunk
      else
        pass
      end
    end
  end

  test 'does not assume the namespace has a name' do
    files = [['x.rb', 'X = 1']]
    with_setup(files) do
      loader.eager_load_namespace(Module.new)

      assert !required?(files[0])
    end
  end

  test 'eager loads everything (multiple root directories)' do
    files = [
      ['rd1/x.rb', 'X = 1'],
      ['rd1/m/x.rb', 'M::X = 1'],
      ['rd2/y.rb', 'Y = 1'],
      ['rd2/m/y.rb', 'M::Y = 1']
    ]
    with_setup(files) do
      loader.eager_load_namespace(Object)

      assert required?(files)
    end
  end

  test 'supports collapsed directories' do
    files = [
      ['rd1/collapsed/m/x.rb', 'M::X = 1'],
      ['rd2/y.rb', 'Y = 1'],
      ['rd2/m/y.rb', 'M::Y = 1']
    ]
    with_setup(files) do
      loader.eager_load_namespace(M)
      assert required?(files[0])
      assert !required?(files[1])
      assert required?(files[2])
    end
  end

  test 'eager loads everything (nested root directories)' do
    files = [
      ['x.rb', 'X = 1'],
      ['m/x.rb', 'M::X = 1'],
      ['nested/y.rb', 'Y = 1'],
      ['nested/m/y.rb', 'M::Y = 1']
    ]
    with_setup(files, dirs: %w(. nested)) do
      loader.eager_load_namespace(Object)

      assert required?(files)
    end
  end

  test 'eager loads a managed namespace' do
    files = [['x.rb', 'X = 1'], ['m/x.rb', 'M::X = 1']]
    with_setup(files) do
      loader.eager_load_namespace(M)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'eager loading a non-managed namespace does not raise' do
    files = [['x.rb', 'X = 1']]
    with_setup(files) do
      loader.eager_load_namespace(self.class)

      assert !required?(files[0])
    end
  end

  test 'does not eager load ignored files' do
    files = [['x.rb', 'X = 1'], ['ignored.rb', 'IGNORED']]
    with_setup(files) do
      loader.eager_load_namespace(Object)

      assert required?(files[0])
      assert !required?(files[1])
    end
  end

  test 'does not eager load shadowed files' do
    files = [['rd1/x.rb', 'X = 1'], ['rd2/x.rb', 'X = 1']]
    with_setup(files) do
      loader.eager_load_namespace(Object)

      assert required?(files[0])
      assert !required?(files[1])
    end
  end

  test 'skips root directories which are excluded from eager loading (Object)' do
    files = [['rd1/a.rb', 'A = 1'], ['rd2/b.rb', 'B = 1']]
    with_setup(files) do
      loader.do_not_eager_load('rd1')
      loader.eager_load_namespace(Object)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'skips directories which are excluded from eager loading (namespace, ancestor)' do
    files = [['rd1/m/a.rb', 'M::A = 1'], ['rd2/m/b.rb', 'M::B = 1']]
    with_setup(files) do
      loader.do_not_eager_load('rd1/m')
      loader.eager_load_namespace(Object)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'skips directories which are excluded from eager loading (namespace, self)' do
    files = [['rd1/m/a.rb', 'M::A = 1'], ['rd2/m/b.rb', 'M::B = 1']]
    with_setup(files) do
      loader.do_not_eager_load('rd1/m')
      loader.eager_load_namespace(M)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'skips directories which are excluded from eager loading (namespace, descendant)' do
    files = [['rd1/m/n/a.rb', 'M::N::A = 1'], ['rd2/m/n/b.rb', 'M::N::B = 1']]
    with_setup(files) do
      loader.do_not_eager_load('rd1/m')
      loader.eager_load_namespace(M::N)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'does not eager load namespaces from other loaders' do
    files = [['a/m/x.rb', 'M::X = 1'], ['b/m/y.rb', 'M::Y = 1']]
    with_files(files) do
      loader.push_dir('a')
      loader.setup

      new_loader(dirs: 'b').eager_load_namespace(M)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'raises if the argument is not a class or module object' do
    with_setup do
      e = assert_raises(Zeitwerk::Error) do
        loader.eager_load_namespace(self.class.name)
      end
      assert_equal %Q("#{self.class.name}" is not a class or module object), e.message
    end
  end

  test 'raises if the argument is not a class or module object, even if eager loaded' do
    with_setup do
      loader.eager_load
      e = assert_raises(Zeitwerk::Error) do
        loader.eager_load_namespace(self.class.name)
      end
      assert_equal %Q("#{self.class.name}" is not a class or module object), e.message
    end
  end
end

class TestEagerLoadNamespaceWithCustomRootNamespace < LoaderTest
  module CN; end
  ancestors = [Object, self, CN]

  ancestors.each do |ancestor|
    test "eager loads everything #{ancestor}" do
      files = [['x.rb', "#{CN}::X = 1"], ['m/x.rb', "#{CN}::M::X = 1"]]
      with_setup(files, namespace: CN) do
        loader.eager_load_namespace(ancestor)

        assert required?(files)
      end
    end

    test "eager loads everything (multiple root directories) #{ancestor}" do
      files = [
        ['rd1/x.rb', "#{CN}::X = 1"],
        ['rd1/m/x.rb', "#{CN}::M::X = 1"],
        ['rd2/y.rb', "#{CN}::Y = 1"],
        ['rd2/m/y.rb', "#{CN}::M::Y = 1"]
      ]
      with_setup(files, namespace: CN) do
        loader.eager_load_namespace(ancestor)

        assert required?(files)
      end
    end

    test "eager loads everything (nested root directories) #{ancestor}" do
      files = [
        ['x.rb', "#{CN}::X = 1"],
        ['m/x.rb', "#{CN}::M::X = 1"],
        ['nested/y.rb', "#{CN}::Y = 1"],
        ['nested/m/y.rb', "#{CN}::M::Y = 1"]
      ]
      with_setup(files, dirs: %w(. nested), namespace: CN) do
        loader.eager_load_namespace(ancestor)

        assert required?(files)
      end
    end

    test "eager loads everything (nested root directories, different namespaces 1) #{ancestor}" do
      files = [
        ['x.rb', "#{CN}::X = 1"],
        ['m/x.rb', "#{CN}::M::X = 1"],
        ['nested/y.rb', 'Y = 1'],
        ['nested/m/y.rb', 'M::Y = 1']
      ]
      with_files(files) do
        loader.push_dir('.', namespace: CN)
        loader.push_dir('nested')
        loader.setup
        loader.eager_load_namespace(ancestor)

        if ancestor.equal?(Object)
          assert required?(files)
        else
          assert required?(files[0..1])
          assert !required?(files[2])
          assert !required?(files[3])
        end
      end
    end

    test "eager loads everything (nested root directories, different namespaces 2) #{ancestor}" do
      files = [
        ['x.rb', 'X = 1'],
        ['m/x.rb', 'M::X = 1'],
        ['nested/y.rb', "#{CN}::Y = 1"],
        ['nested/m/y.rb', "#{CN}::M::Y = 1"]
      ]
      with_files(files) do
        loader.push_dir('.')
        loader.push_dir('nested', namespace: CN)
        loader.setup
        loader.eager_load_namespace(ancestor)

        if ancestor.equal?(Object)
          assert required?(files)
        else
          assert !required?(files[0])
          assert !required?(files[1])
          assert required?(files[2..3])
        end
      end
    end
  end

  test 'does not assume the namespace has a name' do
    files = [['x.rb', 'X = 1']]
    with_setup(files, namespace: CN) do
      loader.eager_load_namespace(Module.new)

      assert !required?(files[0])
    end
  end

  test 'eager loads a managed namespace' do
    files = [['x.rb', "#{CN}::X = 1"], ['m/x.rb', "#{CN}::M::X = 1"]]
    with_setup(files, namespace: CN) do
      loader.eager_load_namespace(CN::M)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'eager loading a non-managed namespace does not raise' do
    files = [['x.rb', "#{CN}::X = 1"]]
    with_setup(files, namespace: CN) do
      loader.eager_load_namespace(String)

      assert !required?(files[0])
    end
  end

  test 'does not eager load ignored files' do
    files = [['x.rb', "#{CN}::X = 1"], ['ignored.rb', 'IGNORED']]
    with_setup(files, namespace: CN) do
      loader.eager_load_namespace(CN)

      assert required?(files[0])
      assert !required?(files[1])
    end
  end

  test 'does not eager load shadowed files' do
    files = [['rd1/x.rb', "#{CN}::X = 1"], ['rd2/x.rb', "#{CN}::X = 1"]]
    with_setup(files, namespace: CN) do
      loader.eager_load_namespace(CN)

      assert required?(files[0])
      assert !required?(files[1])
    end
  end

  test 'skips root directories which are excluded from eager loading (Object)' do
    files = [['rd1/a.rb', "#{CN}::A = 1"], ['rd2/b.rb', "#{CN}::B = 1"]]
    with_setup(files, namespace: CN) do
      loader.do_not_eager_load('rd1')
      loader.eager_load_namespace(Object)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'skips root directories which are excluded from eager loading (self)' do
    files = [['rd1/a.rb', "#{CN}::A = 1"], ['rd2/b.rb', "#{CN}::B = 1"]]
    with_setup(files, namespace: CN) do
      loader.do_not_eager_load('rd1')
      loader.eager_load_namespace(CN)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'skips directories which are excluded from eager loading (namespace, ancestor)' do
    files = [['rd1/m/a.rb', "#{CN}::M::A = 1"], ['rd2/m/b.rb', "#{CN}::M::B = 1"]]
    with_setup(files, namespace: CN) do
      loader.do_not_eager_load('rd1/m')
      loader.eager_load_namespace(Object)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'skips directories which are excluded from eager loading (namespace, self)' do
    files = [['rd1/m/a.rb', "#{CN}::M::A = 1"], ['rd2/m/b.rb', "#{CN}::M::B = 1"]]
    with_setup(files, namespace: CN) do
      loader.do_not_eager_load('rd1/m')
      loader.eager_load_namespace(CN::M)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'skips directories which are excluded from eager loading (namespace, descendant)' do
    files = [['rd1/m/n/a.rb', "#{CN}::M::N::A = 1"], ['rd2/m/n/b.rb', "#{CN}::M::N::B = 1"]]
    with_setup(files, namespace: CN) do
      loader.do_not_eager_load('rd1/m')
      loader.eager_load_namespace(CN::M::N)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'does not eager load namespaces from other loaders' do
    files = [['a/m/x.rb', "#{CN}::M::X = 1"], ['b/m/y.rb', "#{CN}::M::Y = 1"]]
    with_files(files) do
      loader.push_dir('a', namespace: CN)
      loader.setup

      new_loader(dirs: 'b', namespace: CN).eager_load_namespace(CN::M)

      assert !required?(files[0])
      assert required?(files[1])
    end
  end

  test 'raises if called before setup' do
    assert_raises(Zeitwerk::SetupRequired) do
      loader.eager_load_namespace(self.class)
    end
  end

  test 'the class method broadcasts the call to all registered loaders' do
    files = [
      ['a/m/x.rb', 'M::X = 1'],
      ['a/x.rb', 'X = 1'],
      ['b/m/y.rb', 'M::Y = 1'],
      ['b/y.rb', 'Y = 1'],
      ['c/z.rb', 'Z = 1'],
    ]
    with_files(files) do
      new_loader(dirs: 'a')
      new_loader(dirs: 'b')
      new_loader(dirs: 'c')

      Zeitwerk::Loader.eager_load_namespace(M)

      assert required?(files[0])
      assert !required?(files[1])

      assert required?(files[2])
      assert !required?(files[3])

      assert !required?(files[4])
    end
  end

  test 'the class method skips loaders that are not ready' do
    files = [['m/x.rb', 'M::X = 1']]
    with_setup(files) do
      new_loader(setup: false) # should be skipped
      Zeitwerk::Loader.eager_load_namespace(M)
      assert required?(files)
    end
  end
end
