diffux_ci 0.5.0 → 0.6.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/lib/diffux_ci/diff_cluster_finder.rb +47 -0
- data/lib/diffux_ci/server.rb +15 -0
- data/lib/diffux_ci/snapshot_comparer.rb +88 -0
- data/lib/diffux_ci/snapshot_comparison_image/after.rb +21 -0
- data/lib/diffux_ci/snapshot_comparison_image/base.rb +107 -0
- data/lib/diffux_ci/snapshot_comparison_image/before.rb +21 -0
- data/lib/diffux_ci/snapshot_comparison_image/gutter.rb +34 -0
- data/lib/diffux_ci/snapshot_comparison_image/overlayed.rb +88 -0
- data/lib/diffux_ci/utils.rb +2 -1
- data/lib/diffux_ci/version.rb +1 -1
- metadata +8 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b444d088ff7fdcfd876e26252d4a9180b7e4aa71
|
4
|
+
data.tar.gz: cfd65df089cee0611a3fe98a6555d1275a3285da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a65d2651905017d4c1a7a032e1483ecfce10c28028c6380ea3a036251d78213dde25e24b7980e001bd7a677288b50eb93f23600ad42535d1869ce48348c0a7f
|
7
|
+
data.tar.gz: 5575fb5c88d9769503a9e8f7d9e796e7e35560dec30f5f064334c6b4332cff989cdee8d887147a77d7bf64a36ec32ac3c62c2250538322c1d6339eca7171d6bf
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'set'
|
2
|
+
module Diffux
|
3
|
+
# This class finds clusters in a diff. A cluster is defined as rows that are
|
4
|
+
# different, and are closer than DIFF_ROW_THRESHOLD pixels to its neighboring
|
5
|
+
# diff row.
|
6
|
+
class DiffClusterFinder
|
7
|
+
MAXIMUM_ADJACENCY_GAP = 5
|
8
|
+
|
9
|
+
# @param number_of_rows [Numeric]
|
10
|
+
def initialize(number_of_rows)
|
11
|
+
@number_of_rows = number_of_rows
|
12
|
+
@rows_with_diff = SortedSet.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Tell the DiffClusterFinder about a row that is different.
|
16
|
+
#
|
17
|
+
# @param row [Numeric]
|
18
|
+
def row_is_different(row)
|
19
|
+
@rows_with_diff.add row
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Float] the percent of rows that are different
|
23
|
+
def percent_of_rows_different
|
24
|
+
@rows_with_diff.length.to_f / @number_of_rows * 100
|
25
|
+
end
|
26
|
+
|
27
|
+
# Calculate clusters from diff-rows that are close to each other.
|
28
|
+
#
|
29
|
+
# @return [Array<Hash>] a list of clusters modeled as hashes:
|
30
|
+
# `{ start: x, finish: y }`
|
31
|
+
def clusters
|
32
|
+
results = []
|
33
|
+
@rows_with_diff.each do |row|
|
34
|
+
current = results.last
|
35
|
+
if !current || current[:finish] + MAXIMUM_ADJACENCY_GAP < row
|
36
|
+
results << {
|
37
|
+
start: row,
|
38
|
+
finish: row,
|
39
|
+
}
|
40
|
+
else
|
41
|
+
current[:finish] = row
|
42
|
+
end
|
43
|
+
end
|
44
|
+
results
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/diffux_ci/server.rb
CHANGED
@@ -38,6 +38,21 @@ module DiffuxCI
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
get '/*' do
|
42
|
+
config = DiffuxCI::Utils.config
|
43
|
+
file = params[:splat].first
|
44
|
+
if File.exist?(file)
|
45
|
+
send_file file
|
46
|
+
else
|
47
|
+
config['public_directories'].each do |pub_dir|
|
48
|
+
filepath = File.join(Dir.pwd, pub_dir, file)
|
49
|
+
if File.exist?(filepath)
|
50
|
+
send_file filepath
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
41
56
|
post '/reject' do
|
42
57
|
DiffuxCI::Action.new(params[:description], params[:viewport]).reject
|
43
58
|
redirect back
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'oily_png'
|
2
|
+
require 'diff-lcs'
|
3
|
+
require_relative 'diff_cluster_finder'
|
4
|
+
|
5
|
+
module DiffuxCI
|
6
|
+
# This class is responsible for comparing two Snapshots and generating a diff.
|
7
|
+
class SnapshotComparer
|
8
|
+
# @param png_before [ChunkyPNG::Image]
|
9
|
+
# @param png_after [ChunkyPNG::Image]
|
10
|
+
def initialize(png_before, png_after)
|
11
|
+
@png_after = png_after
|
12
|
+
@png_before = png_before
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Hash]
|
16
|
+
def compare!
|
17
|
+
no_diff = {
|
18
|
+
diff_in_percent: 0,
|
19
|
+
diff_image: nil,
|
20
|
+
diff_clusters: []
|
21
|
+
}
|
22
|
+
|
23
|
+
# If these images are totally identical, we don't need to do any more
|
24
|
+
# work.
|
25
|
+
return no_diff if @png_before == @png_after
|
26
|
+
|
27
|
+
array_before = to_array_of_arrays(@png_before)
|
28
|
+
array_after = to_array_of_arrays(@png_after)
|
29
|
+
|
30
|
+
# If the arrays of arrays of colors are identical, we don't need to do any
|
31
|
+
# more work. This might happen if some of the headers are different.
|
32
|
+
return no_diff if array_before == array_after
|
33
|
+
|
34
|
+
sdiff = Diff::LCS.sdiff(array_before, array_after)
|
35
|
+
cluster_finder = DiffClusterFinder.new(sdiff.size)
|
36
|
+
sprite, all_comparisons = initialize_comparison_images(
|
37
|
+
[@png_after.width, @png_before.width].max, sdiff.size)
|
38
|
+
|
39
|
+
sdiff.each_with_index do |row, y|
|
40
|
+
# each row is a Diff::LCS::ContextChange instance
|
41
|
+
all_comparisons.each { |image| image.render_row(y, row) }
|
42
|
+
cluster_finder.row_is_different(y) unless row.unchanged?
|
43
|
+
end
|
44
|
+
|
45
|
+
percent_changed = cluster_finder.percent_of_rows_different
|
46
|
+
{
|
47
|
+
diff_in_percent: percent_changed,
|
48
|
+
diff_image: (sprite if percent_changed > 0),
|
49
|
+
diff_clusters: cluster_finder.clusters,
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# @param [ChunkyPNG::Image]
|
56
|
+
# @return [Array<Array<Integer>>]
|
57
|
+
def to_array_of_arrays(chunky_png)
|
58
|
+
array_of_arrays = []
|
59
|
+
chunky_png.height.times do |y|
|
60
|
+
array_of_arrays << chunky_png.row(y)
|
61
|
+
end
|
62
|
+
array_of_arrays
|
63
|
+
end
|
64
|
+
|
65
|
+
# @param canvas [ChunkyPNG::Image] The output image to draw pixels on
|
66
|
+
# @return [Array<SnapshotComparisonImage>]
|
67
|
+
def initialize_comparison_images(width, height)
|
68
|
+
gutter_width = SnapshotComparisonImage::Gutter::WIDTH
|
69
|
+
total_width = (width * 3) + (gutter_width * 3)
|
70
|
+
|
71
|
+
sprite = ChunkyPNG::Image.new(total_width, height)
|
72
|
+
offset, comparison_images = 0, []
|
73
|
+
comparison_images << SnapshotComparisonImage::Gutter.new(offset, sprite)
|
74
|
+
offset += gutter_width
|
75
|
+
comparison_images << SnapshotComparisonImage::Before.new(offset, sprite)
|
76
|
+
offset += width
|
77
|
+
comparison_images << SnapshotComparisonImage::Gutter.new(offset, sprite)
|
78
|
+
offset += gutter_width
|
79
|
+
comparison_images << SnapshotComparisonImage::Overlayed.new(offset, sprite)
|
80
|
+
offset += width
|
81
|
+
comparison_images << SnapshotComparisonImage::Gutter.new(offset, sprite)
|
82
|
+
offset += gutter_width
|
83
|
+
comparison_images << SnapshotComparisonImage::After.new(offset, sprite)
|
84
|
+
|
85
|
+
[sprite, comparison_images]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Diffux
|
2
|
+
module SnapshotComparisonImage
|
3
|
+
# This subclass of `SnapshotComparisonImage` knows how to draw the
|
4
|
+
# representation of the "after" image.
|
5
|
+
class After < SnapshotComparisonImage::Base
|
6
|
+
# @param y [Integer]
|
7
|
+
# @param row [Diff::LCS:ContextChange]
|
8
|
+
def render_changed_row(y, row)
|
9
|
+
render_added_row(y, row)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param y [Integer]
|
13
|
+
# @param row [Diff::LCS:ContextChange]
|
14
|
+
def render_added_row(y, row)
|
15
|
+
row.new_element.each_with_index do |pixel_after, x|
|
16
|
+
render_pixel(x, y, pixel_after)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'chunky_png'
|
2
|
+
module Diffux
|
3
|
+
module SnapshotComparisonImage
|
4
|
+
# This model represents a "comparison image". Basically it's just a wrapper
|
5
|
+
# around a ChunkyPNG image with some nice methods to make life easier in the
|
6
|
+
# world of diffs.
|
7
|
+
#
|
8
|
+
# This model is never persisted.
|
9
|
+
class Base
|
10
|
+
include ChunkyPNG::Color
|
11
|
+
|
12
|
+
BASE_OPACITY = 0.1
|
13
|
+
BASE_ALPHA = (255 * BASE_OPACITY).round
|
14
|
+
BASE_DIFF_ALPHA = BASE_ALPHA * 2
|
15
|
+
|
16
|
+
MAGENTA = ChunkyPNG::Color.from_hex '#b33682'
|
17
|
+
RED = ChunkyPNG::Color.from_hex '#dc322f'
|
18
|
+
GREEN = ChunkyPNG::Color.from_hex '#859900'
|
19
|
+
|
20
|
+
# @param offset [Integer] the x-offset that this comparison image should
|
21
|
+
# use when rendering on the canvas image.
|
22
|
+
# @param canvas [ChunkyPNG::Image] The canvas image to render pixels on.
|
23
|
+
def initialize(offset, canvas)
|
24
|
+
@offset = offset
|
25
|
+
@canvas = canvas
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param y [Integer]
|
29
|
+
# @param row [Diff::LCS:ContextChange]
|
30
|
+
def render_row(y, row)
|
31
|
+
if row.unchanged?
|
32
|
+
render_unchanged_row(y, row)
|
33
|
+
elsif row.deleting?
|
34
|
+
render_deleted_row(y, row)
|
35
|
+
elsif row.adding?
|
36
|
+
render_added_row(y, row)
|
37
|
+
else # changing?
|
38
|
+
render_changed_row(y, row)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param y [Integer]
|
43
|
+
# @param row [Diff::LCS:ContextChange]
|
44
|
+
def render_unchanged_row(y, row)
|
45
|
+
row.new_element.each_with_index do |pixel, x|
|
46
|
+
# Render the unchanged pixel as-is
|
47
|
+
render_pixel(x, y, pixel)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param y [Integer]
|
52
|
+
# @param row [Diff::LCS:ContextChange]
|
53
|
+
def render_changed_row(y, row)
|
54
|
+
# no default implementation
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param y [Integer]
|
58
|
+
# @param row [Diff::LCS:ContextChange]
|
59
|
+
def render_added_row(y, row)
|
60
|
+
# no default implementation
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param y [Integer]
|
64
|
+
# @param row [Diff::LCS:ContextChange]
|
65
|
+
def render_deleted_row(y, row)
|
66
|
+
# no default implementation
|
67
|
+
end
|
68
|
+
|
69
|
+
# Compute a score that represents the difference between 2 pixels
|
70
|
+
#
|
71
|
+
# This method simply takes the Euclidean distance between the RGBA channels
|
72
|
+
# of 2 colors over the maximum possible Euclidean distance. This gives us a
|
73
|
+
# percentage of how different the two colors are.
|
74
|
+
#
|
75
|
+
# Although it would be more perceptually accurate to calculate a proper
|
76
|
+
# Delta E in Lab colorspace, we probably don't need perceptual accuracy for
|
77
|
+
# this application, and it is nice to avoid the overhead of converting RGBA
|
78
|
+
# to Lab.
|
79
|
+
#
|
80
|
+
# @param pixel_after [Integer]
|
81
|
+
# @param pixel_before [Integer]
|
82
|
+
# @return [Float] number between 0 and 1 where 1 is completely different
|
83
|
+
# and 0 is no difference
|
84
|
+
def pixel_diff_score(pixel_after, pixel_before)
|
85
|
+
ChunkyPNG::Color::euclidean_distance_rgba(pixel_after, pixel_before) /
|
86
|
+
ChunkyPNG::Color::MAX_EUCLIDEAN_DISTANCE_RGBA
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param diff_score [Float]
|
90
|
+
# @return [Integer] a number between 0 and 255 that represents the alpha
|
91
|
+
# channel of of the difference
|
92
|
+
def diff_alpha(diff_score)
|
93
|
+
(BASE_DIFF_ALPHA + ((255 - BASE_DIFF_ALPHA) * diff_score)).round
|
94
|
+
end
|
95
|
+
|
96
|
+
# Renders a pixel on the specified x and y position. Uses the offset that
|
97
|
+
# the comparison image has been configured with.
|
98
|
+
#
|
99
|
+
# @param x [Integer]
|
100
|
+
# @param y [Integer]
|
101
|
+
# @param pixel [Integer]
|
102
|
+
def render_pixel(x, y, pixel)
|
103
|
+
@canvas.set_pixel(x + @offset, y, pixel)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Diffux
|
2
|
+
module SnapshotComparisonImage
|
3
|
+
# This subclass of `SnapshotComparisonImage` knows how to draw the
|
4
|
+
# representation of the "before" image.
|
5
|
+
class Before < SnapshotComparisonImage::Base
|
6
|
+
# @param y [Integer]
|
7
|
+
# @param row [Diff::LCS:ContextChange]
|
8
|
+
def render_changed_row(y, row)
|
9
|
+
render_deleted_row(y, row)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param y [Integer]
|
13
|
+
# @param row [Diff::LCS:ContextChange]
|
14
|
+
def render_deleted_row(y, row)
|
15
|
+
row.old_element.each_with_index do |pixel_before, x|
|
16
|
+
render_pixel(x, y, pixel_before)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Diffux
|
2
|
+
module SnapshotComparisonImage
|
3
|
+
# This class renders a gutter-column with a color representing the type of
|
4
|
+
# change that has happened.
|
5
|
+
class Gutter < SnapshotComparisonImage::Base
|
6
|
+
WIDTH = 10
|
7
|
+
GRAY = ChunkyPNG::Color.from_hex '#cccccc'
|
8
|
+
|
9
|
+
def render_row(y, row)
|
10
|
+
WIDTH.times do |x|
|
11
|
+
render_pixel(x, y, gutter_color(row))
|
12
|
+
end
|
13
|
+
# render a two-pixel empty column
|
14
|
+
2.times do |x|
|
15
|
+
render_pixel(WIDTH - 1 - x, y, WHITE)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def gutter_color(row)
|
22
|
+
if row.unchanged?
|
23
|
+
WHITE
|
24
|
+
elsif row.deleting?
|
25
|
+
RED
|
26
|
+
elsif row.adding?
|
27
|
+
GREEN
|
28
|
+
else # changed?
|
29
|
+
GRAY
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Diffux
|
2
|
+
module SnapshotComparisonImage
|
3
|
+
# This subclass of `SnapshotComparisonImage` knows how to overlay the
|
4
|
+
# after-image on top of the before-image, and render the difference in a
|
5
|
+
# scaled magenta color.
|
6
|
+
class Overlayed < SnapshotComparisonImage::Base
|
7
|
+
WHITE_OVERLAY = ChunkyPNG::Color.fade(WHITE, 1 - BASE_ALPHA)
|
8
|
+
|
9
|
+
# @param offset [Integer]
|
10
|
+
# @param canvas [ChunkyPNG::Image]
|
11
|
+
# @see SnapshotComparisonImage::Base
|
12
|
+
def initialize(offset, canvas)
|
13
|
+
@diff_pixels = {}
|
14
|
+
@faded_pixels = {}
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param y [Integer]
|
19
|
+
# @param row [Diff::LCS:ContextChange]
|
20
|
+
def render_unchanged_row(y, row)
|
21
|
+
# Render translucent original pixels
|
22
|
+
row.new_element.each_with_index do |pixel, x|
|
23
|
+
render_faded_pixel(x, y, pixel)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param y [Integer]
|
28
|
+
# @param row [Diff::LCS:ContextChange]
|
29
|
+
def render_deleted_row(y, row)
|
30
|
+
row.old_element.each_with_index do |pixel_before, x|
|
31
|
+
render_faded_magenta_pixel(TRANSPARENT, pixel_before, x, y)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param y [Integer]
|
36
|
+
# @param row [Diff::LCS:ContextChange]
|
37
|
+
def render_added_row(y, row)
|
38
|
+
row.new_element.each_with_index do |pixel_after, x|
|
39
|
+
render_faded_magenta_pixel(pixel_after, TRANSPARENT, x, y)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param y [Integer]
|
44
|
+
# @param row [Diff::LCS:ContextChange]
|
45
|
+
def render_changed_row(y, row)
|
46
|
+
row.old_element.zip(row.new_element).each_with_index do |pixels, x|
|
47
|
+
pixel_before, pixel_after = pixels
|
48
|
+
render_faded_magenta_pixel(
|
49
|
+
pixel_after || TRANSPARENT,
|
50
|
+
pixel_before || TRANSPARENT,
|
51
|
+
x, y)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# @param pixel_after [Integer]
|
58
|
+
# @param pixel_before [Integer]
|
59
|
+
# @param x [Integer]
|
60
|
+
# @param y [Integer]
|
61
|
+
def render_faded_magenta_pixel(pixel_after, pixel_before, x, y)
|
62
|
+
score = pixel_diff_score(pixel_after, pixel_before)
|
63
|
+
if score > 0
|
64
|
+
render_diff_pixel(x, y, score)
|
65
|
+
else
|
66
|
+
render_faded_pixel(x, y, pixel_after)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param x [Integer]
|
71
|
+
# @param y [Integer]
|
72
|
+
# @param score [Float]
|
73
|
+
def render_diff_pixel(x, y, score)
|
74
|
+
@diff_pixels[score] ||= compose_quick(fade(MAGENTA, diff_alpha(score)),
|
75
|
+
WHITE)
|
76
|
+
render_pixel(x, y, @diff_pixels[score])
|
77
|
+
end
|
78
|
+
|
79
|
+
# @param x [Integer]
|
80
|
+
# @param y [Integer]
|
81
|
+
# @param pixel [Integer]
|
82
|
+
def render_faded_pixel(x, y, pixel)
|
83
|
+
@faded_pixels[pixel] ||= compose_quick(WHITE_OVERLAY, pixel)
|
84
|
+
render_pixel(x, y, @faded_pixels[pixel])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/diffux_ci/utils.rb
CHANGED
@@ -10,6 +10,7 @@ module DiffuxCI
|
|
10
10
|
'snapshots_folder' => './snapshots',
|
11
11
|
'source_files' => [],
|
12
12
|
'stylesheets' => [],
|
13
|
+
'public_directories' => [],
|
13
14
|
'port' => 4567,
|
14
15
|
'driver' => :firefox,
|
15
16
|
'viewports' => {
|
@@ -35,7 +36,7 @@ module DiffuxCI
|
|
35
36
|
end
|
36
37
|
|
37
38
|
def self.normalize_description(description)
|
38
|
-
Base64.
|
39
|
+
Base64.strict_encode64(description).strip
|
39
40
|
end
|
40
41
|
|
41
42
|
def self.path_to(description, viewport_name, file_name)
|
data/lib/diffux_ci/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: diffux_ci
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henric Trotzig
|
@@ -137,12 +137,19 @@ files:
|
|
137
137
|
- bin/diffux
|
138
138
|
- lib/diffux_ci.rb
|
139
139
|
- lib/diffux_ci/action.rb
|
140
|
+
- lib/diffux_ci/diff_cluster_finder.rb
|
140
141
|
- lib/diffux_ci/diffs.html.erb
|
141
142
|
- lib/diffux_ci/logger.rb
|
142
143
|
- lib/diffux_ci/public/diffux_ci-runner.js
|
143
144
|
- lib/diffux_ci/public/diffux_ci-styles.css
|
144
145
|
- lib/diffux_ci/runner.rb
|
145
146
|
- lib/diffux_ci/server.rb
|
147
|
+
- lib/diffux_ci/snapshot_comparer.rb
|
148
|
+
- lib/diffux_ci/snapshot_comparison_image/after.rb
|
149
|
+
- lib/diffux_ci/snapshot_comparison_image/base.rb
|
150
|
+
- lib/diffux_ci/snapshot_comparison_image/before.rb
|
151
|
+
- lib/diffux_ci/snapshot_comparison_image/gutter.rb
|
152
|
+
- lib/diffux_ci/snapshot_comparison_image/overlayed.rb
|
146
153
|
- lib/diffux_ci/uploader.rb
|
147
154
|
- lib/diffux_ci/utils.rb
|
148
155
|
- lib/diffux_ci/version.rb
|