lookbook_visual_tester 0.5.10 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.env.example +13 -0
- data/CHANGELOG.md +31 -0
- data/README.md +18 -9
- data/Rakefile +53 -0
- data/docs/superpowers/plans/2026-08-09-lookbook-visual-tester-hardening.md +1792 -0
- data/lib/lookbook_visual_tester/browser_discovery.rb +31 -0
- data/lib/lookbook_visual_tester/configuration.rb +11 -4
- data/lib/lookbook_visual_tester/drivers/ferrum_driver.rb +8 -4
- data/lib/lookbook_visual_tester/preview_checker.rb +43 -213
- data/lib/lookbook_visual_tester/railtie.rb +2 -8
- data/lib/lookbook_visual_tester/runner.rb +108 -92
- data/lib/lookbook_visual_tester/server_test_runner.rb +102 -0
- data/lib/lookbook_visual_tester/services/image_trimmer.rb +59 -0
- data/lib/lookbook_visual_tester/update_previews.rb +3 -12
- data/lib/lookbook_visual_tester/version.rb +1 -1
- data/lib/lookbook_visual_tester.rb +4 -0
- data/lib/tasks/lookbook_visual_tester.rake +50 -51
- metadata +38 -51
- data/lib/lookbook_visual_tester/baseline_manager.rb +0 -34
- data/lib/lookbook_visual_tester/capybara_setup.rb +0 -29
- data/lib/lookbook_visual_tester/screenshot_taker.rb +0 -118
- data/lib/lookbook_visual_tester/session_manager.rb +0 -17
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# lib/lookbook_visual_tester/browser_discovery.rb
|
|
2
|
+
require 'rbconfig'
|
|
3
|
+
|
|
4
|
+
module LookbookVisualTester
|
|
5
|
+
# Finds a usable Chrome/Chromium binary, preferring standard absolute paths
|
|
6
|
+
# over PATH-resolved lookups. PATH lookup can resolve to wrapper scripts
|
|
7
|
+
# (e.g. a shell wrapper that injects GPU/Wayland flags) that break headless
|
|
8
|
+
# Chrome with errors like "Multiple targets are not supported in headless mode".
|
|
9
|
+
module BrowserDiscovery
|
|
10
|
+
LINUX_CANDIDATES = %w[
|
|
11
|
+
/usr/bin/google-chrome-stable
|
|
12
|
+
/usr/bin/google-chrome
|
|
13
|
+
/usr/bin/chromium
|
|
14
|
+
/usr/bin/chromium-browser
|
|
15
|
+
/opt/google/chrome/google-chrome
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
MAC_CANDIDATES = %w[
|
|
19
|
+
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
|
|
20
|
+
/Applications/Chromium.app/Contents/MacOS/Chromium
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
def self.find_binary
|
|
24
|
+
candidates = case RbConfig::CONFIG['host_os']
|
|
25
|
+
when /darwin/ then MAC_CANDIDATES
|
|
26
|
+
else LINUX_CANDIDATES
|
|
27
|
+
end
|
|
28
|
+
candidates.find { |path| File.executable?(path) }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -4,7 +4,8 @@ module LookbookVisualTester
|
|
|
4
4
|
attr_accessor :lookbook_host, :ui_comparison, :diff_dir, :baseline_dir, :current_dir,
|
|
5
5
|
:history_dir, :history_keep_last_n, :threads, :copy_to_clipboard,
|
|
6
6
|
:components_folder, :automatic_run, :mask_selectors, :driver_adapter,
|
|
7
|
-
:preview_checker_setup, :logger, :wait_time, :tolerance
|
|
7
|
+
:preview_checker_setup, :logger, :wait_time, :tolerance,
|
|
8
|
+
:browser_path, :browser_options, :browser_timeout, :process_timeout
|
|
8
9
|
|
|
9
10
|
DEFAULT_THREADS = 4
|
|
10
11
|
|
|
@@ -21,13 +22,12 @@ module LookbookVisualTester
|
|
|
21
22
|
@history_dir = @base_path.join('history')
|
|
22
23
|
@threads = ENV.fetch('LOOKBOOK_THREADS', DEFAULT_THREADS).to_i
|
|
23
24
|
@history_keep_last_n = 5
|
|
24
|
-
@copy_to_clipboard =
|
|
25
|
+
@copy_to_clipboard = false
|
|
25
26
|
@components_folder = 'app/components'
|
|
26
|
-
@automatic_run = ENV.fetch('LOOKBOOK_AUTOMATIC_RUN', false)
|
|
27
|
+
@automatic_run = ENV.fetch('LOOKBOOK_AUTOMATIC_RUN', 'false') == 'true'
|
|
27
28
|
@mask_selectors = []
|
|
28
29
|
@driver_adapter = :ferrum
|
|
29
30
|
@preview_checker_setup = nil
|
|
30
|
-
@preview_checker_setup = nil
|
|
31
31
|
@wait_time = 0.5
|
|
32
32
|
@tolerance = 0.0
|
|
33
33
|
@logger = if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
@@ -38,6 +38,13 @@ module LookbookVisualTester
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
@lookbook_host = ENV.fetch('LOOKBOOK_HOST', 'http://localhost:5000')
|
|
41
|
+
# Browser (Ferrum/Chrome) configuration. Defaults target modern Chrome's
|
|
42
|
+
# `--headless=new` mode; the old `--headless` flag is rejected by current
|
|
43
|
+
# Chrome builds ("Multiple targets are not supported in headless mode").
|
|
44
|
+
@browser_path = ENV['LOOKBOOK_BROWSER_PATH'] || LookbookVisualTester::BrowserDiscovery.find_binary
|
|
45
|
+
@browser_options = { 'headless' => 'new' }
|
|
46
|
+
@browser_timeout = ENV.fetch('LOOKBOOK_BROWSER_TIMEOUT', '10').to_i
|
|
47
|
+
@process_timeout = ENV.fetch('LOOKBOOK_PROCESS_TIMEOUT', '10').to_i
|
|
41
48
|
end
|
|
42
49
|
|
|
43
50
|
def base_path=(value)
|
|
@@ -6,11 +6,15 @@ module LookbookVisualTester
|
|
|
6
6
|
class FerrumDriver < Driver
|
|
7
7
|
def initialize(config)
|
|
8
8
|
super
|
|
9
|
-
|
|
9
|
+
browser_opts = {
|
|
10
10
|
headless: true,
|
|
11
|
-
window_size: [1280, 800],
|
|
12
|
-
timeout:
|
|
13
|
-
|
|
11
|
+
window_size: [1280, 800],
|
|
12
|
+
timeout: config.browser_timeout,
|
|
13
|
+
process_timeout: config.process_timeout,
|
|
14
|
+
browser_options: config.browser_options
|
|
15
|
+
}
|
|
16
|
+
browser_opts[:browser_path] = config.browser_path if config.browser_path
|
|
17
|
+
@browser = Ferrum::Browser.new(**browser_opts)
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
def visit(url)
|
|
@@ -16,30 +16,15 @@ module LookbookVisualTester
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def deep_check
|
|
19
|
-
# Ensure custom setup is run before deep checks
|
|
20
19
|
run_setup
|
|
21
|
-
check_preview_controller_config
|
|
22
20
|
run_checks(:deep_render_check)
|
|
23
21
|
end
|
|
24
22
|
|
|
25
23
|
def missing
|
|
26
24
|
components_dir = Rails.root.join(@config.components_folder)
|
|
27
|
-
|
|
28
|
-
# But Lookbook can be configured differently. We should use Lookbook's config if possible to know where previews are.
|
|
29
|
-
# For now, let's stick to the user's script logic which assumes standard paths or iterate through loaded components.
|
|
30
|
-
|
|
31
|
-
# Better approach: Iterate through all known components and check if they have a preview.
|
|
32
|
-
# However, "all known components" might be hard to get if they aren't loaded.
|
|
33
|
-
# Let's use the file system approach as in the user script.
|
|
25
|
+
previews_dir = preview_paths.first
|
|
34
26
|
|
|
35
27
|
components = Dir.glob(File.join(components_dir, '**', '*_component.rb'))
|
|
36
|
-
previews_dir = Rails.root.join('test/components/previews') # Default, maybe make configurable?
|
|
37
|
-
|
|
38
|
-
# Trying to find where previews are located from Rails config if possible
|
|
39
|
-
if defined?(Rails) && Rails.application.config.view_component.preview_paths.any?
|
|
40
|
-
# Use simple heuristic: first path
|
|
41
|
-
previews_dir = Pathname.new(Rails.application.config.view_component.preview_paths.first)
|
|
42
|
-
end
|
|
43
28
|
|
|
44
29
|
missing = []
|
|
45
30
|
components.each do |component_path|
|
|
@@ -57,19 +42,22 @@ module LookbookVisualTester
|
|
|
57
42
|
|
|
58
43
|
private
|
|
59
44
|
|
|
45
|
+
def preview_paths
|
|
46
|
+
if defined?(Rails) && Rails.application.config.view_component.preview_paths.any?
|
|
47
|
+
Rails.application.config.view_component.preview_paths.map { |p| Pathname.new(p) }
|
|
48
|
+
else
|
|
49
|
+
[Rails.root.join('test/components/previews')]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def run_setup
|
|
54
|
+
@config.preview_checker_setup&.call
|
|
55
|
+
end
|
|
56
|
+
|
|
60
57
|
def run_checks(check_method)
|
|
61
58
|
previews = Lookbook.previews
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
# We want to flatten the work items: (PreviewClass, example_name)
|
|
65
|
-
work_items = []
|
|
66
|
-
previews.each do |preview|
|
|
67
|
-
# preview is a Lookbook::Preview object which wraps the class
|
|
68
|
-
# But for checking we might want the class directly or iterate scenarios
|
|
69
|
-
examples = preview.scenarios
|
|
70
|
-
examples.each do |example|
|
|
71
|
-
work_items << { preview: preview, example: example }
|
|
72
|
-
end
|
|
59
|
+
work_items = previews.flat_map do |preview|
|
|
60
|
+
preview.scenarios.map { |example| { preview: preview, example: example } }
|
|
73
61
|
end
|
|
74
62
|
|
|
75
63
|
if @config.threads > 1
|
|
@@ -82,44 +70,28 @@ module LookbookVisualTester
|
|
|
82
70
|
results = Concurrent::Promises.zip(*promises).value
|
|
83
71
|
pool.shutdown
|
|
84
72
|
pool.wait_for_termination
|
|
73
|
+
results
|
|
85
74
|
else
|
|
86
|
-
|
|
87
|
-
measure_and_send(item[:preview], item[:example], check_method)
|
|
88
|
-
end
|
|
75
|
+
work_items.map { |item| measure_and_send(item[:preview], item[:example], check_method) }
|
|
89
76
|
end
|
|
90
|
-
|
|
91
|
-
results
|
|
92
77
|
end
|
|
93
78
|
|
|
94
79
|
def measure_and_send(preview, example, method_name)
|
|
95
80
|
result = nil
|
|
96
|
-
time = Benchmark.realtime
|
|
97
|
-
result = send(method_name, preview, example)
|
|
98
|
-
end
|
|
81
|
+
time = Benchmark.realtime { result = send(method_name, preview, example) }
|
|
99
82
|
result.duration = time
|
|
100
83
|
result
|
|
101
84
|
end
|
|
102
85
|
|
|
103
86
|
def basic_check(preview, example)
|
|
104
|
-
# user script logic:
|
|
105
|
-
# preview_instance = preview_class.new
|
|
106
|
-
# component = preview_instance.public_send(preview_example)
|
|
107
|
-
# if component.respond_to?(:render_in) ...
|
|
108
|
-
|
|
109
|
-
# Lookbook::Preview wrapper might help, but let's go to the class
|
|
110
87
|
preview_class = preview.preview_class
|
|
111
88
|
example_name = example.name
|
|
112
89
|
|
|
113
90
|
begin
|
|
114
91
|
preview_instance = preview_class.new
|
|
115
|
-
unless preview_instance.respond_to?(example_name)
|
|
116
|
-
return CheckResult.new(preview_name: preview.name, example_name: example_name,
|
|
117
|
-
status: :passed)
|
|
118
|
-
end
|
|
92
|
+
return CheckResult.new(preview_name: preview.name, example_name: example_name, status: :passed) unless preview_instance.respond_to?(example_name)
|
|
119
93
|
|
|
120
94
|
preview_instance.public_send(example_name)
|
|
121
|
-
|
|
122
|
-
# We don't render, just verify we can call it.
|
|
123
95
|
CheckResult.new(preview_name: preview.name, example_name: example_name, status: :passed)
|
|
124
96
|
rescue StandardError => e
|
|
125
97
|
CheckResult.new(preview_name: preview.name, example_name: example_name, status: :failed,
|
|
@@ -132,82 +104,31 @@ module LookbookVisualTester
|
|
|
132
104
|
example_name = example.name
|
|
133
105
|
|
|
134
106
|
begin
|
|
135
|
-
# Use ViewComponent::Preview logic to get the component to render
|
|
136
|
-
# This handles the case where the method returns nil (implicit template)
|
|
137
|
-
# and returns a component that renders the template.
|
|
138
107
|
if preview_class.respond_to?(:preview_example)
|
|
139
108
|
result = preview_class.preview_example(example_name)
|
|
140
|
-
puts "DEBUG: preview_example('#{example_name}') returned: #{result.inspect} (class: #{result.class})"
|
|
141
109
|
else
|
|
142
|
-
# Fallback for older VC or non-standard setups
|
|
143
110
|
preview_instance = preview_class.new
|
|
144
|
-
unless preview_instance.respond_to?(example_name)
|
|
145
|
-
return CheckResult.new(preview_name: preview.name, example_name: example_name,
|
|
146
|
-
status: :passed)
|
|
147
|
-
end
|
|
111
|
+
return CheckResult.new(preview_name: preview.name, example_name: example_name, status: :passed) unless preview_instance.respond_to?(example_name)
|
|
148
112
|
result = preview_instance.public_send(example_name)
|
|
149
113
|
end
|
|
150
114
|
|
|
151
|
-
if result.
|
|
152
|
-
# Mock current_user/pundit if needed on the component itself if possible
|
|
153
|
-
# But mainly we render it with a view context
|
|
154
|
-
view_context = setup_view_context
|
|
115
|
+
result = result[:component] if result.is_a?(Hash) && result.key?(:component)
|
|
155
116
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if @mocks
|
|
159
|
-
@mocks.each do |key, value|
|
|
160
|
-
if result.respond_to?(key) || !result.respond_to?(key) # Force define
|
|
161
|
-
result.define_singleton_method(key) { value }
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
output = result.render_in(view_context)
|
|
117
|
+
if result.respond_to?(:render_in)
|
|
118
|
+
output = result.render_in(build_view_context)
|
|
167
119
|
if output.is_a?(String) && output.include?('ActionView::Template::Error')
|
|
168
|
-
return CheckResult.new(preview_name: preview.name,
|
|
169
|
-
example_name:,
|
|
120
|
+
return CheckResult.new(preview_name: preview.name, example_name: example_name,
|
|
170
121
|
status: :failed, error: 'ActionView::Template::Error found in rendered output',
|
|
171
122
|
backtrace: [])
|
|
172
123
|
end
|
|
173
124
|
elsif result.is_a?(String)
|
|
174
125
|
if result.include?('ActionView::Template::Error')
|
|
175
|
-
return CheckResult.new(preview_name: preview.name,
|
|
176
|
-
example_name:,
|
|
126
|
+
return CheckResult.new(preview_name: preview.name, example_name: example_name,
|
|
177
127
|
status: :failed, error: 'ActionView::Template::Error found in rendered output',
|
|
178
128
|
backtrace: [])
|
|
179
129
|
end
|
|
180
|
-
# Rendered string, good.
|
|
181
130
|
elsif result.nil?
|
|
182
|
-
|
|
183
|
-
# We need to verify that the template exists.
|
|
184
|
-
|
|
185
|
-
# Try to find the template file based on conventions
|
|
186
|
-
# Convention: preview_file_dir/preview_file_name/example_name.html.erb
|
|
187
|
-
|
|
188
|
-
# We need the preview file path. Lookbook::Preview might not expose it easily
|
|
189
|
-
# but the class might have source_location.
|
|
190
|
-
|
|
191
|
-
method = preview_class.instance_method(example_name)
|
|
192
|
-
source_file = method.source_location&.first
|
|
193
|
-
|
|
194
|
-
found_template = false
|
|
195
|
-
if source_file
|
|
196
|
-
dir = File.dirname(source_file)
|
|
197
|
-
filename = File.basename(source_file, '.rb')
|
|
198
|
-
template_dir = File.join(dir, filename)
|
|
199
|
-
|
|
200
|
-
extensions = ['.html.erb', '.html.haml', '.html.slim']
|
|
201
|
-
path = File.join(template_dir, "#{example_name}")
|
|
202
|
-
|
|
203
|
-
found_template = extensions.any? { |ext| File.exist?("#{path}#{ext}") }
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
unless found_template
|
|
207
|
-
raise ViewComponent::MissingPreviewTemplateError.new("Preview #{example_name} returned nil and no template found at #{path}.* (checked erb, haml, slim)") if defined?(ViewComponent::MissingPreviewTemplateError)
|
|
208
|
-
|
|
209
|
-
raise "Preview returned nil and no template found at #{path}.*"
|
|
210
|
-
end
|
|
131
|
+
verify_implicit_template!(preview_class, example_name)
|
|
211
132
|
end
|
|
212
133
|
|
|
213
134
|
CheckResult.new(preview_name: preview.name, example_name: example_name, status: :passed)
|
|
@@ -217,126 +138,35 @@ module LookbookVisualTester
|
|
|
217
138
|
end
|
|
218
139
|
end
|
|
219
140
|
|
|
220
|
-
def
|
|
221
|
-
return unless defined?(Rails)
|
|
222
|
-
|
|
223
|
-
controller_name = Rails.application.config.view_component.preview_controller
|
|
224
|
-
return unless controller_name
|
|
225
|
-
|
|
226
|
-
begin
|
|
227
|
-
controller_class = controller_name.constantize
|
|
228
|
-
unless controller_class.action_methods.include?('render_scenario_to_string')
|
|
229
|
-
puts "WARNING: Configured preview controller '#{controller_name}' does not have 'render_scenario_to_string' action."
|
|
230
|
-
puts ' This is required for Lookbook to render previews correctly.'
|
|
231
|
-
puts " Please ensure your preview controller inherits from 'Lookbook::PreviewController'."
|
|
232
|
-
# We could raise an error here to fail deep_check
|
|
233
|
-
raise "Preview Controller '#{controller_name}' missing required action 'render_scenario_to_string'"
|
|
234
|
-
end
|
|
235
|
-
rescue NameError
|
|
236
|
-
puts "WARNING: Configured preview controller '#{controller_name}' could not be loaded."
|
|
237
|
-
raise "Preview Controller '#{controller_name}' could not be loaded"
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
def run_setup
|
|
242
|
-
if @config.preview_checker_setup
|
|
243
|
-
@config.preview_checker_setup.call
|
|
244
|
-
else
|
|
245
|
-
default_setup
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
def default_setup
|
|
250
|
-
# Logic from the user's script
|
|
251
|
-
# We need to set up a controller and view context globally or for use in checks
|
|
252
|
-
|
|
253
|
-
# This part is tricky because we need to make these available to the `deep_render_check` method.
|
|
254
|
-
# We can store the view_context in an instance variable or re-create it.
|
|
255
|
-
|
|
256
|
-
# Let's perform the class-level mocks here (User, etc.)
|
|
257
|
-
|
|
258
|
-
# Mock User
|
|
259
|
-
unless defined?(User)
|
|
260
|
-
# Defining a dummy user if not exists is risky if the app doesn't have User.
|
|
261
|
-
# But the script assumes User exists or creates a mock.
|
|
262
|
-
# Let's create a OpenStruct-like mock for typical Devise/Pundit usage.
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
# The script logic:
|
|
266
|
-
# controller = ApplicationController.new ...
|
|
267
|
-
# We will do this in `setup_view_context` called per check or once?
|
|
268
|
-
# `ApplicationController` might not be thread safe if we modify it?
|
|
269
|
-
# Actually we create a new controller instance.
|
|
270
|
-
|
|
271
|
-
# Define @mocks to be injected
|
|
272
|
-
@mocks = {}
|
|
273
|
-
@mocks[:current_user] = build_mock_user
|
|
274
|
-
@mocks[:pundit_user] = @mocks[:current_user]
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
def build_mock_user
|
|
278
|
-
# Try to load a real user or build a struct
|
|
279
|
-
if defined?(User) && User.respond_to?(:first) && User.first
|
|
280
|
-
User.first
|
|
281
|
-
else
|
|
282
|
-
# Fallback mock
|
|
283
|
-
u = Object.new
|
|
284
|
-
u.define_singleton_method(:email) { 'test@example.com' }
|
|
285
|
-
u.define_singleton_method(:id) { 1 }
|
|
286
|
-
# Add other common methods as needed or let them fail/mock dynamic
|
|
287
|
-
u
|
|
288
|
-
end
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
def setup_view_context
|
|
292
|
-
# We need a controller to get a view context
|
|
141
|
+
def build_view_context
|
|
293
142
|
controller = if defined?(ApplicationController)
|
|
294
143
|
ApplicationController.new
|
|
295
144
|
else
|
|
296
145
|
ActionController::Base.new
|
|
297
146
|
end
|
|
298
|
-
|
|
299
147
|
controller.request = ActionDispatch::TestRequest.create
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
controller.request.env['rack.session.options'] = { id: SecureRandom.uuid }
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
# Devise mapping
|
|
306
|
-
if defined?(Devise)
|
|
307
|
-
controller.request.env['devise.mapping'] = Devise.mappings[:user] if Devise.mappings[:user]
|
|
308
|
-
|
|
309
|
-
# Mock warden
|
|
310
|
-
warden = Object.new
|
|
311
|
-
warden.define_singleton_method(:authenticate!) { |*| true }
|
|
312
|
-
warden.define_singleton_method(:authenticate) { |*| true }
|
|
313
|
-
warden.define_singleton_method(:user) { |*| @mocks[:current_user] }
|
|
314
|
-
controller.request.env['warden'] = warden
|
|
315
|
-
end
|
|
148
|
+
controller.view_context
|
|
149
|
+
end
|
|
316
150
|
|
|
317
|
-
|
|
151
|
+
def verify_implicit_template!(preview_class, example_name)
|
|
152
|
+
method = preview_class.instance_method(example_name)
|
|
153
|
+
source_file = method&.source_location&.first
|
|
154
|
+
return if source_file.nil?
|
|
318
155
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
156
|
+
dir = File.dirname(source_file)
|
|
157
|
+
filename = File.basename(source_file, '.rb')
|
|
158
|
+
template_dir = File.join(dir, filename)
|
|
322
159
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
vc_singleton.send(:define_method, key) { value }
|
|
326
|
-
end
|
|
327
|
-
end
|
|
160
|
+
extensions = ['.html.erb', '.html.haml', '.html.slim']
|
|
161
|
+
path = File.join(template_dir, example_name)
|
|
328
162
|
|
|
329
|
-
|
|
330
|
-
vc_singleton.send(:define_method, :signed_in?) { true }
|
|
163
|
+
return if extensions.any? { |ext| File.exist?("#{path}#{ext}") }
|
|
331
164
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
true, true, true, true, true, true, true, true
|
|
336
|
-
)
|
|
337
|
-
end
|
|
165
|
+
raise ViewComponent::MissingPreviewTemplateError.new(
|
|
166
|
+
"Preview #{example_name} returned nil and no template found at #{path}.* (checked erb, haml, slim)"
|
|
167
|
+
) if defined?(ViewComponent::MissingPreviewTemplateError)
|
|
338
168
|
|
|
339
|
-
|
|
169
|
+
raise "Preview returned nil and no template found at #{path}.*"
|
|
340
170
|
end
|
|
341
171
|
end
|
|
342
172
|
end
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# lib/lookbook_visual_tester/railtie.rb
|
|
2
2
|
|
|
3
|
-
# require 'lookbook_visual_tester/capybara_setup'
|
|
4
|
-
require 'lookbook_visual_tester/update_previews'
|
|
5
|
-
|
|
6
3
|
module LookbookVisualTester
|
|
7
4
|
class Railtie < ::Rails::Railtie
|
|
8
5
|
rake_tasks do
|
|
@@ -12,25 +9,22 @@ module LookbookVisualTester
|
|
|
12
9
|
|
|
13
10
|
initializer 'LookbookVisualTester.lookbook_after_change' do |_app|
|
|
14
11
|
Rails.logger.info "LookbookVisualTester initialized with host: #{LookbookVisualTester.config.lookbook_host}"
|
|
15
|
-
Lookbook.after_change do |
|
|
12
|
+
Lookbook.after_change do |_app, changes|
|
|
16
13
|
next unless LookbookVisualTester.config.automatic_run
|
|
17
14
|
|
|
18
|
-
# get hash of content of modified files to see if has changed
|
|
19
15
|
modified = changes[:modified]
|
|
20
16
|
my_hash = modified.sort.map { |f| File.read(f) }.hash
|
|
21
17
|
|
|
22
18
|
lock_file = Rails.root.join('tmp', 'lookbook_visual_tester.lock')
|
|
23
19
|
Rails.logger.info ">>> LookbookVisualTester: No changes detected in #{LookbookVisualTester.data}"
|
|
24
20
|
|
|
25
|
-
# Rails.logger.info ">>> LookbookVisualTester: Stack trace: #{caller.join("\n")}"
|
|
26
|
-
Rails.logger.info ">>> LookbookVisualTester: Changes in #{Rails.cache.instance_variable_get(:@data).keys.inspect}"
|
|
27
21
|
File.open(lock_file, 'w') do |file|
|
|
28
22
|
if file.flock(File::LOCK_EX | File::LOCK_NB)
|
|
29
23
|
if LookbookVisualTester.data[:last_hash] == my_hash
|
|
30
24
|
Rails.logger.info 'LookbookVisualTester: No changes detected in Lookbook'
|
|
31
25
|
else
|
|
32
26
|
LookbookVisualTester.data[:last_hash] = my_hash
|
|
33
|
-
Rails.logger.info "LookbookVisualTester: Running UpdatePreviews,
|
|
27
|
+
Rails.logger.info "LookbookVisualTester: Running UpdatePreviews, updating to #{LookbookVisualTester.data.inspect}"
|
|
34
28
|
LookbookVisualTester::UpdatePreviews.call(changes)
|
|
35
29
|
end
|
|
36
30
|
file.flock(File::LOCK_UN)
|