panda-core 0.9.3 → 0.9.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e7863be64e4e612a42759b596547772fdc8ea9f50d85dc57b6c879328cbb8fa
4
- data.tar.gz: 14c99c7ad8e4dd48c69aecf9b958ab9201e77e5159affcdc3bba8bb5a6608998
3
+ metadata.gz: '04695145fc8e1f8c8bdd13d1ee35d91e1a3eb470070d265463f72e97f9ddd19c'
4
+ data.tar.gz: 509bd365e3b501f2f34d0add9617f29b757dcb1b2ca6db6d65c6f1dccfc2142f
5
5
  SHA512:
6
- metadata.gz: 337d68b1a7c03cedfdcea65f5978d13dc3cd6692d79188dc0132e4fb55a781d3b4c0991a23582e9fae585c88fa58ea314401f1a0cc5a9bc5059617d385536753
7
- data.tar.gz: 5be39c37ddf7d54f51071bc2486fd33b81e1be1bc1176358c890180de86676ce7848d89b426ecad80663e7a72e371de164fb71b1a3bd82c44e6decd09519bf57
6
+ metadata.gz: 3187a5adef002caceeda919102eb8f281aad08058bdc53a10be6297fcc78edaeecade4cdd6ead4246d6596f4fdbd1d37c44d1a2b280bb91023de20b6dc9a75c9
7
+ data.tar.gz: 6b7ee0a2d2dc9fcfd4c35bde5217a6390d700085dfdb52417189e702c9906d446d5a8d4f89efb21db8c14bd38962a4f23a7c4748b797f7cc235db349b6d7c7c8
@@ -248,6 +248,16 @@ module Panda
248
248
  options[:src] = source.start_with?("/") ? source : "/assets/#{source}"
249
249
  content_tag(:script, "", options)
250
250
  end
251
+
252
+ def self.last_dummy_asset_report
253
+ # Only expose this in test/CI/development — never in production
254
+ return nil unless Rails.env.test? || ENV["CI"].present?
255
+
256
+ if defined?(Panda::Core::Assets::ReportRegistry) &&
257
+ Panda::Core::Assets::ReportRegistry.present?
258
+ Panda::Core::Assets::ReportRegistry.last
259
+ end
260
+ end
251
261
  end
252
262
  end
253
263
  end
@@ -92,11 +92,7 @@ module Panda
92
92
 
93
93
  # Compile CSS with timestamp
94
94
  require "open3"
95
- require "fileutils"
96
95
 
97
- FileUtils.mkdir_p(assets_dir)
98
-
99
- # Get content paths from ModuleRegistry
100
96
  content_paths = Panda::Core::ModuleRegistry.tailwind_content_paths
101
97
  content_flags = content_paths.map { |path| "--content '#{path}'" }.join(" ")
102
98
 
@@ -306,15 +306,15 @@ module Panda
306
306
  file_path = find_javascript_file(relative_path)
307
307
 
308
308
  if file_path && File.file?(file_path)
309
- puts "[JavaScriptMiddleware] Serving: #{path} from #{file_path}"
309
+ puts "[JavaScriptMiddleware] Serving: #{path} from #{file_path}" if ENV["RSPEC_DEBUG"]
310
310
  serve_file(file_path, env)
311
311
  else
312
- puts "[JavaScriptMiddleware] Not found: #{path} (tried #{relative_path})"
312
+ puts "[JavaScriptMiddleware] Not found: #{path} (tried #{relative_path})" if ENV["RSPEC_DEBUG"]
313
313
  @app.call(env)
314
314
  end
315
315
  rescue => e
316
316
  # On error, log and pass to next middleware
317
- puts "[JavaScriptMiddleware] Error: #{e.message}"
317
+ puts "[JavaScriptMiddleware] Error: #{e.message}" if ENV["RSPEC_DEBUG"]
318
318
  Rails.logger.error("[ModuleRegistry::JavaScriptMiddleware] Error: #{e.message}\n#{e.backtrace.join("\n")}") if defined?(Rails.logger)
319
319
  @app.call(env)
320
320
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Capture and log browser console output for debugging CI failures
4
+ module BrowserConsoleLogger
5
+ def self.included(base)
6
+ base.after do |example|
7
+ # Only log console in CI when test fails
8
+ next unless ENV["CI"] && example.exception
9
+
10
+ if respond_to?(:page) && page.driver.is_a?(Capybara::Cuprite::Driver)
11
+ begin
12
+ console_logs = page.driver.browser.console_messages
13
+
14
+ if console_logs.any?
15
+ puts "\n" + "=" * 80
16
+ puts "BROWSER CONSOLE OUTPUT (#{console_logs.length} messages)"
17
+ puts "=" * 80
18
+
19
+ console_logs.each_with_index do |msg, index|
20
+ type_icon = case msg["type"]
21
+ when "error" then "❌"
22
+ when "warning" then "⚠️"
23
+ when "info" then "ℹ️"
24
+ else "📝"
25
+ end
26
+
27
+ puts "#{index + 1}. [#{msg["type"].upcase}] #{type_icon}"
28
+ puts " Message: #{msg["message"]}"
29
+ puts " Source: #{msg["source"]}" if msg["source"]
30
+ puts " Line: #{msg["line"]}" if msg["line"]
31
+ puts ""
32
+ end
33
+
34
+ puts "=" * 80
35
+ else
36
+ puts "\n⚠️ No console messages captured (browser may not have started)"
37
+ end
38
+ rescue => e
39
+ puts "\n⚠️ Failed to capture console logs: #{e.message}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ # Include in all system tests
47
+ RSpec.configure do |config|
48
+ config.include BrowserConsoleLogger, type: :system
49
+ end
@@ -32,6 +32,8 @@ Capybara.singleton_class.prepend(Module.new do
32
32
  end)
33
33
 
34
34
  # Configure server host and port
35
+ # This is loaded for all environments
36
+ # → you do not want fixed ports there
35
37
  Capybara.server_host = "127.0.0.1"
36
38
  Capybara.server_port = ENV["CAPYBARA_PORT"]&.to_i # Let Capybara choose if not specified
37
39
 
@@ -15,11 +15,17 @@ RSpec.configure do |config|
15
15
  config.before(:suite) do
16
16
  Capybara.server = :puma_ci
17
17
  Capybara.default_max_wait_time = Integer(ENV.fetch("CAPYBARA_MAX_WAIT_TIME", 5))
18
+
19
+ port = Integer(ENV.fetch("CAPYBARA_PORT", 3001))
18
20
  Capybara.server_host = "127.0.0.1"
21
+ Capybara.server_port = port
22
+ Capybara.app_host = "http://127.0.0.1:#{port}"
19
23
  Capybara.always_include_port = true
20
24
 
21
25
  puts "[CI Config] Capybara.server = #{Capybara.server.inspect}"
26
+ puts "[CI Config] Capybara.app_host = #{Capybara.app_host.inspect}"
22
27
  puts "[CI Config] Capybara.server_host = #{Capybara.server_host.inspect}"
28
+ puts "[CI Config] Capybara.server_port = #{Capybara.server_port.inspect}"
23
29
  puts "[CI Config] Capybara.max_wait = #{Capybara.default_max_wait_time}s"
24
30
  end
25
31
  end
@@ -35,8 +41,8 @@ Capybara.register_server :puma_ci do |app, port, host|
35
41
  Port: port,
36
42
  Threads: "#{min_threads}:#{max_threads}",
37
43
  Workers: 0,
38
- Silent: false,
39
- Verbose: true,
44
+ Silent: !ENV["RSPEC_DEBUG"],
45
+ Verbose: ENV["RSPEC_DEBUG"],
40
46
  PreloadApp: false
41
47
  }
42
48
 
@@ -19,15 +19,18 @@ module Panda
19
19
  module CupriteSetup
20
20
  # Base Cuprite options shared across all drivers
21
21
  def self.base_options
22
+ default_timeout = 2
23
+ default_process_timeout = 2
24
+
22
25
  {
23
26
  window_size: [1440, 1000],
24
27
  inspector: ENV["INSPECTOR"].in?(%w[y 1 yes true]),
25
28
  headless: !ENV["HEADLESS"].in?(%w[n 0 no false]),
26
29
  slowmo: ENV["SLOWMO"]&.to_f || 0,
27
- timeout: ENV["CUPRITE_TIMEOUT"]&.to_i || 2,
30
+ timeout: ENV["CUPRITE_TIMEOUT"]&.to_i || default_timeout,
28
31
  js_errors: true, # IMPORTANT: Report JavaScript errors as test failures
29
32
  ignore_default_browser_options: false,
30
- process_timeout: ENV["CUPRITE_PROCESS_TIMEOUT"]&.to_i || 2,
33
+ process_timeout: ENV["CUPRITE_PROCESS_TIMEOUT"]&.to_i || default_process_timeout,
31
34
  wait_for_network_idle: false, # Don't wait for all network requests
32
35
  pending_connection_errors: false, # Don't fail on pending external connections
33
36
  browser_options: {
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Panda
4
4
  module Core
5
- VERSION = "0.9.3"
5
+ # Version 0.9.4 - CI fixes
6
+ VERSION = "0.9.4"
6
7
  end
7
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: panda-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3
4
+ version: 0.9.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Otaina Limited
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-11-13 00:00:00.000000000 Z
12
+ date: 2025-11-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: image_processing
@@ -194,7 +194,21 @@ dependencies:
194
194
  - !ruby/object:Gem::Version
195
195
  version: '0'
196
196
  - !ruby/object:Gem::Dependency
197
- name: rspec-rails
197
+ name: webrick
198
+ requirement: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ type: :runtime
204
+ prerelease: false
205
+ version_requirements: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ version: '0'
210
+ - !ruby/object:Gem::Dependency
211
+ name: benchmark
198
212
  requirement: !ruby/object:Gem::Requirement
199
213
  requirements:
200
214
  - - ">="
@@ -208,7 +222,7 @@ dependencies:
208
222
  - !ruby/object:Gem::Version
209
223
  version: '0'
210
224
  - !ruby/object:Gem::Dependency
211
- name: rails-controller-testing
225
+ name: rspec-rails
212
226
  requirement: !ruby/object:Gem::Requirement
213
227
  requirements:
214
228
  - - ">="
@@ -222,7 +236,7 @@ dependencies:
222
236
  - !ruby/object:Gem::Version
223
237
  version: '0'
224
238
  - !ruby/object:Gem::Dependency
225
- name: capybara
239
+ name: rails-controller-testing
226
240
  requirement: !ruby/object:Gem::Requirement
227
241
  requirements:
228
242
  - - ">="
@@ -236,7 +250,7 @@ dependencies:
236
250
  - !ruby/object:Gem::Version
237
251
  version: '0'
238
252
  - !ruby/object:Gem::Dependency
239
- name: cuprite
253
+ name: capybara
240
254
  requirement: !ruby/object:Gem::Requirement
241
255
  requirements:
242
256
  - - ">="
@@ -250,7 +264,7 @@ dependencies:
250
264
  - !ruby/object:Gem::Version
251
265
  version: '0'
252
266
  - !ruby/object:Gem::Dependency
253
- name: database_cleaner-active_record
267
+ name: cuprite
254
268
  requirement: !ruby/object:Gem::Requirement
255
269
  requirements:
256
270
  - - ">="
@@ -264,7 +278,7 @@ dependencies:
264
278
  - !ruby/object:Gem::Version
265
279
  version: '0'
266
280
  - !ruby/object:Gem::Dependency
267
- name: shoulda-matchers
281
+ name: database_cleaner-active_record
268
282
  requirement: !ruby/object:Gem::Requirement
269
283
  requirements:
270
284
  - - ">="
@@ -278,7 +292,7 @@ dependencies:
278
292
  - !ruby/object:Gem::Version
279
293
  version: '0'
280
294
  - !ruby/object:Gem::Dependency
281
- name: simplecov
295
+ name: shoulda-matchers
282
296
  requirement: !ruby/object:Gem::Requirement
283
297
  requirements:
284
298
  - - ">="
@@ -292,7 +306,7 @@ dependencies:
292
306
  - !ruby/object:Gem::Version
293
307
  version: '0'
294
308
  - !ruby/object:Gem::Dependency
295
- name: simplecov-json
309
+ name: simplecov
296
310
  requirement: !ruby/object:Gem::Requirement
297
311
  requirements:
298
312
  - - ">="
@@ -306,7 +320,7 @@ dependencies:
306
320
  - !ruby/object:Gem::Version
307
321
  version: '0'
308
322
  - !ruby/object:Gem::Dependency
309
- name: standard
323
+ name: simplecov-json
310
324
  requirement: !ruby/object:Gem::Requirement
311
325
  requirements:
312
326
  - - ">="
@@ -320,7 +334,7 @@ dependencies:
320
334
  - !ruby/object:Gem::Version
321
335
  version: '0'
322
336
  - !ruby/object:Gem::Dependency
323
- name: brakeman
337
+ name: standard
324
338
  requirement: !ruby/object:Gem::Requirement
325
339
  requirements:
326
340
  - - ">="
@@ -334,7 +348,7 @@ dependencies:
334
348
  - !ruby/object:Gem::Version
335
349
  version: '0'
336
350
  - !ruby/object:Gem::Dependency
337
- name: bundler-audit
351
+ name: brakeman
338
352
  requirement: !ruby/object:Gem::Requirement
339
353
  requirements:
340
354
  - - ">="
@@ -348,7 +362,7 @@ dependencies:
348
362
  - !ruby/object:Gem::Version
349
363
  version: '0'
350
364
  - !ruby/object:Gem::Dependency
351
- name: yamllint
365
+ name: bundler-audit
352
366
  requirement: !ruby/object:Gem::Requirement
353
367
  requirements:
354
368
  - - ">="
@@ -502,20 +516,19 @@ files:
502
516
  - lib/panda/core/testing/rails_helper.rb
503
517
  - lib/panda/core/testing/support/authentication_helpers.rb
504
518
  - lib/panda/core/testing/support/authentication_test_helpers.rb
505
- - lib/panda/core/testing/support/generator_spec_helper.rb
506
519
  - lib/panda/core/testing/support/html_helpers.rb
507
520
  - lib/panda/core/testing/support/omniauth_setup.rb
508
521
  - lib/panda/core/testing/support/service_stubs.rb
509
522
  - lib/panda/core/testing/support/setup.rb
510
523
  - lib/panda/core/testing/support/system/better_system_tests.rb
511
- - lib/panda/core/testing/support/system/capybara_setup.rb
524
+ - lib/panda/core/testing/support/system/browser_console_logger.rb
525
+ - lib/panda/core/testing/support/system/capybara_config.rb
512
526
  - lib/panda/core/testing/support/system/ci_capybara_config.rb
513
527
  - lib/panda/core/testing/support/system/cuprite_helpers.rb
514
528
  - lib/panda/core/testing/support/system/cuprite_setup.rb
515
529
  - lib/panda/core/testing/support/system/database_connection_helpers.rb
516
530
  - lib/panda/core/testing/support/system/system_test_helpers.rb
517
531
  - lib/panda/core/version.rb
518
- - lib/tasks/assets.rake
519
532
  - lib/tasks/panda/core/migrations.rake
520
533
  - lib/tasks/panda_core.rake
521
534
  - lib/tasks/panda_core_tasks.rake
@@ -1,97 +0,0 @@
1
- require "rails/generators"
2
-
3
- module GeneratorSpecHelper
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- before(:each) do
8
- prepare_destination
9
- @original_stdout = $stdout
10
- $stdout = File.new(File::NULL, "w")
11
- end
12
-
13
- after(:each) do
14
- FileUtils.rm_rf(destination_root)
15
- $stdout = @original_stdout
16
- end
17
- end
18
-
19
- def destination_root
20
- @destination_root ||= File.expand_path("../../tmp/generators", __dir__)
21
- end
22
-
23
- def prepare_destination
24
- FileUtils.rm_rf(destination_root)
25
- FileUtils.mkdir_p(destination_root)
26
- end
27
-
28
- def run_generator(args = [])
29
- args = Array(args)
30
- # Use the generator namespace instead of class name
31
- generator_name = described_class.namespace
32
- Rails::Generators.invoke(generator_name, args, destination_root: destination_root)
33
- end
34
-
35
- def generator
36
- @generator ||= described_class.new([], destination_root: destination_root)
37
- end
38
-
39
- def file_exists?(path)
40
- File.exist?(File.join(destination_root, path))
41
- end
42
-
43
- def read_file(path)
44
- File.read(File.join(destination_root, path))
45
- end
46
-
47
- private
48
-
49
- def capture(stream)
50
- stream = stream.to_s
51
- captured_stream = StringIO.new
52
-
53
- # Map stream names to their global variables to avoid eval
54
- streams = {
55
- "stdout" => $stdout,
56
- "stderr" => $stderr,
57
- "stdin" => $stdin
58
- }
59
-
60
- original_stream = streams[stream]
61
- case stream
62
- when "stdout"
63
- $stdout = captured_stream
64
- when "stderr"
65
- $stderr = captured_stream
66
- when "stdin"
67
- $stdin = captured_stream
68
- else
69
- raise ArgumentError, "Unsupported stream: #{stream}"
70
- end
71
-
72
- yield
73
- captured_stream.string
74
- ensure
75
- case stream
76
- when "stdout"
77
- $stdout = original_stream
78
- when "stderr"
79
- $stderr = original_stream
80
- when "stdin"
81
- $stdin = original_stream
82
- end
83
- end
84
- end
85
-
86
- RSpec.configure do |config|
87
- config.include GeneratorSpecHelper, type: :generator
88
-
89
- # Ensure generator tests have a clean environment
90
- config.before(:each, type: :generator) do
91
- prepare_destination
92
- end
93
-
94
- config.after(:each, type: :generator) do
95
- FileUtils.rm_rf(destination_root) if defined?(destination_root)
96
- end
97
- end
@@ -1,159 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Panda Core Asset Tasks
5
- #
6
-
7
- namespace :panda do
8
- namespace :core do
9
- namespace :assets do
10
- # =========================================================
11
- # 1) ENGINE CSS COMPILATION (unchanged, just cleaned)
12
- # =========================================================
13
-
14
- desc "Compile Panda Core CSS assets (development mode)"
15
- task :compile do
16
- puts "🐼 Compiling Panda Core CSS assets..."
17
-
18
- output_dir = panda_engine_root.join("public", "panda-core-assets")
19
- FileUtils.mkdir_p(output_dir)
20
-
21
- compile_css_development(output_dir)
22
-
23
- puts "🎉 CSS compiled → #{output_dir}"
24
- end
25
-
26
- desc "Compile and version Panda Core CSS assets for release"
27
- task :release do
28
- require_relative "../../panda/core/version"
29
-
30
- version = Panda::Core::VERSION
31
- output_dir = panda_engine_root.join("public", "panda-core-assets")
32
- FileUtils.mkdir_p(output_dir)
33
-
34
- compile_css_release(output_dir, version)
35
-
36
- puts "🎉 Release assets compiled"
37
- end
38
-
39
- # =========================================================
40
- # CSS INTERNAL HELPERS
41
- # =========================================================
42
-
43
- def panda_engine_root
44
- Panda::Core::Engine.root
45
- end
46
-
47
- def compile_css_development(output_dir)
48
- input = panda_engine_root.join("app/assets/tailwind/application.css")
49
- output = output_dir.join("panda-core.css")
50
-
51
- content = Panda::Core::ModuleRegistry.tailwind_content_paths
52
- flags = content.map { |p| "--content '#{p}'" }.join(" ")
53
-
54
- cmd = "bundle exec tailwindcss -i #{input} -o #{output} #{flags} --minify"
55
- abort("❌ CSS compile failed") unless system(cmd)
56
-
57
- puts " ✓ #{output.basename} (#{File.size(output)} bytes)"
58
- end
59
-
60
- def compile_css_release(output_dir, version)
61
- input = panda_engine_root.join("app/assets/tailwind/application.css")
62
- file = output_dir.join("panda-core-#{version}.css")
63
-
64
- content = Panda::Core::ModuleRegistry.tailwind_content_paths
65
- flags = content.map { |p| "--content '#{p}'" }.join(" ")
66
-
67
- cmd = "bundle exec tailwindcss -i #{input} -o #{file} #{flags} --minify"
68
- abort("❌ CSS release compile failed") unless system(cmd)
69
-
70
- symlink = output_dir.join("panda-core.css")
71
- FileUtils.rm_f(symlink)
72
- FileUtils.ln_sf(file.basename, symlink)
73
-
74
- puts " ✓ Versioned file + symlink created"
75
- end
76
-
77
- # =========================================================
78
- # 2) DUMMY APP PREP (FOR SYSTEM TESTS + CI)
79
- # =========================================================
80
-
81
- desc "Compile Panda Core + dummy app assets into spec/dummy/public/assets"
82
- task compile_dummy: :environment do
83
- dummy_root = find_dummy_root
84
- assets_root = dummy_root.join("public/assets")
85
-
86
- puts "🚧 Compiling dummy assets..."
87
- puts " → #{assets_root}"
88
- FileUtils.mkdir_p(assets_root)
89
-
90
- Dir.chdir(dummy_root) do
91
- # IMPORTANT: this is now the correct task name
92
- unless system("bundle exec rake panda:core:assets:compile")
93
- abort("❌ panda:core:assets:compile failed in dummy app")
94
- end
95
-
96
- # Propshaft (Rails 8)
97
- abort("❌ assets:precompile failed") \
98
- unless system("bundle exec rake assets:precompile RAILS_ENV=#{Rails.env}")
99
- end
100
-
101
- puts "✅ Dummy assets compiled"
102
- end
103
-
104
- desc "Generate importmap.json for the dummy app"
105
- task generate_dummy_importmap: :environment do
106
- dummy_root = find_dummy_root
107
- output = dummy_root.join("public/assets/importmap.json")
108
-
109
- puts "🗺️ Generating importmap.json..."
110
- FileUtils.mkdir_p(output.dirname)
111
-
112
- Dir.chdir(dummy_root) do
113
- json = Rails.application.importmap.to_json(
114
- resolver: ActionController::Base.helpers
115
- )
116
- File.write(output, JSON.pretty_generate(json))
117
- end
118
-
119
- puts " ✓ importmap.json written"
120
- end
121
-
122
- desc "Verify dummy assets for CI (fail-fast)"
123
- task verify_dummy: :environment do
124
- dummy_root = find_dummy_root
125
- assets = dummy_root.join("public/assets")
126
- manifest = assets.join(".manifest.json")
127
- importmap = assets.join("importmap.json")
128
-
129
- abort("❌ Missing #{assets}") unless assets.exist?
130
- abort("❌ Missing #{manifest}") unless manifest.exist?
131
- abort("❌ Missing #{importmap}") unless importmap.exist?
132
-
133
- begin
134
- parsed = JSON.parse(File.read(manifest))
135
- abort("❌ Empty .manifest.json") if parsed.empty?
136
- rescue
137
- abort("❌ Invalid .manifest.json")
138
- end
139
-
140
- puts "✅ Dummy assets verified"
141
- end
142
-
143
- # =========================================================
144
- # INTERNAL UTILITIES
145
- # =========================================================
146
-
147
- def find_dummy_root
148
- root = Rails.root
149
-
150
- return root if root.basename.to_s == "dummy"
151
-
152
- candidate = root.join("spec/dummy")
153
- return candidate if candidate.exist?
154
-
155
- abort("❌ Cannot find dummy root — expected #{candidate}")
156
- end
157
- end
158
- end
159
- end