Skip to content
54 changes: 44 additions & 10 deletions lib/hanami/cli/commands/app/generate/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,58 @@ def generator_class
# @since 2.2.0
# @api private
def call(name:, slice: nil, **opts)
if slice
base_path = fs.join("slices", inflector.underscore(slice))
raise MissingSliceError.new(slice) unless fs.exist?(base_path)
slice ||= detect_slice_from_cwd

generator.call(
key: name,
namespace: slice,
base_path: base_path,
**opts,
)
else
if slice.nil?
generator.call(
key: name,
namespace: app.namespace,
base_path: "app",
**opts,
)
return
end

slice_root = slice.respond_to?(:root) ? slice.root : detect_slice_root(slice)
raise MissingSliceError.new(slice) unless fs.exist?(slice_root)

generator.call(
key: name,
namespace: slice,
base_path: slice_root,
**opts,
)
end

private

def detect_slice_from_cwd
slices_by_root = app.slices.with_nested.each.to_h { |slice| [slice.root.to_s, slice] }
slices_by_root[fs.pwd]
end

# Returns the root for the given slice name.
#
# This currently works with top-level slices only, and it simply appends the slice's
# name onto the "slices/" dir, returning e.g. "slices/main" when given "main".
#
# TODO: Make this work with nested slices when given slash-delimited slice names like
# "parent/child", which should look for "slices/parent/slices/child".
#
# This method makes two checks for the slice root (building off both `app.root` as well
# as `fs`). This is entirely to account for how we test commands, with most tests using
# an in-memory `fs` adapter, any files created via which will be invisible to the `app`,
# which doesn't know about the `fs`.
#
# FIXME: It would be better to find a way for this to make one check only. An ideal
# approach would be to use the slice_name to find actual slice registered within
# `app.slices`. To do this, we'd probably need to stop testing with an in-memory `fs`
# here.
def detect_slice_root(slice_name)
slice_root_in_fs = fs.join("slices", inflector.underscore(slice_name))
return slice_root_in_fs if fs.exist?(slice_root_in_fs)

app.root.join("slices", inflector.underscore(slice_name))
end
end
end
Expand Down
95 changes: 95 additions & 0 deletions spec/unit/hanami/cli/commands/app/generate/slice_detection_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# frozen_string_literal: true

require "hanami"

RSpec.describe "slice detection", :app_integration do
subject(:cmd) { Hanami::CLI::Commands::App::Generate::View.new(inflector: inflector, out: out) }

let(:inflector) { Dry::Inflector.new }
let(:out) { StringIO.new }
def output
out.rewind && out.read.chomp
end

before do
with_directory(@app_dir) do
write "config/app.rb", <<~RUBY
module TestApp
class App < Hanami::App
end
end
RUBY

write "slices/main/.keep", ""
write "slices/admin/.keep", ""
write "slices/admin/slices/search/.keep", ""

require "hanami/prepare"
end
end

around do |example|
@app_dir = make_tmp_directory
orig_cwd = Dir.pwd

# Use non-block form of chdir to avoid "conflicting chdir" warnings when we do it inside the
# examples
Dir.chdir(@app_dir)
example.call
Dir.chdir(orig_cwd)
end

describe "slice detection from current working directory" do
it "detects the slice based on the current working directory" do
Dir.chdir("slices/main") do
subject.call(name: "showcase")
end

expect(File.exist?("slices/main/views/showcase.rb")).to be true
end

context "with deeply nested slices existing" do
it "determines the nested slice when inside of it" do
Dir.chdir("slices/admin/slices/search") do
subject.call(name: "panel")
end

expect(File.exist?("slices/admin/slices/search/views/panel.rb")).to be true
end

it "still determines the parent slice when inside of it" do
Dir.chdir("slices/main") do
subject.call(name: "bookcase")
end

expect(File.exist?("slices/main/views/bookcase.rb")).to be true
end
end
end

context "when --slice option is provided" do
it "in child slice - respects the --slice option - overwrites slice detection" do
Dir.chdir("slices/admin/slices/search") do
subject.call(name: "bookcase", slice: "main")
end

expect(File.exist?("slices/main/views/bookcase.rb")).to be true
end

it "in sibling slice - respects the --slice option - overwrites slice detection" do
Dir.chdir("slices/main") do
subject.call(name: "shop", slice: "admin")
end

expect(File.exist?("slices/admin/views/shop.rb")).to be true
end
end

context "when working outside slice directory" do
it "uses app namespace when not in a slice directory" do
subject.call(name: "important_view")

expect(File.exist?("app/views/important_view.rb")).to be true
end
end
end