Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed
- **The legacy API is removed in 2.1, not 3.0.** There is no 3.0. 2.0.0 final ships
*with* the full v1 compatibility layer — the `Capybara::Screenshot*` /
`CapybaraScreenshotDiff*` shims, the per-constant deprecation warnings and the
one-per-process migration notice — so 2.0 is a real migration window. 2.1 then
deletes all of it. Earlier entries below (and the beta migration notice) said
"3.0"; read every such mention as 2.1.
- **The ChunkyPNG driver and `shift_distance_limit` are also removed in 2.1.**
Both are deprecated in 2.0; VIPS becomes the only driver. Add `ruby-vips` and
install libvips before taking 2.1, and replace `shift_distance_limit` with
`median_filter_window_size`.

---

## [v2.0.0.beta3] - 2026-08-23

Fixes the canonical `SnapDiff` entry points, which were incomplete in beta2.
Expand Down
11 changes: 5 additions & 6 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ require "rake/testtask"

task default: :test

# THE 3.0 SPLIT.
# THE LEGACY-REMOVAL SPLIT.
#
# test/legacy/ holds every test whose SUBJECT is the v1 compatibility surface
# -- the old Capybara::Screenshot / CapybaraScreenshotDiff namespaces, their
# deprecation warnings, and the gates that keep lib/capybara* alias-only.
# Those tests guard the v1 contract for the whole 2.x line, so they stay and
# stay green; in 3.0 they are deleted by the same commit that deletes what
# they test:
# Those tests guard the v1 contract through 2.0, so they stay and stay green;
# in 2.1 they are deleted by the same commit that deletes what they test:
#
# git rm -r lib/capybara* lib/capybara_screenshot_diff.rb \
# lib/snap_diff/legacy_shims.rb lib/snap_diff/deprecation.rb \
Expand All @@ -23,7 +22,7 @@ task default: :test
#
# `rake test` -- everything, today's gate.
# `rake test:canonical` -- exactly what must still pass once test/legacy and
# the v1 trees are gone. THE 3.0 GATE.
# the v1 trees are gone. THE 2.1 GATE.
# `rake test:unit` -- unit-sized tests; test/legacy is unit-sized too
# (legacy/ marks lifetime, not kind), so it is in.
LEGACY_SURFACE_TESTS = "test/legacy/**/*_test.rb"
Expand All @@ -34,7 +33,7 @@ Rake::TestTask.new(:test) do |t|
t.test_files = FileList["test/**/*_test.rb"]
end

desc "Run every test that must survive the 3.0 deletion of the v1 surface"
desc "Run every test that must survive the 2.1 deletion of the v1 surface"
Rake::TestTask.new("test:canonical") do |t|
t.libs << "test"
t.libs << "lib"
Expand Down
12 changes: 8 additions & 4 deletions docs/UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,18 @@ This means you can migrate your codebase incrementally **now**, before opting in

### Deprecation Warnings

**The contract, plainly: every legacy name keeps working in 2.0 and warns. All of it is removed in
2.1** — the `Capybara::Screenshot*` / `CapybaraScreenshotDiff*` namespaces, the shims, and the
deprecation machinery itself. 2.0 is your migration window; do the rename before you take 2.1.

v2.0 emits two different things, and it is worth knowing which is which.

#### 1. The migration notice — one line per process

The first time a process touches *any* hookable legacy API, you get a single line:

```
[snap_diff deprecation] This process uses the v1 `Capybara::Screenshot*` / `CapybaraScreenshotDiff*` API. It still works in 2.x and is REMOVED in 3.0 -- see docs/UPGRADING.md for the SnapDiff replacements. Silence with `SnapDiff.silence_deprecations = true` or SNAP_DIFF_SILENCE_DEPRECATIONS=1. (shown once per process)
[snap_diff deprecation] This process uses the v1 `Capybara::Screenshot*` / `CapybaraScreenshotDiff*` API. It still works in 2.0 and is REMOVED in 2.1 -- see docs/UPGRADING.md for the SnapDiff replacements. Silence with `SnapDiff.silence_deprecations = true` or SNAP_DIFF_SILENCE_DEPRECATIONS=1. (shown once per process)
```

It fires once and never again, whichever door you came through:
Expand All @@ -175,8 +179,8 @@ It fires once and never again, whichever door you came through:
- a lazily shimmed legacy constant (see below)
- `include Capybara::Screenshot` / `include Capybara::Screenshot::Diff`

It exists because most of the v1 surface **cannot** warn per use, so without it a 2.x app could
be entirely silent right up to the bare `NameError` it would get on 3.0.
It exists because most of the v1 surface **cannot** warn per use, so without it a 2.0 app could
be entirely silent right up to the bare `NameError` it would get on 2.1.

#### 2. Per-constant warnings — one line per lazily shimmed constant

Expand Down Expand Up @@ -261,7 +265,7 @@ Capybara::Screenshot::Diff.stub_const(:AVAILABLE_DRIVERS, []) { ... }
SnapDiff::Drivers.stub_const(:AVAILABLE_DRIVERS, []) { ... }
```

**`SnapDiff::Config::MAPPING` is gone.** It split in two: `SnapDiff::Config::SETTINGS` (the setting names, no legacy knowledge) and `SnapDiff::LegacyShims::CONFIG_MAPPING` (which legacy holder each name hangs off). If you referenced `MAPPING` — iterating settings in a test helper, say — use `SETTINGS`; `CONFIG_MAPPING` is `@api private` and disappears in 3.0 with the rest of the v1 surface.
**`SnapDiff::Config::MAPPING` is gone.** It split in two: `SnapDiff::Config::SETTINGS` (the setting names, no legacy knowledge) and `SnapDiff::LegacyShims::CONFIG_MAPPING` (which legacy holder each name hangs off). If you referenced `MAPPING` — iterating settings in a test helper, say — use `SETTINGS`; `CONFIG_MAPPING` is `@api private` and disappears in 2.1 with the rest of the v1 surface.

---

Expand Down
4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ Drivers abstract image processing operations. Shared default behavior lives in t

**Auto-detection:** `SnapDiff::Drivers.detect_available` tries to load `:vips` first (via `ruby-vips` gem), then `:chunky_png`. The `:auto` driver mode picks the first available. `Utils.detect_available_drivers` is the older name and one-lines into it.

**Registry (ADR-008 step 5b):** `SnapDiff::Drivers.loaded` is the canonical driver-class cache — a `name => class` hash filled lazily by `Utils.find_driver_class_for`, and the registration point for custom drivers (the legacy `Capybara::Screenshot::Diff::LOADED_DRIVERS` is an eager same-object alias, so registrations through either land in the same hash). `SnapDiff::Drivers.available` is the canonical read API for the detected list, and since the 3.0-readiness pass the value lives with it, as `SnapDiff::Drivers::AVAILABLE_DRIVERS` — that constant is now the published stubbing point, and the legacy `Capybara::Screenshot::Diff::AVAILABLE_DRIVERS` is an eager same-object alias of it. `SnapDiff::Drivers.for` resolves an options hash to a driver instance. See [Custom drivers](snapdiff.md#custom-drivers).
**Registry (ADR-008 step 5b):** `SnapDiff::Drivers.loaded` is the canonical driver-class cache — a `name => class` hash filled lazily by `Utils.find_driver_class_for`, and the registration point for custom drivers (the legacy `Capybara::Screenshot::Diff::LOADED_DRIVERS` is an eager same-object alias, so registrations through either land in the same hash). `SnapDiff::Drivers.available` is the canonical read API for the detected list, and the value lives with it, as `SnapDiff::Drivers::AVAILABLE_DRIVERS` — that constant is now the published stubbing point, and the legacy `Capybara::Screenshot::Diff::AVAILABLE_DRIVERS` is an eager same-object alias of it. `SnapDiff::Drivers.for` resolves an options hash to a driver instance. See [Custom drivers](snapdiff.md#custom-drivers).

### 6. Difference Region Detection

Expand Down Expand Up @@ -223,7 +223,7 @@ Since ADR-008 step 1 the storage ownership is inverted from the original v2 cons

The legacy `Capybara::Screenshot.*` / `Capybara::Screenshot::Diff.*` accessors are thin delegators generated from `SnapDiff::LegacyShims::CONFIG_MAPPING` (both singleton and instance methods, matching what `mattr_accessor` used to define) that forward to that one object. One storage, two views — a write through either surface is visible through the other structurally, not by synchronization.

Since the 3.0-readiness pass, `lib/snap_diff/legacy_shims.rb` is the single file that holds the v1 surface as code: the `const_missing` forwarders, `CONFIG_MAPPING` and its generator, the derived forwarders (`Screenshot.active?`, `Diff.configure`, `Diff.default_options`, …) and `SnapDiff.start`. `Config` itself names nothing from the v1 namespaces — it declares its settings in `Config::SETTINGS`, and `LegacyShims::CONFIG_MAPPING` says which legacy holder each one is exposed on (an invariant pinned by `snap_diff_config_test.rb`). `lib/capybara/screenshot/diff/config_legacy.rb` remains at the old path as a pair of requires.
`lib/snap_diff/legacy_shims.rb` is the single file that holds the v1 surface as code: the `const_missing` forwarders, `CONFIG_MAPPING` and its generator, the derived forwarders (`Screenshot.active?`, `Diff.configure`, `Diff.default_options`, …) and `SnapDiff.start`. **It is not a permanent fixture — it is the removal layer.** It exists so that deleting v1 support in 2.1 is a `git rm` of `lib/capybara*`, `legacy_shims.rb`, `deprecation.rb` and `test/legacy/`, not a refactor; two mechanical gates (`legacy_tree_is_alias_only_test.rb`, `core_tree_has_no_legacy_deps_test.rb`) keep that true, and `legacy_deletion_test.rb` actually performs the deletion in a tmpdir and loads every canonical entry point from it. `Config` itself names nothing from the v1 namespaces — it declares its settings in `Config::SETTINGS`, and `LegacyShims::CONFIG_MAPPING` says which legacy holder each one is exposed on (an invariant pinned by `snap_diff_config_test.rb`). `lib/capybara/screenshot/diff/config_legacy.rb` remains at the old path as a pair of requires.

The two legacy views are organized into two namespaces:

Expand Down
5 changes: 5 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ Capybara::Screenshot::Diff.color_distance_limit = 42

### Allowed shift distance

> **Deprecated — removed in 2.1.** `shift_distance_limit` only ever worked on the ChunkyPNG
> driver (VIPS ignores it), and the ChunkyPNG driver is removed in 2.1 as well. Use
> `median_filter_window_size` on the VIPS driver instead — it is the same idea and far faster.
> See [Image Processing Drivers](drivers.md).

Sometimes you want to allow small movements in the images. For example, jquery-tablesorter
renders the same table slightly differently sometimes. You can set set the shift distance
threshold for the comparison using the `shift_distance_limit` option to the `screenshot`
Expand Down
6 changes: 6 additions & 0 deletions docs/drivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ There are several options to setup active driver: `:auto`, `:chunky_png` and `:v
* `:auto` - will try to load `:vips` if there is gem `ruby-vips`, in other cases will load `:chunky_png`
* `:chunky_png` and `:vips` will load correspondent driver

> **`:chunky_png` is deprecated in 2.0 and removed in 2.1.** VIPS becomes the only driver, and
> `shift_distance_limit` — a ChunkyPNG-only option — goes with it. If you are on ChunkyPNG today,
> add `ruby-vips` to your Gemfile and install libvips before taking 2.1, and replace
> `shift_distance_limit` with `median_filter_window_size` and `color_distance_limit` with
> `perceptual_threshold`.

## Enable VIPS image processing

[Vips](https://www.rubydoc.info/gems/ruby-vips/Vips/Image) driver provides a faster comparison,
Expand Down
2 changes: 1 addition & 1 deletion lib/capybara/screenshot/diff/config_legacy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# and the old accessor names, Diff.configure/.compare, SnapDiff.start and
# the AVAILABLE_DRIVERS alias are generated by snap_diff/legacy_shims -- the
# one file that holds the v1 surface as code, so that the canonical core
# needs nothing from this tree and 3.0 can delete both together. The v1
# needs nothing from this tree and 2.1 can delete both together. The v1
# surface (Capybara::Screenshot.window_size = ..., Diff.configure { ... },
# Diff.compare) keeps working unchanged: one storage, two views.
#
Expand Down
6 changes: 3 additions & 3 deletions lib/snap_diff.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ def self.assert_single_gem!(loaded_specs = Gem.loaded_specs)
# -- snap_diff_test.rb's "bare require never loads the umbrella" guard
# enforces it -- so nothing required below may reach back here. None of
# these requires reaches into lib/capybara* at all, so the canonical entry
# point is exactly what 3.0 keeps.
# point is exactly what survives the removal of the legacy trees.
#
# "capybara/dsl" is needed directly (not just transitively) so
# `Capybara.default_max_wait_time` in Config#default_options resolves even
# when "snap_diff" is required standalone (SnapDiffTest's
# "standalone-loadable in a fresh process" regression test).
#
# snap_diff/legacy_shims is deliberate and is the ONE line here that 3.0
# drops: it carries the whole v1 surface (const_missing forwarders, the old
# snap_diff/legacy_shims is deliberate and is the ONE line here that goes
# with the legacy trees: it carries the whole v1 surface (const_missing forwarders, the old
# mattr_accessors, SnapDiff.start), so a process that only ever requires
# "snap_diff" still resolves the old Capybara::Screenshot::Diff names --
# with deprecation warnings -- exactly as it did when this file reached
Expand Down
2 changes: 1 addition & 1 deletion lib/snap_diff/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# This file is the LEAF of the config require graph (ADR-008 step 1):
# config_legacy.rb requires it, so it must never require config_legacy nor
# anything that leads back to either entry point. It also names nothing from
# the v1 trees at all (3.0 readiness): which legacy accessor each setting is
# the v1 trees at all (legacy-removal readiness): which legacy accessor each setting is
# exposed as is snap_diff/legacy_shims' business, and that file is deleted
# together with lib/capybara* -- see LegacyShims::CONFIG_MAPPING.

Expand Down
6 changes: 3 additions & 3 deletions lib/snap_diff/deprecation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ module Deprecation
# The ONE line a v1 user gets, whichever door they came through. Most of
# the v1 surface cannot warn per use -- the config accessors are plain
# delegators and the eager aliases never reach const_missing -- so
# without this a 2.x app is completely silent right up to the bare
# NameError it gets on 3.0. Deliberately generic and once per process:
# without this a 2.0 app is completely silent right up to the bare
# NameError it gets on 2.1. Deliberately generic and once per process:
# an actionable signal, not per-call stderr noise.
MIGRATION_NOTICE =
"[snap_diff deprecation] This process uses the v1 `Capybara::Screenshot*` / " \
"`CapybaraScreenshotDiff*` API. It still works in 2.x and is REMOVED in 3.0 -- " \
"`CapybaraScreenshotDiff*` API. It still works in 2.0 and is REMOVED in 2.1 -- " \
"see docs/UPGRADING.md for the SnapDiff replacements. Silence with " \
"`SnapDiff.silence_deprecations = true` or SNAP_DIFF_SILENCE_DEPRECATIONS=1. " \
"(shown once per process)"
Expand Down
3 changes: 2 additions & 1 deletion lib/snap_diff/drivers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def self.detect_available
result
end

# Canonical home of the detected-drivers list (3.0 readiness: it used
# Canonical home of the detected-drivers list (legacy-removal
# readiness: it used
# to live only on Capybara::Screenshot::Diff::AVAILABLE_DRIVERS, so
# `require "snap_diff/drivers"` alone left .available raising
# NameError). Detection runs HERE, at this file's load, and the legacy
Expand Down
2 changes: 1 addition & 1 deletion lib/snap_diff/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

# Must NOT require "capybara_screenshot_diff": that would cycle back here via
# this file's old-path forwarder. Nothing from the v1 trees is required here
# at all (3.0 readiness): the three requires below used to point at their
# at all (legacy-removal readiness): the three requires below used to point at their
# capybara/screenshot/diff/* forwarders, which made this unit depend on the
# compatibility tree it is meant to replace.
# DSL includes Capybara::DSL directly below, so it needs the base gem
Expand Down
2 changes: 1 addition & 1 deletion lib/snap_diff/legacy_shims.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# code. lib/capybara* is alias-only by contract
# (legacy_tree_is_alias_only_test.rb) and the canonical core names nothing
# from it (core_tree_has_no_legacy_deps_test.rb), so this file plus those
# trees is exactly what 3.0 deletes.
# trees is exactly what 2.1 deletes.
#
# Three things live here:
# 1. the const_missing forwarders for the pre-v2 namespaces (below);
Expand Down
4 changes: 2 additions & 2 deletions test/legacy/errors_alias_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require "test_helper"
# The shared harness loads canonical entry points only, so a legacy-surface
# test pulls in the v1 entry itself -- the require goes with the file in 3.0.
# test pulls in the v1 entry itself -- the require goes with the file in 2.1.
require "capybara_screenshot_diff"

# ADR-008 step 2: the error classes live in SnapDiff (snap_diff/errors);
Expand All @@ -14,7 +14,7 @@
# warnings, so these tests double as proof the aliases stay warning-free.
#
# LEGACY SURFACE (test/legacy/, see the Rakefile): deleted with lib/capybara*
# in 3.0. The hierarchy assertions that outlive the aliases moved to
# in 2.1. The hierarchy assertions that outlive the aliases moved to
# test/unit/errors_test.rb.
class ErrorsAliasTest < ActiveSupport::TestCase
# old constant path => new constant path
Expand Down
4 changes: 2 additions & 2 deletions test/legacy/legacy_config_accessors_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require "test_helper"
# The shared harness loads canonical entry points only, so a legacy-surface
# test pulls in the v1 entry itself -- the require goes with the file in 3.0.
# test pulls in the v1 entry itself -- the require goes with the file in 2.1.
require "capybara_screenshot_diff"

# LEGACY SURFACE (test/legacy/, see the Rakefile).
Expand All @@ -12,7 +12,7 @@
# as a second VIEW of the one SnapDiff::Config storage. Everything here is
# about that view -- the mapping's completeness, and that a write through
# either surface is visible from the other. Verbatim from the canonical
# file, which keeps the Config-only half; both go on passing until 3.0
# file, which keeps the Config-only half; both go on passing until 2.1
# deletes legacy_shims.rb, this file, and the trees they serve.
class LegacyConfigAccessorsTest < ActiveSupport::TestCase
def config
Expand Down
2 changes: 1 addition & 1 deletion test/legacy/legacy_config_default_timing_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# LEGACY SURFACE (test/legacy/, see the Rakefile).
#
# The v1 half of config_default_timing_test.rb. Two claims, both about the
# old entry points and the old accessor view, both deleted in 3.0:
# old entry points and the old accessor view, both deleted in 2.1:
#
# 1. every legacy entry point produces the SAME require-time defaults and
# the same freezing/liveness behaviour as the canonical ones -- proved by
Expand Down
4 changes: 2 additions & 2 deletions test/legacy/legacy_entry_point_probe_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#
# The v1 half of support_load_probe_test.rb: what the OLD entry points must
# still provide. Names and constants restored verbatim -- every assertion
# here is about a name 3.0 deletes, so repointing them at SnapDiff would
# here is about a name 2.1 deletes, so repointing them at SnapDiff would
# have quietly turned this file into a duplicate of the canonical one.
class LegacyEntryPointProbeTest < ActiveSupport::TestCase
# Alias-completeness probe (the f89cea2 bug class): each documented entry
Expand Down Expand Up @@ -110,7 +110,7 @@ class LegacyEntryPointProbeTest < ActiveSupport::TestCase

# SnapDiff.start moved here out of the canonical CANONICAL_SURFACE gate: it
# is defined in legacy_shims.rb and yields the two v1 config holders, so a
# canonical gate demanding it fails the moment 3.0 deletes them. It is
# canonical gate demanding it fails the moment 2.1 deletes them. It is
# still a documented v1 method, so the per-entry-point availability claim
# the canonical gate used to make lives on here -- for the entries that
# actually keep it. (What it yields is pinned in legacy_forwarders_test.)
Expand Down
4 changes: 2 additions & 2 deletions test/legacy/legacy_forwarders_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require "test_helper"
require "open3"
# The shared harness loads canonical entry points only, so a legacy-surface
# test pulls in the v1 entry itself -- the require goes with the file in 3.0.
# test pulls in the v1 entry itself -- the require goes with the file in 2.1.
require "capybara_screenshot_diff"
require "capybara_screenshot_diff/static"

Expand Down Expand Up @@ -86,7 +86,7 @@ class LegacyForwardersTest < ActiveSupport::TestCase
# absent from $LOADED_FEATURES.
#
# Lives here rather than in snap_diff_test: its subject is the v1
# umbrella, and once 3.0 deletes that file the grep below is empty by
# umbrella, and once 2.1 deletes that file the grep below is empty by
# construction and the guard can never fail again.
test "bare require \"snap_diff\" never loads the umbrella capybara_screenshot_diff" do
script = <<~RUBY
Expand Down
Loading
Loading