Module: Glib::MailerTester

Extended by:
ActiveSupport::Concern
Defined in:
lib/glib/mailer_tester.rb

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.compute_missing_previews(mailer_actions:, previewed_actions:, except: []) ⇒ Object

Pure (no Rails reflection) so it is unit-testable.

mailer_actions:    { "FooMailer" => ["welcome", "goodbye"] }
previewed_actions: ->(mailer_name) { ["welcome"] }
except:            ["FooMailer#goodbye"]


80
81
82
83
84
85
86
87
88
# File 'lib/glib/mailer_tester.rb', line 80

def self.compute_missing_previews(mailer_actions:, previewed_actions:, except: [])
  excluded = except.map(&:to_s).to_set
  mailer_actions.flat_map do |mailer, actions|
    previewed = Array(previewed_actions.call(mailer)).map(&:to_s).to_set
    actions.map(&:to_s)
           .reject { |action| previewed.include?(action) }
           .map { |action| "#{mailer}##{action}" }
  end.reject { |id| excluded.include?(id) }.sort
end

.load_mailer_filesObject

Loads only ‘app/mailers` (so every mailer subclass is defined) rather than eager-loading the whole app — faster, and avoids unrelated load failures.



118
119
120
121
122
123
124
125
126
127
128
# File 'lib/glib/mailer_tester.rb', line 118

def self.load_mailer_files
  dir = Rails.root.join('app/mailers')
  return unless dir.exist?

  loader = Rails.respond_to?(:autoloaders) ? Rails.autoloaders.main : nil
  if loader.respond_to?(:eager_load_dir)
    loader.eager_load_dir(dir.to_s)
  else
    Rails.application.eager_load!
  end
end

.load_preview_filesObject

Loads every preview file (idempotent — ‘require` no-ops if already loaded).



62
63
64
# File 'lib/glib/mailer_tester.rb', line 62

def self.load_preview_files
  Dir.glob(Rails.root + 'test/mailers/previews/*').each { |file| require file }
end

.missing_previews(except: []) ⇒ Object

Sorted Array<String> of “Mailer#action” entries that have no preview.



67
68
69
70
71
72
73
74
# File 'lib/glib/mailer_tester.rb', line 67

def self.missing_previews(except: [])
  load_preview_files
  compute_missing_previews(
    mailer_actions: reflect_mailer_actions,
    previewed_actions: ->(mailer) { reflect_preview_methods(mailer) },
    except: except
  )
end

.missing_previews_message(missing) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/glib/mailer_tester.rb', line 90

def self.missing_previews_message(missing)
  <<~MSG
    Every mailer action must have a preview so `generate_preview_tests` snapshot-tests it.
    These actions have none:

      #{missing.join("\n  ")}

    Add a method named after the action to the matching `<Mailer>Preview` class under
    test/mailers/previews/, returning `<Mailer>.<action>(...)`. If an action genuinely
    cannot be previewed, allowlist it with a reason:

      assert_all_mailer_actions_have_previews(except: ['FooMailer#some_action']) # why
  MSG
end

.reflect_mailer_actionsObject

Reflection glue (needs a loaded Rails app). Scopes to app mailers under ‘ApplicationMailer` so third-party mailers (Devise, etc.) are not flagged.



107
108
109
110
111
112
113
114
# File 'lib/glib/mailer_tester.rb', line 107

def self.reflect_mailer_actions
  load_mailer_files
  base = '::ApplicationMailer'.safe_constantize || ActionMailer::Base
  base.descendants.each_with_object({}) do |mailer, acc|
    actions = mailer.action_methods.to_a
    acc[mailer.name] = actions unless actions.empty?
  end
end

.reflect_preview_methods(mailer_name) ⇒ Object



130
131
132
133
# File 'lib/glib/mailer_tester.rb', line 130

def self.reflect_preview_methods(mailer_name)
  preview = "#{mailer_name}Preview".safe_constantize
  preview ? preview.instance_methods(false).map(&:to_s) : []
end

Instance Method Details

#assert_mail_unchanged(mail) ⇒ Object

TODO: Remove test result files that are not relevant anymore



165
166
167
168
169
170
171
172
173
174
175
# File 'lib/glib/mailer_tester.rb', line 165

def assert_mail_unchanged(mail)
  # `method_name` refers to the name of the current test method.
  dir = File.join(log_dir, method_name)
  unless File.directory?(dir)
    FileUtils.mkdir_p(dir)
  end

  mail.to.each do |recipient|
    _assert_mail_body_unchanged("#{method_name}/#{recipient}", mail)
  end
end

#assert_mails_unchangedObject



158
159
160
161
162
# File 'lib/glib/mailer_tester.rb', line 158

def assert_mails_unchanged
  mails.each do |mail|
    assert_mail_unchanged(mail)
  end
end

#last_mailObject



152
153
154
155
156
# File 'lib/glib/mailer_tester.rb', line 152

def last_mail
  raise 'Last mail does not exist' unless (mail = mails.last)

  mail
end

#log_dirObject

Overridable



140
141
142
# File 'lib/glib/mailer_tester.rb', line 140

def log_dir
  File.expand_path(File.join(log_root_dir, "results/#{class_name.underscore}/"))
end

#log_root_dirObject



135
136
137
# File 'lib/glib/mailer_tester.rb', line 135

def log_root_dir
  raise 'Implementation needed'
end

#mailsObject



148
149
150
# File 'lib/glib/mailer_tester.rb', line 148

def mails
  ActionMailer::Base.deliveries
end

#reset_mailsObject



144
145
146
# File 'lib/glib/mailer_tester.rb', line 144

def reset_mails
  ActionMailer::Base.deliveries = []
end