diff --git a/lib/kangaru.rb b/lib/kangaru.rb index 2ee951f..701d4c7 100644 --- a/lib/kangaru.rb +++ b/lib/kangaru.rb @@ -9,9 +9,30 @@ require "yaml" module Kangaru - Zeitwerk::Loader.for_gem(warn_on_extra_files: false).setup + INFLECTIONS = { + "rspec" => "RSpec" + }.freeze + + @loader = Zeitwerk::Loader.for_gem( + warn_on_extra_files: false + ).tap do |loader| + loader.inflector.inflect(INFLECTIONS) + loader.setup + end class << self attr_accessor :application + + def env=(value) + @env = value.to_sym + end + + def env + @env ||= :runtime + end + + def eager_load(namespace) + @loader.eager_load_namespace(namespace) + end end end diff --git a/lib/kangaru/initialiser.rb b/lib/kangaru/initialiser.rb index a1e7072..55aae43 100644 --- a/lib/kangaru/initialiser.rb +++ b/lib/kangaru/initialiser.rb @@ -22,6 +22,7 @@ def self.extended(namespace) source = caller[0].gsub(/:.*$/, "") Kangaru.application = Application.new(source:, namespace:) + Kangaru.eager_load(Initialisers) namespace.extend InjectedMethods end diff --git a/lib/kangaru/initialisers/rspec.rb b/lib/kangaru/initialisers/rspec.rb new file mode 100644 index 0000000..68c9341 --- /dev/null +++ b/lib/kangaru/initialisers/rspec.rb @@ -0,0 +1,11 @@ +module Kangaru + module Initialisers + module RSpec + if Object.const_defined?(:RSpec) + ::RSpec.configure do + Kangaru.env = :test + end + end + end + end +end diff --git a/sig/kangaru.rbs b/sig/kangaru.rbs index 4135d43..c9fab43 100644 --- a/sig/kangaru.rbs +++ b/sig/kangaru.rbs @@ -1,10 +1,16 @@ module Kangaru VERSION: String + INFLECTIONS: Hash[String, String] extend ClassMethods module ClassMethods + @loader: Zeitwerk::Loader + attr_accessor application: Application + attr_accessor env: Symbol + + def eager_load: (Module) -> void end # Included in target applications that extend Kangaru::Initialiser. diff --git a/sig/kangaru/initialisers/rspec.rbs b/sig/kangaru/initialisers/rspec.rbs new file mode 100644 index 0000000..36aca68 --- /dev/null +++ b/sig/kangaru/initialisers/rspec.rbs @@ -0,0 +1,6 @@ +module Kangaru + module Initialisers + module RSpec + end + end +end diff --git a/sig/zeitwerk/loader.rbs b/sig/zeitwerk/loader.rbs new file mode 100644 index 0000000..b2ec374 --- /dev/null +++ b/sig/zeitwerk/loader.rbs @@ -0,0 +1,5 @@ +module Zeitwerk + class Loader + def eager_load_namespace: (Module) -> void + end +end diff --git a/spec/features/initialiser_spec.rb b/spec/features/initialiser_spec.rb index 05c441d..2cc270e 100644 --- a/spec/features/initialiser_spec.rb +++ b/spec/features/initialiser_spec.rb @@ -10,88 +10,112 @@ class Foobar RUBY end - context "when the target gem does not extend the initialiser" do - before do - gem.main_file.write(<<~RUBY) - require "kangaru" + describe "initialising application" do + context "when the target gem does not extend the initialiser" do + before do + gem.main_file.write(<<~RUBY) + require "kangaru" + + module SomeGem + end + RUBY + end - module SomeGem - end - RUBY - end + it "loads the gem module" do + expect { require_gem } + .to change { Object.const_defined?(:SomeGem) } + .from(false) + .to(true) + end - it "loads the gem module" do - expect { require_gem } - .to change { Object.const_defined?(:SomeGem) } - .from(false) - .to(true) - end + it "does not autoload gem files" do + expect { require_gem } + .not_to change { Object.const_defined?("SomeGem::Foobar") } + .from(false) + end - it "does not autoload gem files" do - expect { require_gem } - .not_to change { Object.const_defined?("SomeGem::Foobar") } - .from(false) - end + it "does not set the Kangaru application reference" do + expect { require_gem }.not_to change { Kangaru.application }.from(nil) + end - it "does not set the Kangaru application reference" do - expect { require_gem }.not_to change { Kangaru.application }.from(nil) + it "does not define the run! method in the gem's root module" do + require_gem + expect(SomeGem).not_to respond_to(:run!) + end end - it "does not define the run! method in the gem's root module" do - require_gem - expect(SomeGem).not_to respond_to(:run!) - end - end + context "when the target gem extends the initialiser" do + before do + gem.main_file.write(<<~RUBY) + require "kangaru" - context "when the target gem extends the initialiser" do - before do - gem.main_file.write(<<~RUBY) - require "kangaru" + module SomeGem + extend Kangaru::Initialiser + end + RUBY + end - module SomeGem - extend Kangaru::Initialiser - end - RUBY - end + it "loads the gem module" do + expect { require_gem } + .to change { Object.const_defined?("SomeGem") } + .from(false) + .to(true) + end - it "loads the gem module" do - expect { require_gem } - .to change { Object.const_defined?("SomeGem") } - .from(false) - .to(true) - end + it "autoloads gem files" do + expect { require_gem } + .to change { Object.const_defined?("SomeGem::Foobar") } + .from(false) + .to(true) + end - it "autoloads gem files" do - expect { require_gem } - .to change { Object.const_defined?("SomeGem::Foobar") } - .from(false) - .to(true) - end + it "sets the Kangaru application reference" do + expect { require_gem } + .to change { Kangaru.application } + .from(nil) + .to(a_kind_of(Kangaru::Application)) + end - it "sets the Kangaru application reference" do - expect { require_gem } - .to change { Kangaru.application } - .from(nil) - .to(a_kind_of(Kangaru::Application)) - end + it "defines the run! method in the gem's root module" do + require_gem + expect(SomeGem).to respond_to(:run!) + end - it "defines the run! method in the gem's root module" do - require_gem - expect(SomeGem).to respond_to(:run!) - end + describe "application reference" do + subject(:application) { Kangaru.application } - describe "application reference" do - subject(:application) { Kangaru.application } + before { require_gem } - before { require_gem } + it "sets the application source to the gem main file" do + expect(application.paths.source).to eq(gem.main_file) + end - it "sets the application source to the gem main file" do - expect(application.paths.source).to eq(gem.main_file) + it "sets the application name to the expected value" do + expect(application.paths.name).to eq("some_gem") + end end + end + end - it "sets the application name to the expected value" do - expect(application.paths.name).to eq("some_gem") - end + describe "setting test environment in RSpec" do + subject(:run_test) { gem.exec("rspec #{test_file}") } + + let(:test_file) { gem.spec_path("test_spec") } + + before do + test_file.write(<<~RUBY) + RSpec.describe do + it "sets the test environment" do + expect(Kangaru).to be_test + end + end + RUBY + end + + include_context :kangaru_initialised + + it "configures Kangaru to the test environment" do + expect(run_test).not_to include("FAILED") end end end diff --git a/spec/kangaru/initialiser_spec.rb b/spec/kangaru/initialiser_spec.rb index db589a5..a7e0dd1 100644 --- a/spec/kangaru/initialiser_spec.rb +++ b/spec/kangaru/initialiser_spec.rb @@ -5,6 +5,7 @@ stub_const "Namespace", Module.new allow(Kangaru::Application).to receive(:new).and_return(application) + allow(Kangaru).to receive(:eager_load) allow_any_instance_of(Kernel).to receive(:caller).and_return([callsite]) end @@ -40,6 +41,15 @@ .to change { Kangaru.application } .to(application) end + + it "eager loads the Initialisers namespace" do + extended + + expect(Kangaru) + .to have_received(:eager_load) + .with(Kangaru::Initialisers) + .once + end end context "when calling file is not in a gem structure" do diff --git a/spec/kangaru_spec.rb b/spec/kangaru_spec.rb new file mode 100644 index 0000000..fa00136 --- /dev/null +++ b/spec/kangaru_spec.rb @@ -0,0 +1,92 @@ +RSpec.describe Kangaru do + describe "@loader" do + subject(:loader) { described_class.instance_variable_get(:@loader) } + + let(:lib_directory) { File.join(File.dirname(__dir__), "lib") } + + it "sets the @loader instance variable" do + expect(described_class).to be_instance_variable_defined(:@loader) + end + + it "sets the instance variable to a Zeitwerk loader" do + expect(loader).to be_a(Zeitwerk::Loader) + end + + it "autoloads the lib directory" do + expect(loader.dirs).to include(lib_directory) + end + end + + describe ".env=" do + subject(:set_env) { described_class.env = env } + + def env_ivar = described_class.instance_variable_get(:@env) + + after do + described_class.remove_instance_variable(:@env) if env_ivar + end + + context "when env is a symbol" do + let(:env) { :env } + + it "sets to a symbol" do + expect { set_env }.to change { env_ivar }.to(env) + end + end + + context "when env is a string" do + let(:env) { "env" } + + it "sets to a symbol" do + expect { set_env }.to change { env_ivar }.to(env.to_sym) + end + end + end + + describe ".env" do + subject(:env) { described_class.env } + + def env_ivar = described_class.instance_variable_get(:@env) + + around do |spec| + described_class.remove_instance_variable(:@env) if env_ivar + spec.run + described_class.remove_instance_variable(:@env) if env_ivar + end + + context "when env is not set" do + it "returns :runtime" do + expect(described_class.env).to eq(:runtime) + end + end + + context "when env is set" do + before { described_class.env = env } + + let(:env) { :foobar } + + it "returns the set value" do + expect(described_class.env).to eq(env) + end + end + end + + describe ".eager_load" do + subject(:eager_load) { described_class.eager_load(namespace) } + + let(:namespace) { Module.new } + + let(:loader) { described_class.instance_variable_get(:@loader) } + + before { allow(loader).to receive(:eager_load_namespace) } + + it "delegates to the zeitwerk loader" do + eager_load + + expect(loader) + .to have_received(:eager_load_namespace) + .with(namespace) + .once + end + end +end