Class: SnapDiff::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/snap_diff/config.rb

Overview

Flat consolidation of every legacy Capybara::Screenshot / Capybara::Screenshot::Diff setting behind one object: SnapDiff.config.attr.

Storage ownership (ADR-008 step 1, inverted from the original v2 consolidation): Config IS the single storage. The legacy accessors on Capybara::Screenshot / Capybara::Screenshot::Diff are thin delegators generated by snap_diff/legacy_shims from its CONFIG_MAPPING -- one storage, two views, so a write through either surface is visible through the other structurally, not by synchronization.

Default timing contract (pinned by config_default_timing_test.rb): every stored default below is evaluated ONCE, in #initialize, which runs at require time of this file (the eager Config.new at the bottom) -- the same load moment the old mattr_accessor default blocks evaluated at. In particular root (from Rails.root / pwd) must never become a lazy read-time default, memoized or not.

Two values are deliberately LIVE and are not storage at all: default_options[:wait] stays a method-body read of Capybara.default_max_wait_time in #default_options, and fail_if_new falls back to ENV in its reader whenever nothing explicit was set -- see #fail_if_new for why freezing that sniff into storage let the environment outrank the user.

Constant Summary collapse

SETTINGS =

Every setting this object stores, in the order the two legacy holders used to declare them.

screenshot_enabled is the one name that differs from its legacy spelling: Capybara::Screenshot.enabled and Capybara::Screenshot::Diff.enabled are independent settings (see #active?, which reads both) that happened to share a bare name in their own modules. A flat Config can't expose two attributes both called enabled, so the Screenshot-side one is renamed here; Diff's keeps the bare enabled name since it's the one most existing configuration touches directly.

%i[
  add_driver_path
  add_os_path
  blur_active_element
  screenshot_enabled
  hide_caret
  disable_animations
  root
  stability_time_limit
  window_size
  save_path
  use_lfs
  screenshot_format
  capybara_screenshot_options
  delayed
  area_size_limit
  record
  fail_if_new
  pending_if_new
  fail_on_difference
  color_distance_limit
  enabled
  shift_distance_limit
  skip_area
  driver
  tolerance
  perceptual_threshold
  screenshoter
  manager
].freeze
RECORD_MODES =

The record modes, in the order they escalate: do nothing new, refuse to record, record everything. See #record.

%i[once none all].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfig

Returns a new instance of Config.



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/snap_diff/config.rb', line 115

def initialize
  # Every setting gets its ivar up front (nil-defaulted ones included)
  # so the full set always exists -- test_helper's per-test isolation
  # snapshots/restores config by instance variable, and an ivar that
  # only appears on first write would escape that snapshot and leak
  # between tests.
  SETTINGS.each { |key| instance_variable_set(:"@#{key}", nil) }
  # Capybara::Screenshot side.
  @blur_active_element = true
  @hide_caret = true
  # Raw Rails.root (no coercion), matching the old mattr_reader default;
  # only the writer below coerces.
  @root = (defined?(Rails) && defined?(Rails.root) && Rails.root) || Pathname(".").expand_path
  @save_path = "doc/screenshots"
  @screenshot_format = "png"
  @capybara_screenshot_options = {}
  # Capybara::Screenshot::Diff side.
  @delayed = true
  # No stored default for fail_if_new on purpose -- see the reader.
  @pending_if_new = false
  @fail_on_difference = true
  @enabled = true
  @driver = :auto
  @screenshoter = SnapDiff::Screenshoter
  @manager = SnapDiff::SnapManager
end

Instance Attribute Details

#driverObject

Returns the value of attribute driver.



113
114
115
# File 'lib/snap_diff/config.rb', line 113

def driver
  @driver
end

#fail_on_differenceObject

Returns the value of attribute fail_on_difference.



113
114
115
# File 'lib/snap_diff/config.rb', line 113

def fail_on_difference
  @fail_on_difference
end

#pending_if_newObject

Returns the value of attribute pending_if_new.



113
114
115
# File 'lib/snap_diff/config.rb', line 113

def pending_if_new
  @pending_if_new
end

#rootObject

Returns the value of attribute root.



113
114
115
# File 'lib/snap_diff/config.rb', line 113

def root
  @root
end

#shift_distance_limitObject

Returns the value of attribute shift_distance_limit.



113
114
115
# File 'lib/snap_diff/config.rb', line 113

def shift_distance_limit
  @shift_distance_limit
end

Class Method Details

.validate_record_mode!(mode) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Shared by #record= and the per-screenshot record: option, which is resolved in ScreenshotMatcher. A misspelt mode must not read back as "nobody said" and silently mean today's behaviour -- that is the exact silent no-op ADR-010 exists to stop.

Raises:

  • (ArgumentError)


97
98
99
100
101
102
# File 'lib/snap_diff/config.rb', line 97

def self.validate_record_mode!(mode)
  return mode if mode.nil? || RECORD_MODES.include?(mode)

  raise ArgumentError,
    "unknown record mode #{mode.inspect} -- one of #{RECORD_MODES.map(&:inspect).join(", ")} (or nil)"
end

Instance Method Details

#active?Boolean

ex Capybara::Screenshot.active?. The two enabled settings are independent (see SETTINGS): the Screenshot-side one wins whenever it was set at all, and only a nil there falls through to the Diff-side one.

Returns:

  • (Boolean)


247
248
249
# File 'lib/snap_diff/config.rb', line 247

def active?
  screenshot_enabled || (screenshot_enabled.nil? && enabled)
end

#default_optionsObject

ex Capybara::Screenshot::Diff.default_options: the capture/compare defaults handed to SnapDiff::Comparison. Carries the one literal that is not a stored setting -- the vips tolerance floor.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/snap_diff/config.rb', line 268

def default_options
  {
    area_size_limit: area_size_limit,
    color_distance_limit: color_distance_limit,
    driver: driver,
    screenshot_format: screenshot_format,
    capybara_screenshot_options: capybara_screenshot_options,
    perceptual_threshold: perceptual_threshold,
    shift_distance_limit: shift_distance_limit,
    skip_area: skip_area,
    stability_time_limit: stability_time_limit,
    tolerance: tolerance || ((driver == :vips) ? 0.001 : nil),
    # Deliberately LIVE (pinned by config_default_timing_test.rb):
    # read at call time, never frozen into storage.
    wait: Capybara.default_max_wait_time
  }
end

#fail_if_newObject

An explicit setting outranks the environment. nil means nobody said, and only then does the CI sniff answer -- read live, so a CI variable that appears at any point is honoured, not just one that happened to be exported before the gem was required. Assigning nil hands the setting back to the environment.

This is a precedence rule, not a change of default: failing only under CI stays (a locally recorded baseline is often worthless across OS). Storing the sniff instead, as this did, made the two indistinguishable -- fail_if_new = false and "CI was absent at require time" were the same false, so the environment could win. Same fix as insta#924 ("normally, CLI flags take precedence over environment variables") and the inverse of jest#12288.



159
160
161
# File 'lib/snap_diff/config.rb', line 159

def fail_if_new
  @fail_if_new.nil? ? !ENV["CI"].to_s.empty? : @fail_if_new
end

#fail_if_new=(value) ⇒ Object

Announces the 2.1 removal. The writer, not the reader: #record reads fail_if_new on every screenshot for everyone, and #initialize stores no default at all, so only a user who sets it hears about it.



166
167
168
169
# File 'lib/snap_diff/config.rb', line 166

def fail_if_new=(value)
  Removal.warn_once(:fail_if_new, Removal::FAIL_IF_NEW_REMOVED)
  @fail_if_new = value
end

#recordObject

THE ACCEPT WORKFLOW (#259). What to do about a screenshot whose baseline is missing -- or, for :all, about every screenshot there is. VCR-shaped: modes in config, because there is no runner to hang a CLI flag on.

:once:: (default) record a screenshot that has no committed baseline; compare against the baseline when there is one. :none:: strict. A missing baseline always fails. :all:: re-record. Every screenshot is written as the new baseline and nothing is compared -- the bulk-accept verb, for the redesign that changed forty screenshots at once. Refused under CI (see ScreenshotMatcher).

PRECEDENCE: an explicitly set mode outranks fail_if_new; nil means nobody said, and only then does fail_if_new answer -- so a setup with no record line behaves EXACTLY as it did before this setting existed, CI sniff and all. :none is the mode spelling of fail_if_new = true and :once of = false, which is why the fallback can express today's behaviour without a special case anywhere downstream: the matcher branches on the mode alone.

The missing-baseline DEFAULT is deliberately unchanged -- failing only under CI is what Jest, AVA, Vitest, testthat and jest-image-snapshot all chose, and a locally recorded screenshot baseline is often worthless across OS. :none makes strictness an explicit choice instead; that is the whole point of having the mode.



212
213
214
# File 'lib/snap_diff/config.rb', line 212

def record
  @record || (fail_if_new ? :none : :once)
end

#record=(mode) ⇒ Object



216
217
218
# File 'lib/snap_diff/config.rb', line 216

def record=(mode)
  @record = Config.validate_record_mode!(mode)
end

#screenshot_areaObject

ex Capybara::Screenshot.screenshot_area: the save_path, optionally segmented per OS and per Capybara driver.



253
254
255
256
257
258
# File 'lib/snap_diff/config.rb', line 253

def screenshot_area
  parts = [save_path]
  parts << Os.name if add_os_path
  parts << Capybara.current_driver.to_s if add_driver_path
  File.join(*parts)
end

#screenshot_area_absObject

ex Capybara::Screenshot.screenshot_area_abs.



261
262
263
# File 'lib/snap_diff/config.rb', line 261

def screenshot_area_abs
  root / screenshot_area
end