Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix tests
  • Loading branch information
bopm committed Jun 2, 2025
commit e416f2da1c35c5878d55513773b7abd67852a926
12 changes: 5 additions & 7 deletions lib/tailwindcss/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,13 @@ def rails_css_compressor?
defined?(Rails) && Rails&.application&.config&.assets&.css_compressor.present?
end

def engines_tailwindcss_roots
def engines_roots
return [] unless defined?(Rails)
return [] unless Rails.application&.config&.tailwindcss_rails&.engines

Rails::Engine.subclasses.select do |engine|
Rails::Engine.descendants.select do |engine|
begin
spec = Gem::Specification.find_by_name(engine.engine_name)
spec.dependencies.any? { |d| d.name == 'tailwindcss-rails' }
rescue Gem::MissingSpecError
false
engine.engine_name.in?(Rails.application.config.tailwindcss_rails.engines)
end
end.map do |engine|
[
Expand All @@ -61,7 +59,7 @@ def engines_tailwindcss_roots
end

def with_dynamic_input
engine_roots = Tailwindcss::Commands.engines_tailwindcss_roots
engine_roots = Tailwindcss::Commands.engines_roots
if engine_roots.any?
Tempfile.create('tailwind.css') do |file|
file.write(engine_roots.map { |root| "@import \"#{root}\";" }.join("\n"))
Expand Down
4 changes: 4 additions & 0 deletions lib/tailwindcss/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ class Engine < ::Rails::Engine
config.tailwindcss_rails = ActiveSupport::OrderedOptions.new
config.tailwindcss_rails.engines = []

initializer 'tailwindcss.load_hook' do |app|
ActiveSupport.run_load_hooks(:tailwindcss_rails, app)
end

initializer "tailwindcss.disable_generator_stylesheets" do
Rails.application.config.generators.stylesheets = false
end
Expand Down
262 changes: 150 additions & 112 deletions test/lib/tailwindcss/commands_test.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
require "test_helper"
require "ostruct"
require "minitest/mock"

class Tailwindcss::CommandsTest < ActiveSupport::TestCase
attr_accessor :executable
attr_accessor :executable, :original_rails, :tmp_dir

def setup
super
setup do
@tmp_dir = Dir.mktmpdir
@original_rails = Object.const_get(:Rails) if Object.const_defined?(:Rails)
@executable = Tailwindcss::Ruby.executable
end

teardown do
FileUtils.rm_rf(@tmp_dir)
Tailwindcss::Commands.remove_tempfile! if Tailwindcss::Commands.class_variable_defined?(:@@tempfile)
restore_rails_constant
end

test ".compile_command" do
Rails.stub(:root, File) do # Rails.root won't work in this test suite
actual = Tailwindcss::Commands.compile_command("app/assets/tailwind/application.css")
Expand Down Expand Up @@ -127,138 +135,168 @@ def setup
end
end

test ".engines_tailwindcss_roots when there are no engines" do
Rails.stub(:root, Pathname.new("/dummy")) do
Rails::Engine.stub(:subclasses, []) do
assert_empty Tailwindcss::Commands.engines_tailwindcss_roots
end
test ".engines_roots when Rails is not defined" do
Object.send(:remove_const, :Rails) if Object.const_defined?(:Rails)
assert_empty Tailwindcss::Commands.engines_roots
end

test ".engines_roots when no engines are configured" do
with_rails_app do
assert_empty Tailwindcss::Commands.engines_roots
end
end

test ".engines_tailwindcss_roots when there are engines" do
Dir.mktmpdir do |tmpdir|
root = Pathname.new(tmpdir)

# Create multiple engines
engine_root1 = root.join('engine1')
engine_root2 = root.join('engine2')
engine_root3 = root.join('engine3')
FileUtils.mkdir_p(engine_root1)
FileUtils.mkdir_p(engine_root2)
FileUtils.mkdir_p(engine_root3)

engine1 = Class.new(Rails::Engine) do
define_singleton_method(:engine_name) { "test_engine1" }
define_singleton_method(:root) { engine_root1 }
end
test ".engines_roots when there are engines" do
within_engine_configs do |engine1, engine2, engine3|
roots = Tailwindcss::Commands.engines_roots

assert_equal 2, roots.size
assert_includes roots, engine1.css_path.to_s
assert_includes roots, engine2.css_path.to_s
refute_includes roots, engine3.css_path.to_s
end
end

test ".with_dynamic_input yields tempfile path when engines exist" do
within_engine_configs do |engine1, engine2|
Tailwindcss::Commands.with_dynamic_input do |css_path|
assert_match(/tailwind\.css/, css_path)
assert File.exist?(css_path)

engine2 = Class.new(Rails::Engine) do
define_singleton_method(:engine_name) { "test_engine2" }
define_singleton_method(:root) { engine_root2 }
content = File.read(css_path)
assert_match %r{@import "#{engine1.css_path}";}, content
assert_match %r{@import "#{engine2.css_path}";}, content
assert_match %r{@import "#{Rails.root.join('app/assets/tailwind/application.css')}";}, content
end
end
end

engine3 = Class.new(Rails::Engine) do
define_singleton_method(:engine_name) { "test_engine3" }
define_singleton_method(:root) { engine_root3 }
test ".with_dynamic_input yields application.css path when no engines" do
with_rails_app do
expected_path = Rails.root.join("app/assets/tailwind/application.css").to_s
Tailwindcss::Commands.with_dynamic_input do |css_path|
assert_equal expected_path, css_path
end
end
end

# Create mock specs for engines
spec1 = Minitest::Mock.new
spec1.expect(:dependencies, [Gem::Dependency.new("tailwindcss-rails")])

spec2 = Minitest::Mock.new
spec2.expect(:dependencies, [Gem::Dependency.new("tailwindcss-rails")])

spec3 = Minitest::Mock.new
spec3.expect(:dependencies, [])

# Set up file structure
# Engine 1: CSS in engine root
engine1_css = engine_root1.join("app/assets/tailwind/test_engine1/application.css")
FileUtils.mkdir_p(File.dirname(engine1_css))
FileUtils.touch(engine1_css)

# Engine 2: CSS in Rails root
engine2_css = root.join("app/assets/tailwind/test_engine2/application.css")
FileUtils.mkdir_p(File.dirname(engine2_css))
FileUtils.touch(engine2_css)

# Engine 3: CsS in engine root, but no tailwindcss-rails dependency
engine3_css = engine_root2.join("app/assets/tailwind/test_engine3/application.css")
FileUtils.mkdir_p(File.dirname(engine3_css))
FileUtils.touch(engine3_css)

find_by_name_results = {
"test_engine1" => spec1,
"test_engine2" => spec2,
"test_engine3" => spec3,
}

Gem::Specification.stub(:find_by_name, ->(name) { find_by_name_results[name] }) do
Rails.stub(:root, root) do
Rails::Engine.stub(:subclasses, [engine1, engine2]) do
roots = Tailwindcss::Commands.engines_tailwindcss_roots

assert_equal 2, roots.size
assert_includes roots, engine1_css.to_s
assert_includes roots, engine2_css.to_s
assert_not_includes roots, engine3_css.to_s
end
test "engines can be configured via tailwindcss_rails.engines" do
with_rails_app do
# Create a test engine
test_engine = Class.new(Rails::Engine) do
def self.engine_name
"test_engine"
end

def self.root
Pathname.new(Dir.mktmpdir)
end
end

spec1.verify
spec2.verify
# Create CSS file for the engine
engine_css_path = test_engine.root.join("app/assets/tailwind/test_engine/application.css")
FileUtils.mkdir_p(File.dirname(engine_css_path))
FileUtils.touch(engine_css_path)

# Create application-level CSS file
app_css_path = Rails.root.join("app/assets/tailwind/test_engine/application.css")
FileUtils.mkdir_p(File.dirname(app_css_path))
FileUtils.touch(app_css_path)

# Register the engine
Rails::Engine.descendants << test_engine

# Store the hook for later execution
hook = nil
ActiveSupport.on_load(:tailwindcss_rails) do
hook = self
Rails.application.config.tailwindcss_rails.engines << "test_engine"
end

# Trigger the hook manually
ActiveSupport.run_load_hooks(:tailwindcss_rails, hook)

# Verify the engine is included in roots
roots = Tailwindcss::Commands.engines_roots
assert_equal 1, roots.size
assert_includes roots, app_css_path.to_s
ensure
FileUtils.rm_rf(test_engine.root) if defined?(test_engine)
FileUtils.rm_rf(File.dirname(app_css_path)) if defined?(app_css_path)
end
end

test ".with_dynamic_input when there are no engines" do
Dir.mktmpdir do |tmpdir|
root = Pathname.new(tmpdir)
input_path = root.join("app/assets/tailwind/application.css").to_s
private
def with_rails_app
Object.send(:remove_const, :Rails) if Object.const_defined?(:Rails)
Object.const_set(:Rails, setup_mock_rails)
yield
end

def setup_mock_rails
mock_engine = Class.new do
class << self
attr_accessor :engine_name, :root

Rails.stub(:root, root) do
Tailwindcss::Commands.stub(:engines_tailwindcss_roots, []) do
Tailwindcss::Commands.with_dynamic_input do |actual|
assert_equal input_path, actual
def descendants
@descendants ||= []
end
end
end
end
end

test ".with_dynamic_input when there are engines" do
Dir.mktmpdir do |tmpdir|
root = Pathname.new(tmpdir)
input_path = root.join("app/assets/tailwind/application.css").to_s
mock_rails = Class.new do
class << self
attr_accessor :root, :application

# Create necessary files
FileUtils.mkdir_p(File.dirname(input_path))
FileUtils.touch(input_path)
def const_get(const_name)
return Engine if const_name == :Engine
super
end
end
end

# Create engine CSS file
engine_css_path = root.join("app/assets/tailwind/test_engine/application.css")
FileUtils.mkdir_p(File.dirname(engine_css_path))
FileUtils.touch(engine_css_path)
mock_rails.const_set(:Engine, mock_engine)
mock_rails.root = Pathname.new(@tmp_dir)
mock_rails.application = OpenStruct.new(
config: OpenStruct.new(
tailwindcss_rails: OpenStruct.new(engines: []),
assets: OpenStruct.new(css_compressor: nil)
)
)
mock_rails
end

Rails.stub(:root, root) do
Tailwindcss::Commands.stub(:engines_tailwindcss_roots, [engine_css_path.to_s]) do
Tailwindcss::Commands.with_dynamic_input do |actual|
temp_path = Pathname.new(actual)
refute_equal input_path, temp_path.to_s # input path should be different
assert_match(/tailwind\.css/, temp_path.basename.to_s) # should use temp file
assert_includes [Dir.tmpdir, '/tmp'], temp_path.dirname.to_s # should be in temp directory

# Check temp file contents
temp_content = File.read(actual)
expected_content = <<~CSS
@import "#{engine_css_path}";
@import "#{input_path}";
CSS
assert_equal expected_content.strip, temp_content.strip
end
def restore_rails_constant
Object.send(:remove_const, :Rails) if Object.const_defined?(:Rails)
Object.const_set(:Rails, @original_rails) if @original_rails
end

def within_engine_configs
engine_configs = create_test_engines
with_rails_app do
Rails.application.config.tailwindcss_rails.engines = %w[test_engine1 test_engine2]

# Create and register mock engine classes
engine_configs.each do |config|
engine_class = Class.new(Rails::Engine)
engine_class.engine_name = config.name
engine_class.root = Pathname.new(config.root)
Rails::Engine.descendants << engine_class
end

yield(*engine_configs)
end
end

def create_test_engines
[1, 2, 3].map do |i|
engine = OpenStruct.new
engine.name = "test_engine#{i}"
engine.root = File.join(@tmp_dir, "engine#{i}")
engine.css_path = File.join(@tmp_dir, "app/assets/tailwind/test_engine#{i}/application.css")
FileUtils.mkdir_p(File.dirname(engine.css_path))
FileUtils.touch(engine.css_path)
engine
end
end
end
end