lookbook_visual_tester 0.6.0 → 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 +26 -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 +41 -214
- 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,85 +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
109
|
else
|
|
141
|
-
# Fallback for older VC or non-standard setups
|
|
142
110
|
preview_instance = preview_class.new
|
|
143
|
-
unless preview_instance.respond_to?(example_name)
|
|
144
|
-
return CheckResult.new(preview_name: preview.name, example_name: example_name,
|
|
145
|
-
status: :passed)
|
|
146
|
-
end
|
|
111
|
+
return CheckResult.new(preview_name: preview.name, example_name: example_name, status: :passed) unless preview_instance.respond_to?(example_name)
|
|
147
112
|
result = preview_instance.public_send(example_name)
|
|
148
113
|
end
|
|
149
114
|
|
|
150
|
-
# Handle Hash return (ViewComponent 3.x+ behavior?)
|
|
151
115
|
result = result[:component] if result.is_a?(Hash) && result.key?(:component)
|
|
152
116
|
|
|
153
117
|
if result.respond_to?(:render_in)
|
|
154
|
-
|
|
155
|
-
# But mainly we render it with a view context
|
|
156
|
-
view_context = setup_view_context
|
|
157
|
-
|
|
158
|
-
# Inject mocks into result if it supports it or reliance on global view_context
|
|
159
|
-
# The user script defines singleton methods on the result.
|
|
160
|
-
if @mocks
|
|
161
|
-
@mocks.each do |key, value|
|
|
162
|
-
if result.respond_to?(key) || !result.respond_to?(key) # Force define
|
|
163
|
-
result.define_singleton_method(key) { value }
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
output = result.render_in(view_context)
|
|
169
|
-
|
|
118
|
+
output = result.render_in(build_view_context)
|
|
170
119
|
if output.is_a?(String) && output.include?('ActionView::Template::Error')
|
|
171
|
-
return CheckResult.new(preview_name: preview.name,
|
|
172
|
-
example_name: example_name,
|
|
120
|
+
return CheckResult.new(preview_name: preview.name, example_name: example_name,
|
|
173
121
|
status: :failed, error: 'ActionView::Template::Error found in rendered output',
|
|
174
122
|
backtrace: [])
|
|
175
123
|
end
|
|
176
124
|
elsif result.is_a?(String)
|
|
177
125
|
if result.include?('ActionView::Template::Error')
|
|
178
|
-
return CheckResult.new(preview_name: preview.name,
|
|
179
|
-
example_name: example_name,
|
|
126
|
+
return CheckResult.new(preview_name: preview.name, example_name: example_name,
|
|
180
127
|
status: :failed, error: 'ActionView::Template::Error found in rendered output',
|
|
181
128
|
backtrace: [])
|
|
182
129
|
end
|
|
183
|
-
# Rendered string, good.
|
|
184
130
|
elsif result.nil?
|
|
185
|
-
|
|
186
|
-
# We need to verify that the template exists.
|
|
187
|
-
|
|
188
|
-
# Try to find the template file based on conventions
|
|
189
|
-
# Convention: preview_file_dir/preview_file_name/example_name.html.erb
|
|
190
|
-
|
|
191
|
-
# We need the preview file path. Lookbook::Preview might not expose it easily
|
|
192
|
-
# but the class might have source_location.
|
|
193
|
-
|
|
194
|
-
method = preview_class.instance_method(example_name)
|
|
195
|
-
source_file = method.source_location&.first
|
|
196
|
-
|
|
197
|
-
found_template = false
|
|
198
|
-
if source_file
|
|
199
|
-
dir = File.dirname(source_file)
|
|
200
|
-
filename = File.basename(source_file, '.rb')
|
|
201
|
-
template_dir = File.join(dir, filename)
|
|
202
|
-
|
|
203
|
-
extensions = ['.html.erb', '.html.haml', '.html.slim']
|
|
204
|
-
path = File.join(template_dir, "#{example_name}")
|
|
205
|
-
|
|
206
|
-
found_template = extensions.any? { |ext| File.exist?("#{path}#{ext}") }
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
unless found_template
|
|
210
|
-
raise ViewComponent::MissingPreviewTemplateError.new("Preview #{example_name} returned nil and no template found at #{path}.* (checked erb, haml, slim)") if defined?(ViewComponent::MissingPreviewTemplateError)
|
|
211
|
-
|
|
212
|
-
raise "Preview returned nil and no template found at #{path}.*"
|
|
213
|
-
end
|
|
131
|
+
verify_implicit_template!(preview_class, example_name)
|
|
214
132
|
end
|
|
215
133
|
|
|
216
134
|
CheckResult.new(preview_name: preview.name, example_name: example_name, status: :passed)
|
|
@@ -220,126 +138,35 @@ module LookbookVisualTester
|
|
|
220
138
|
end
|
|
221
139
|
end
|
|
222
140
|
|
|
223
|
-
def
|
|
224
|
-
return unless defined?(Rails)
|
|
225
|
-
|
|
226
|
-
controller_name = Rails.application.config.view_component.preview_controller
|
|
227
|
-
return unless controller_name
|
|
228
|
-
|
|
229
|
-
begin
|
|
230
|
-
controller_class = controller_name.constantize
|
|
231
|
-
unless controller_class.action_methods.include?('render_scenario_to_string')
|
|
232
|
-
puts "WARNING: Configured preview controller '#{controller_name}' does not have 'render_scenario_to_string' action."
|
|
233
|
-
puts ' This is required for Lookbook to render previews correctly.'
|
|
234
|
-
puts " Please ensure your preview controller inherits from 'Lookbook::PreviewController'."
|
|
235
|
-
# We could raise an error here to fail deep_check
|
|
236
|
-
raise "Preview Controller '#{controller_name}' missing required action 'render_scenario_to_string'"
|
|
237
|
-
end
|
|
238
|
-
rescue NameError
|
|
239
|
-
puts "WARNING: Configured preview controller '#{controller_name}' could not be loaded."
|
|
240
|
-
raise "Preview Controller '#{controller_name}' could not be loaded"
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
def run_setup
|
|
245
|
-
if @config.preview_checker_setup
|
|
246
|
-
@config.preview_checker_setup.call
|
|
247
|
-
else
|
|
248
|
-
default_setup
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
def default_setup
|
|
253
|
-
# Logic from the user's script
|
|
254
|
-
# We need to set up a controller and view context globally or for use in checks
|
|
255
|
-
|
|
256
|
-
# This part is tricky because we need to make these available to the `deep_render_check` method.
|
|
257
|
-
# We can store the view_context in an instance variable or re-create it.
|
|
258
|
-
|
|
259
|
-
# Let's perform the class-level mocks here (User, etc.)
|
|
260
|
-
|
|
261
|
-
# Mock User
|
|
262
|
-
unless defined?(User)
|
|
263
|
-
# Defining a dummy user if not exists is risky if the app doesn't have User.
|
|
264
|
-
# But the script assumes User exists or creates a mock.
|
|
265
|
-
# Let's create a OpenStruct-like mock for typical Devise/Pundit usage.
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
# The script logic:
|
|
269
|
-
# controller = ApplicationController.new ...
|
|
270
|
-
# We will do this in `setup_view_context` called per check or once?
|
|
271
|
-
# `ApplicationController` might not be thread safe if we modify it?
|
|
272
|
-
# Actually we create a new controller instance.
|
|
273
|
-
|
|
274
|
-
# Define @mocks to be injected
|
|
275
|
-
@mocks = {}
|
|
276
|
-
@mocks[:current_user] = build_mock_user
|
|
277
|
-
@mocks[:pundit_user] = @mocks[:current_user]
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
def build_mock_user
|
|
281
|
-
# Try to load a real user or build a struct
|
|
282
|
-
if defined?(User) && User.respond_to?(:first) && User.first
|
|
283
|
-
User.first
|
|
284
|
-
else
|
|
285
|
-
# Fallback mock
|
|
286
|
-
u = Object.new
|
|
287
|
-
u.define_singleton_method(:email) { 'test@example.com' }
|
|
288
|
-
u.define_singleton_method(:id) { 1 }
|
|
289
|
-
# Add other common methods as needed or let them fail/mock dynamic
|
|
290
|
-
u
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
def setup_view_context
|
|
295
|
-
# We need a controller to get a view context
|
|
141
|
+
def build_view_context
|
|
296
142
|
controller = if defined?(ApplicationController)
|
|
297
143
|
ApplicationController.new
|
|
298
144
|
else
|
|
299
145
|
ActionController::Base.new
|
|
300
146
|
end
|
|
301
|
-
|
|
302
147
|
controller.request = ActionDispatch::TestRequest.create
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
controller.request.env['rack.session.options'] = { id: SecureRandom.uuid }
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
# Devise mapping
|
|
309
|
-
if defined?(Devise)
|
|
310
|
-
controller.request.env['devise.mapping'] = Devise.mappings[:user] if Devise.mappings[:user]
|
|
311
|
-
|
|
312
|
-
# Mock warden
|
|
313
|
-
warden = Object.new
|
|
314
|
-
warden.define_singleton_method(:authenticate!) { |*| true }
|
|
315
|
-
warden.define_singleton_method(:authenticate) { |*| true }
|
|
316
|
-
warden.define_singleton_method(:user) { |*| @mocks[:current_user] }
|
|
317
|
-
controller.request.env['warden'] = warden
|
|
318
|
-
end
|
|
148
|
+
controller.view_context
|
|
149
|
+
end
|
|
319
150
|
|
|
320
|
-
|
|
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?
|
|
321
155
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
156
|
+
dir = File.dirname(source_file)
|
|
157
|
+
filename = File.basename(source_file, '.rb')
|
|
158
|
+
template_dir = File.join(dir, filename)
|
|
325
159
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
vc_singleton.send(:define_method, key) { value }
|
|
329
|
-
end
|
|
330
|
-
end
|
|
160
|
+
extensions = ['.html.erb', '.html.haml', '.html.slim']
|
|
161
|
+
path = File.join(template_dir, example_name)
|
|
331
162
|
|
|
332
|
-
|
|
333
|
-
vc_singleton.send(:define_method, :signed_in?) { true }
|
|
163
|
+
return if extensions.any? { |ext| File.exist?("#{path}#{ext}") }
|
|
334
164
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
true, true, true, true, true, true, true, true
|
|
339
|
-
)
|
|
340
|
-
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)
|
|
341
168
|
|
|
342
|
-
|
|
169
|
+
raise "Preview returned nil and no template found at #{path}.*"
|
|
343
170
|
end
|
|
344
171
|
end
|
|
345
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)
|