capybara-screenshot-diff 1.8.3 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -11
  3. data/capybara-screenshot-diff.gemspec +1 -2
  4. data/gems.rb +4 -4
  5. data/lib/capybara/screenshot/diff/area_calculator.rb +56 -0
  6. data/lib/capybara/screenshot/diff/browser_helpers.rb +1 -1
  7. data/lib/capybara/screenshot/diff/comparison.rb +6 -0
  8. data/lib/capybara/screenshot/diff/cucumber.rb +1 -9
  9. data/lib/capybara/screenshot/diff/difference.rb +8 -4
  10. data/lib/capybara/screenshot/diff/drivers/base_driver.rb +0 -4
  11. data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +10 -5
  12. data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +14 -3
  13. data/lib/capybara/screenshot/diff/drivers.rb +1 -1
  14. data/lib/capybara/screenshot/diff/image_compare.rb +80 -114
  15. data/lib/capybara/screenshot/diff/region.rb +28 -7
  16. data/lib/capybara/screenshot/diff/reporters/default.rb +117 -0
  17. data/lib/capybara/screenshot/diff/screenshot_matcher.rb +24 -53
  18. data/lib/capybara/screenshot/diff/screenshoter.rb +61 -42
  19. data/lib/capybara/screenshot/diff/stable_screenshoter.rb +51 -29
  20. data/lib/capybara/screenshot/diff/test_methods.rb +75 -8
  21. data/lib/capybara/screenshot/diff/{drivers/utils.rb → utils.rb} +0 -7
  22. data/lib/capybara/screenshot/diff/vcs.rb +1 -1
  23. data/lib/capybara/screenshot/diff/version.rb +1 -1
  24. data/lib/capybara/screenshot/diff.rb +1 -111
  25. data/lib/capybara-screenshot-diff.rb +1 -1
  26. data/lib/capybara_screenshot_diff/cucumber.rb +12 -0
  27. data/lib/capybara_screenshot_diff/dsl.rb +10 -0
  28. data/lib/capybara_screenshot_diff/minitest.rb +45 -0
  29. data/lib/capybara_screenshot_diff/rspec.rb +31 -0
  30. data/lib/capybara_screenshot_diff.rb +85 -0
  31. metadata +15 -37
  32. data/lib/capybara/screenshot/diff/stabilization.rb +0 -0
  33. data/sig/capybara/screenshot/diff/diff.rbs +0 -28
  34. data/sig/capybara/screenshot/diff/difference.rbs +0 -33
  35. data/sig/capybara/screenshot/diff/drivers/base_driver.rbs +0 -63
  36. data/sig/capybara/screenshot/diff/drivers/browser_helpers.rbs +0 -36
  37. data/sig/capybara/screenshot/diff/drivers/chunky_png_driver.rbs +0 -89
  38. data/sig/capybara/screenshot/diff/drivers/utils.rbs +0 -13
  39. data/sig/capybara/screenshot/diff/drivers/vips_driver.rbs +0 -25
  40. data/sig/capybara/screenshot/diff/image_compare.rbs +0 -93
  41. data/sig/capybara/screenshot/diff/os.rbs +0 -11
  42. data/sig/capybara/screenshot/diff/region.rbs +0 -43
  43. data/sig/capybara/screenshot/diff/screenshot_matcher.rbs +0 -60
  44. data/sig/capybara/screenshot/diff/screenshoter.rbs +0 -48
  45. data/sig/capybara/screenshot/diff/stable_screenshoter.rbs +0 -29
  46. data/sig/capybara/screenshot/diff/test_methods.rbs +0 -39
  47. data/sig/capybara/screenshot/diff/vcs.rbs +0 -17
@@ -1,63 +0,0 @@
1
- module Capybara
2
- module Screenshot
3
- module Diff
4
- module Drivers
5
- class BaseDriver[ImageEntity]
6
-
7
- type images_entity[out T] = [T, T]
8
- type dimension_entity = [Numeric, Numeric]
9
-
10
- type options_entity = {
11
- area_size_limit?: Numeric?,
12
- color_distance_limit?: Numeric?,
13
- driver: (:auto | :vips | :chunky_png | VipsDriver | ChunkyPNGDriver)?,
14
- dimensions: dimension_entity?,
15
- median_filter_window_size: Numeric?,
16
- shift_distance_limit?: Numeric?,
17
- skip_area?: Array[Region]?,
18
- stability_time_limit?: Numeric?,
19
- tolerance?: Numeric?,
20
- wait?: Numeric?
21
- }
22
-
23
- type color = [Integer, Integer, Integer, Integer]
24
-
25
- def find_difference_region: (ImageEntity new_image, ImageEntity old_image, Numeric color_distance_limit, Numeric _shift_distance_limit, Integer _area_size_limit, ?fast_fail: bool) -> Difference
26
-
27
- def inscribed?: (dimension_entity dimensions, ImageEntity i) -> boolish
28
-
29
- def crop: (Region region, ImageEntity i) -> ImageEntity
30
-
31
- def filter_image_with_median: (ImageEntity image, Numeric median_filter_window_size) -> ImageEntity
32
-
33
- def add_black_box: (ImageEntity memo, Region region) -> void
34
-
35
- def difference_level: (ImageEntity diff_mask, ImageEntity old_img, Region _region) -> Float
36
-
37
- def image_area_size: (ImageEntity old_img) -> Integer
38
-
39
- def height_for: (ImageEntity image) -> Integer
40
-
41
- def width_for: (ImageEntity image) -> Integer
42
-
43
- # Vips could not work with the same file. Per each process we require to create new file
44
- def save_image_to: (ImageEntity image, String filename) -> void
45
-
46
- def resize_image_to: (ImageEntity image, Integer new_width, Integer new_height) -> ImageEntity
47
-
48
- def load_images: (String old_file_name, String new_file_name) -> images_entity[ImageEntity]
49
-
50
- def from_file: (TestMethods::path_entity filename) -> ImageEntity
51
-
52
- def dimension: (ImageEntity image) -> dimension_entity
53
-
54
- def draw_rectangles: (images_entity[ImageEntity] images, Region region, color rgba, Integer offset) -> void
55
-
56
- def same_pixels?: () -> boolish
57
- end
58
-
59
- def self.for: (ScreenshotMatcher::input_options) -> (VipsDriver | ChunkyPNGDriver)
60
- end
61
- end
62
- end
63
- end
@@ -1,36 +0,0 @@
1
- module Capybara
2
- class Result
3
- def map: () -> Result
4
- end
5
- module Screenshot
6
- module BrowserHelpers
7
- def self.current_capybara_driver_class: () -> top
8
-
9
- def self.selenium?: () -> boolish?
10
-
11
- def self.window_size_is_wrong?: () -> boolish
12
-
13
- def self.bounds_for_css: (*String css_selectors) -> Array[Region::raw_region_entity]
14
-
15
- IMAGE_WAIT_SCRIPT: String
16
-
17
- def self.pending_image_to_load: () -> top?
18
-
19
- HIDE_CARET_SCRIPT: String
20
-
21
- def self.hide_caret: () -> void
22
-
23
- FIND_ACTIVE_ELEMENT_SCRIPT: String
24
-
25
- type capybara_element = top
26
-
27
- def self.blur_from_focused_element: () -> capybara_element?
28
-
29
- GET_BOUNDING_CLIENT_RECT_SCRIPT: String
30
-
31
- def self.all_visible_regions_for: (String selector) -> Array[Region::raw_region_entity]
32
-
33
- def self.region_for: (Result element) -> Region::raw_region_entity
34
- end
35
- end
36
- end
@@ -1,89 +0,0 @@
1
- module ChunkyPNG
2
- class Canvas
3
- end
4
-
5
- class Image
6
- def self.from_blob: (String str) -> Image
7
-
8
- def self.from_file: (String filename) -> Image
9
- end
10
- end
11
-
12
- module Capybara
13
- module Screenshot
14
- module Diff
15
- module Drivers
16
- class ChunkyPNGDriver < BaseDriver[ChunkyPNG::Canvas]
17
-
18
- def _load_images: (String old_file, String new_file) -> [ChunkyPNG::Image, ChunkyPNG::Image]
19
-
20
- class DifferenceRegionFinder
21
- def find_diff_rectangle: (
22
- ChunkyPNG::Image org_img,
23
- ChunkyPNG::Image new_img,
24
- (Region | Region::raw_region_entity) area_coordinates,
25
- cache: ImageCompare::cache_entity
26
- ) -> Region?
27
-
28
- def find_top: (
29
- ChunkyPNG::Image old_img,
30
- ChunkyPNG::Image new_img,
31
- cache: ImageCompare::cache_entity
32
- ) -> Region::raw_region_entity?
33
-
34
- def find_left_right_and_top: (
35
- ChunkyPNG::Image old_img,
36
- ChunkyPNG::Image new_img,
37
- (Region | Region::raw_region_entity) region,
38
- cache: ImageCompare::cache_entity
39
- ) -> Region::raw_region_entity
40
-
41
- def find_bottom: (
42
- ChunkyPNG::Image old_img,
43
- ChunkyPNG::Image new_img,
44
- Integer left,
45
- Integer right,
46
- Integer bottom,
47
- cache: ImageCompare::cache_entity
48
- ) -> Integer
49
-
50
- def same_color?: (
51
- ChunkyPNG::Image old_img,
52
- ChunkyPNG::Image new_img,
53
- Integer x,
54
- Integer y,
55
- ?cache: ImageCompare::cache_entity
56
- ) -> boolish
57
-
58
- def skipped_region?: (Integer x, Integer y) -> boolish
59
-
60
- def color_distance_at: (
61
- ChunkyPNG::Image new_img,
62
- ChunkyPNG::Image old_img,
63
- Integer x,
64
- Integer y,
65
- shift_distance_limit: Numeric?
66
- ) -> Float
67
-
68
- def shift_distance_at: (
69
- ChunkyPNG::Image new_img,
70
- ChunkyPNG::Image old_img,
71
- Integer x,
72
- Integer y,
73
- color_distance_limit: Numeric?
74
- ) -> Numeric
75
-
76
- def color_matches: (
77
- ChunkyPNG::Image new_img,
78
- Integer org_color,
79
- Integer x,
80
- Integer y,
81
- Numeric? color_distance_limit
82
- ) -> boolish
83
- end
84
-
85
- end
86
- end
87
- end
88
- end
89
- end
@@ -1,13 +0,0 @@
1
- module Capybara
2
- module Screenshot
3
- module Diff
4
- module Utils
5
- def self.detect_available_drivers: () -> Array[(:vips | :chunky_png)]
6
-
7
- def self.find_driver_class_for: [T] (Symbol driver) -> T
8
-
9
- def self.detect_test_framework_assert: [T < Exception] () -> T
10
- end
11
- end
12
- end
13
- end
@@ -1,25 +0,0 @@
1
- module ::Vips
2
- class Image
3
- def self.new_from_file: (String filename) -> Image
4
- end
5
- end
6
-
7
- module Capybara
8
- module Screenshot
9
- module Diff
10
- module Drivers
11
- class VipsDriver < BaseDriver[Vips::Image]
12
- class VipsUtil
13
- def self.difference_area: (Vips::Image old_image, Vips::Image new_image, ?color_distance: ::Integer) -> Numeric
14
-
15
- def self.difference_area_size_by: (Vips::Image difference_mask) -> Numeric
16
-
17
- def self.difference_mask: (Vips::Image, Vips::Image, ?Numeric? color_distance) -> Vips::Image
18
-
19
- def self.difference_region_by: (Vips::Image diff_mask) -> Region?
20
- end
21
- end
22
- end
23
- end
24
- end
25
- end
@@ -1,93 +0,0 @@
1
- module Capybara
2
- module Screenshot
3
- module Diff
4
- LOADED_DRIVERS: { vips: ImageCompare::driver_entity, chunky_png: ImageCompare::driver_entity }
5
-
6
- # Compare two images_entities and determine if they are equal, different, or within some comparison
7
- # range considering color values and difference area size.
8
- class ImageCompare
9
- TOLERABLE_OPTIONS: [:tolerance, :color_distance_limit, :shift_distance_limit, :area_size_limit]
10
-
11
- class Comparison
12
-
13
- end
14
-
15
- type driver_entity = (Drivers::VipsDriver | Drivers::ChunkyPNGDriver)
16
- type image_entity = (ChunkyPNG::Canvas | Vips::Image)
17
- type images_entities = [image_entity, image_entity]
18
- type cache_entity = (::Hash[Symbol, top] | top)
19
-
20
- @annotated_base_image_path: Pathname
21
- @annotated_image_path: Pathname
22
- @difference: Difference
23
- @error_message: String?
24
-
25
- attr_reader error_message: String?
26
- attr_reader annotated_base_image_path: Pathname
27
- attr_reader annotated_image_path: Pathname
28
- attr_reader driver: driver_entity
29
- attr_reader driver_options: Drivers::BaseDriver::options_entity
30
- attr_reader annotated_new_file_name: TestMethods::path_entity
31
- attr_reader annotated_old_file_name: TestMethods::path_entity
32
- attr_reader new_file_name: String
33
- attr_reader old_file_name: String
34
- attr_reader skip_area: Array[Region]?
35
- attr_accessor shift_distance_limit: Numeric?
36
- attr_accessor area_size_limit: Numeric?
37
- attr_accessor color_distance_limit: Numeric?
38
-
39
- @median_filter_window_size: Numeric?
40
- @dimensions: Drivers::BaseDriver::dimension_entity?
41
- @tolerance: Numeric?
42
-
43
- def initialize: (TestMethods::path_entity new_file_name, TestMethods::path_entity old_file_name, ?Drivers::BaseDriver::options_entity options) -> void
44
-
45
- # Compare the two image_entity files and return `true` or `false` as quickly as possible.
46
- # Return falsely if the old file does not exist or the image_entity dimensions do not match.
47
- def quick_equal?: () -> boolish
48
-
49
- # Compare the two images_entities referenced by this object, and return `true` if they are different,
50
- # and `false` if they are the same.
51
- def different?: () -> bool
52
-
53
- def clean_tmp_files: () -> void
54
-
55
- def save: (image_entity, TestMethods::path_entity) -> void
56
-
57
- def old_file_exists?: () -> boolish
58
-
59
- def reset: () -> void
60
-
61
- NEW_LINE: "\n"
62
-
63
- private
64
-
65
- def _different?: -> String?
66
-
67
- def different: (Difference images) -> String
68
-
69
- def preprocess_images: (images_entities images) -> images_entities
70
-
71
- def preprocess_image: (image_entity image) -> image_entity
72
-
73
- def old_file_size: () -> Integer
74
-
75
- def new_file_size: () -> Integer
76
-
77
- def not_different: () -> nil
78
-
79
- def annotate_and_save_images: (Difference) -> void
80
-
81
- def annotate_and_save_image: (Difference, image_entity, TestMethods::path_entity) -> void
82
-
83
- DIFF_COLOR: [255, 0, 0, 255]
84
-
85
- def annotate_difference: (image_entity, Region) -> void
86
-
87
- SKIP_COLOR: [255, 192, 0, 255]
88
-
89
- def annotate_skip_areas: (image_entity, Array[Region] skip_areas) -> void
90
- end
91
- end
92
- end
93
- end
@@ -1,11 +0,0 @@
1
- module Capybara
2
- module Screenshot
3
- module Os
4
- ON_WINDOWS: bool
5
- ON_MAC: bool
6
- ON_LINUX: bool
7
-
8
- def self.name: () -> ("windows" | "macos" | "linux" | "unknown")
9
- end
10
- end
11
- end
@@ -1,43 +0,0 @@
1
- class Region
2
- type raw_region_entity = [Numeric, Numeric, Numeric, Numeric]
3
-
4
- attr_accessor x: Numeric
5
-
6
- attr_accessor y: Numeric
7
-
8
- attr_accessor width: Numeric
9
-
10
- attr_accessor height: Numeric
11
-
12
- def initialize: (Numeric x, Numeric y, Numeric width, Numeric height) -> void
13
-
14
- def self.from_top_left_corner_coordinates: (Numeric x, Numeric y, Numeric width, Numeric height) -> Region?
15
-
16
- def self.from_edge_coordinates: (Numeric left, Numeric `top`, Numeric right, Numeric bottom) -> Region?
17
-
18
- def to_edge_coordinates: () -> ::Array[Numeric]
19
-
20
- def to_top_left_corner_coordinates: () -> ::Array[Numeric]
21
-
22
- def top: () -> Numeric
23
-
24
- def bottom: () -> Numeric
25
-
26
- def left: () -> Numeric
27
-
28
- def right: () -> Numeric
29
-
30
- def size: () -> Numeric
31
-
32
- def to_a: () -> ::Array[Numeric]
33
-
34
- def find_intersect_with: (Region region) -> Region?
35
-
36
- def intersect?: (Region region) -> boolish
37
-
38
- def move_by: (Numeric right_by, Numeric down_by) -> Region
39
-
40
- def find_relative_intersect: (Region region) -> Region?
41
-
42
- def cover?: (Numeric x, Numeric y) -> boolish
43
- end
@@ -1,60 +0,0 @@
1
- module Capybara
2
- module Screenshot
3
- module Diff
4
- class ScreenshotMatcher
5
-
6
- type job_entity = Array[top]
7
-
8
- attr_reader base_screenshot_path: TestMethods::path_entity
9
- attr_reader driver_options: Drivers::BaseDriver::options_entity
10
- attr_reader screenshot_full_name: TestMethods::path_entity
11
- attr_reader screenshot_path: Pathname
12
-
13
- def build_screenshot_matches_job: -> job_entity?
14
- def cleanup: -> void
15
-
16
- def self.base_image_path_from: (TestMethods::path_entity) -> Pathname
17
-
18
- private
19
-
20
- def build_screenshoter_for: (capture_options_entity capture_options, Drivers::BaseDriver::options_entity driver_options) -> (Screenshoter | StableScreenshoter)
21
-
22
- def checkout_base_screenshot: -> void
23
-
24
- def take_comparison_screenshot: (capture_options_entity capture_options, Drivers::BaseDriver::options_entity driver_options, TestMethods::path_entity screenshot_path) -> void
25
-
26
- def create_output_directory_for: (Pathname file_name) -> void
27
-
28
- type skip_area_entity = String | Region::raw_region_entity
29
- type flex_skip_area_entity = (skip_area_entity | Array[skip_area_entity])
30
- type flex_crop_entity = (nil | String | Region | Region::raw_region_entity)
31
-
32
- def calculate_skip_area: (flex_skip_area_entity skip_area, flex_crop_entity crop) -> Array[Region]
33
-
34
- type input_region = (Region::raw_region_entity | String | Region)
35
-
36
- type input_options = {
37
- area_size_limit: Numeric?,
38
- color_distance_limit: Numeric?,
39
- driver: (:auto | :vips | :chunky_png | ImageCompare::driver_entity)?,
40
- median_filter_window_size: Numeric?,
41
- shift_distance_limit: Numeric?,
42
- skip_area: nil | Array[input_region] | input_region,
43
- stability_time_limit: Numeric?,
44
- tolerance: Numeric?,
45
- wait: Numeric?
46
- }
47
-
48
- def calculate_crop_region: (input_options driver_options) -> Region?
49
-
50
- type capture_options_entity = {
51
- stability_time_limit: Numeric?,
52
- wait: Numeric?,
53
- crop: Region?
54
- }
55
-
56
- def build_regions_for: ((Enumerable[Region::raw_region_entity]) coordinates) -> Array[Region?]
57
- end
58
- end
59
- end
60
- end
@@ -1,48 +0,0 @@
1
- module Capybara
2
- module Screenshot
3
- class Screenshoter
4
- @_csd_retina_warned: bool?
5
-
6
- attr_reader capture_options: Diff::ScreenshotMatcher::capture_options_entity
7
-
8
- attr_reader comparison_options: Diff::Drivers::BaseDriver::options_entity
9
-
10
- attr_reader driver: Diff::ImageCompare::driver_entity
11
-
12
- def initialize: (?Diff::ScreenshotMatcher::capture_options_entity capture_options, Diff::ImageCompare::driver_entity driver) -> void
13
-
14
- def crop: () -> Numeric?
15
-
16
- def wait: () -> Numeric?
17
-
18
- def self.attempts_screenshot_paths: (Diff::TestMethods::path_entity base_file) -> Array[String]
19
-
20
- def self.cleanup_attempts_screenshots: (Diff::TestMethods::path_entity base_file) -> void
21
-
22
- # Try to get screenshot from browser.
23
- # On `stability_time_limit` it checks that page stop updating by comparison several screenshot attempts
24
- # On reaching `wait` limit then it has been failed. On failing we annotate screenshot attempts to help to debug
25
- def take_comparison_screenshot: (Diff::TestMethods::path_entity screenshot_path) -> void
26
-
27
- def self.gen_next_attempt_path: (Diff::TestMethods::path_entity screenshot_path, Integer iteration) -> Pathname
28
-
29
- def take_screenshot: (Diff::TestMethods::path_entity screenshot_path) -> void
30
-
31
- def browser_save_screenshot: (Diff::TestMethods::path_entity screenshot_path) -> void
32
-
33
- def process_screenshot: (Diff::TestMethods::path_entity screenshot_path) -> void
34
-
35
- def reduce_retina_image_size: (Diff::TestMethods::path_entity file_name) -> void
36
-
37
- def notice_how_to_avoid_this: () -> void
38
-
39
- def prepare_page_for_screenshot: (timeout: Numeric) -> BrowserHelpers::capybara_element?
40
-
41
- def wait_images_loaded: (timeout: Numeric) -> void
42
-
43
- private
44
-
45
- def selenium_with_retina_screen?: () -> boolish
46
- end
47
- end
48
- end
@@ -1,29 +0,0 @@
1
- module Capybara
2
- module Screenshot
3
- module Diff
4
- class StableScreenshoter
5
- STABILITY_OPTIONS: [:stability_time_limit, :wait]
6
-
7
- @_csd_retina_warned: boolish
8
-
9
- @comparison_options: Drivers::BaseDriver::options_entity
10
- @screenshoter: (StableScreenshoter | Screenshoter)
11
- @stability_time_limit: Numeric
12
-
13
- def take_comparison_screenshot: (TestMethods::path_entity screenshot_path) -> void
14
-
15
- def take_stable_screenshot: (TestMethods::path_entity) -> Pathname?
16
-
17
- private
18
-
19
- def annotate_attempts_and_fail!: (TestMethods::path_entity screenshot_path) -> void
20
-
21
- def build_comparison_for: (TestMethods::path_entity attempt_path, TestMethods::path_entity previous_attempt_path) -> ImageCompare
22
-
23
- def prepare_page_for_screenshot: (timeout: Numeric) -> top?
24
-
25
- def annotate_stabilization_images: (Array[String]) -> void
26
- end
27
- end
28
- end
29
- end
@@ -1,39 +0,0 @@
1
- # Add the `screenshot` method to ActionDispatch::IntegrationTest
2
- module Capybara
3
- module Screenshot
4
- module Diff
5
- module TestMethods
6
- type name_entity = (Symbol | String)
7
- type path_entity = (string | Pathname)
8
-
9
- @screenshot_counter: Numeric?
10
- @screenshot_group: String?
11
- @screenshot_section: String?
12
- @test_screenshot_errors: Array[top]?
13
- @test_screenshots: Array[[Array[String]?, String, ImageCompare]]?
14
-
15
- def initialize: (*untyped) -> untyped
16
-
17
- def group_parts: () -> Array[String]
18
-
19
- def build_full_name: (name_entity name) -> String
20
-
21
- def schedule_match_job: ([untyped, untyped, untyped] job) -> true
22
-
23
- def screenshot_dir: () -> String
24
-
25
- def screenshot_section: (name_entity name) -> void
26
-
27
- def screenshot_group: (name_entity? name) -> void
28
-
29
- def screenshot: (name_entity name, ?skip_stack_frames: ::Integer, **untyped options) -> boolish
30
-
31
- def assert_image_not_changed: (String caller, String name, ImageCompare comparison) -> ::String?
32
-
33
- private
34
-
35
- def build_screenshot_matches_job: (String, Drivers::BaseDriver::options_entity) -> ScreenshotMatcher::job_entity?
36
- end
37
- end
38
- end
39
- end
@@ -1,17 +0,0 @@
1
- module Capybara
2
- module Screenshot
3
- module Diff
4
- module Vcs
5
- SILENCE_ERRORS: String
6
-
7
- def self.checkout_vcs: (Pathname screenshot_path, Pathname checkout_path) -> bool
8
-
9
- def self.restore_git_revision: (Pathname screenshot_path, Pathname checkout_path) -> bool
10
-
11
- def self.restore_svn_revision: -> bool
12
-
13
- def self.svn?: -> bool
14
- end
15
- end
16
- end
17
- end