bidi2pdf 0.1.8 → 0.1.10

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -3
  3. data/README.md +146 -7
  4. data/docker/Dockerfile.chromedriver +23 -5
  5. data/docker/entrypoint.sh +41 -0
  6. data/lib/bidi2pdf/bidi/auth_interceptor.rb +3 -0
  7. data/lib/bidi2pdf/bidi/browser_tab.rb +60 -19
  8. data/lib/bidi2pdf/bidi/client.rb +7 -5
  9. data/lib/bidi2pdf/bidi/commands/cdp_get_session.rb +21 -0
  10. data/lib/bidi2pdf/bidi/commands/page_print.rb +101 -0
  11. data/lib/bidi2pdf/bidi/commands/print_parameters_validator.rb +4 -1
  12. data/lib/bidi2pdf/bidi/commands.rb +2 -0
  13. data/lib/bidi2pdf/bidi/connection_manager.rb +3 -0
  14. data/lib/bidi2pdf/bidi/event_manager.rb +34 -4
  15. data/lib/bidi2pdf/bidi/interceptor.rb +12 -2
  16. data/lib/bidi2pdf/bidi/session.rb +35 -2
  17. data/lib/bidi2pdf/bidi/web_socket_dispatcher.rb +5 -5
  18. data/lib/bidi2pdf/chromedriver_manager.rb +25 -11
  19. data/lib/bidi2pdf/cli.rb +9 -2
  20. data/lib/bidi2pdf/test_helpers/configuration.rb +67 -0
  21. data/lib/bidi2pdf/test_helpers/images/extractor.rb +99 -0
  22. data/lib/bidi2pdf/test_helpers/images/image_similarity_checker.rb +50 -0
  23. data/lib/bidi2pdf/test_helpers/images/tiff_helper.rb +204 -0
  24. data/lib/bidi2pdf/test_helpers/images.rb +12 -0
  25. data/lib/bidi2pdf/test_helpers/matchers/contains_pdf_image.rb +29 -0
  26. data/lib/bidi2pdf/test_helpers/pdf_file_helper.rb +39 -0
  27. data/lib/bidi2pdf/test_helpers/spec_paths_helper.rb +60 -0
  28. data/lib/bidi2pdf/test_helpers/testcontainers/chromedriver_container.rb +0 -6
  29. data/lib/bidi2pdf/test_helpers/testcontainers/chromedriver_test_helper.rb +103 -0
  30. data/lib/bidi2pdf/test_helpers/testcontainers/shared_docker_network.rb +21 -0
  31. data/lib/bidi2pdf/test_helpers/testcontainers/testcontainers_refinement.rb +53 -0
  32. data/lib/bidi2pdf/test_helpers/testcontainers.rb +17 -0
  33. data/lib/bidi2pdf/test_helpers.rb +7 -0
  34. data/lib/bidi2pdf/version.rb +1 -1
  35. data/sig/bidi2pdf/bidi/event_manager.rbs +19 -13
  36. metadata +55 -10
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module TestHelpers
5
+ module Images
6
+ # rubocop: disable Metrics/ModuleLength, Metrics/AbcSize
7
+ module TIFFHelper
8
+ # TIFF Tag IDs
9
+ IMAGE_WIDTH = 256
10
+ IMAGE_LENGTH = 257
11
+ BITS_PER_SAMPLE = 258
12
+ COMPRESSION = 259
13
+ PHOTOMETRIC_INTERPRETATION = 262
14
+ STRIP_OFFSETS = 273
15
+ SAMPLES_PER_PIXEL = 277
16
+ ROWS_PER_STRIP = 278
17
+ STRIP_BYTE_COUNTS = 279
18
+ PLANAR_CONFIGURATION = 284
19
+ INK_SET = 332
20
+
21
+ # TIFF Data Types
22
+ TYPE_SHORT = 3
23
+ TYPE_LONG = 4
24
+
25
+ # TIFF Compression Types
26
+ COMPRESSION_NONE = 1
27
+ COMPRESSION_CCITT_G3 = 3
28
+ COMPRESSION_CCITT_G4 = 4
29
+
30
+ # TIFF Photometric Interpretations
31
+ PHOTO_WHITE_IS_ZERO = 0
32
+ PHOTO_BLACK_IS_ZERO = 1
33
+ PHOTO_RGB = 2
34
+ PHOTO_SEPARATION = 5
35
+
36
+ # Planar Configuration
37
+ PLANAR_CHUNKY = 1
38
+
39
+ def tiff_header(hash, data)
40
+ cs_entry = hash[:ColorSpace]
41
+
42
+ cs = if cs_entry.is_a?(Array) && cs_entry.first == :ICCBased
43
+ icc_stream = cs_entry[1]
44
+ icc_stream.hash[:Alternate]
45
+ else
46
+ cs_entry
47
+ end
48
+
49
+ case cs
50
+ when :DeviceCMYK then tiff_header_for_CMYK(hash, data)
51
+ when :DeviceGray then tiff_header_for_gray(hash, data)
52
+ when :DeviceRGB then tiff_header_for_rgb(hash, data)
53
+ else
54
+ logger.warn("Unsupported color space '#{cs}' for compressed image with filter '#{hash[:Filter]}'. Skipping image.")
55
+ nil # Skip processing this image
56
+ end
57
+ end
58
+
59
+ # See:
60
+ # * https://gist.github.com/gstorer/f6a9f1dfe41e8e64dcf58d07afa9ab2a
61
+ # * https://github.com/yob/pdf-reader/blob/main/examples/extract_images.rb
62
+ def pack_tiff(entries)
63
+ fields = entries.size
64
+ fmt = [
65
+ "a2", # Byte order ("II")
66
+ "S<", # TIFF magic (42)
67
+ "L<", # Offset to first IFD (8)
68
+ "S<", # Number of directory entries
69
+ ("S< S< L< L<" * fields), # each tag: id, type, count, value
70
+ "L<" # Next IFD offset (0 = end)
71
+ ].join(" ")
72
+
73
+ # Build the flat array: ['II', 42, 8, fields, tag1, type1, count1, value1, …, 0]
74
+ values = ["II", 42, 8, fields] + entries.flatten + [0]
75
+ values.pack(fmt)
76
+ end
77
+
78
+ def tiff_header_for_gray(hash, data)
79
+ width = hash[:Width]
80
+ height = hash[:Height]
81
+ bpc = hash[:BitsPerComponent] || 8
82
+ img_size = data.bytesize
83
+
84
+ # 9 tags, no extra arrays needed
85
+ fields = 9
86
+ # size of header+IFD before the image data starts
87
+ header_ifd_size = 2 + 2 + 4 + 2 + (fields * 12) + 4
88
+ data_offset = header_ifd_size
89
+
90
+ entries = [
91
+ [IMAGE_WIDTH, TYPE_LONG, 1, width], # ImageWidth
92
+ [IMAGE_LENGTH, TYPE_LONG, 1, height], # ImageLength
93
+ [BITS_PER_SAMPLE, TYPE_SHORT, 1, bpc], # BitsPerSample
94
+ [COMPRESSION, TYPE_SHORT, 1, COMPRESSION_NONE], # Compression (1 = none)
95
+ [PHOTOMETRIC_INTERPRETATION, TYPE_SHORT, 1, PHOTO_BLACK_IS_ZERO], # PhotometricInterpretation (1 = BlackIsZero)
96
+ [STRIP_OFFSETS, TYPE_LONG, 1, data_offset], # StripOffsets
97
+ [SAMPLES_PER_PIXEL, TYPE_SHORT, 1, 1], # SamplesPerPixel
98
+ [STRIP_BYTE_COUNTS, TYPE_LONG, 1, img_size], # StripByteCounts
99
+ [PLANAR_CONFIGURATION, TYPE_SHORT, 1, PLANAR_CHUNKY] # PlanarConfiguration (1 = chunky)
100
+ ]
101
+
102
+ pack_tiff(entries)
103
+ end
104
+
105
+ def tiff_header_for_ccitt(hash, data)
106
+ dp = hash[:DecodeParms] || {}
107
+ width = dp[:Columns] || hash[:Width]
108
+ height = hash[:Height]
109
+ k = dp[:K] || 0
110
+ group = (k.positive? ? COMPRESSION_CCITT_G3 : COMPRESSION_CCITT_G4)
111
+ img_size = data.bytesize
112
+
113
+ # We’ll emit exactly 8 tags:
114
+ fields = 8
115
+ # Calculate where the image data will start:
116
+ header_size = 2 + 2 + 4 + 2 + (fields * 12) + 4
117
+
118
+ entries = [
119
+ [IMAGE_WIDTH, TYPE_LONG, 1, width], # ImageWidth
120
+ [IMAGE_LENGTH, TYPE_LONG, 1, height], # ImageLength
121
+ [BITS_PER_SAMPLE, TYPE_SHORT, 1, 1], # BitsPerSample
122
+ [COMPRESSION, TYPE_SHORT, 1, group], # Compression (3=G3, 4=G4)
123
+ [PHOTOMETRIC_INTERPRETATION, TYPE_SHORT, 1, PHOTO_WHITE_IS_ZERO], # PhotometricInterpretation (0 = WhiteIsZero)
124
+ [STRIP_OFFSETS, TYPE_LONG, 1, header_size], # StripOffsets
125
+ [ROWS_PER_STRIP, TYPE_LONG, 1, height], # RowsPerStrip
126
+ [STRIP_BYTE_COUNTS, TYPE_LONG, 1, img_size] # StripByteCounts
127
+ ]
128
+
129
+ pack_tiff(entries)
130
+ end
131
+
132
+ def tiff_header_for_cmyk(hash, data)
133
+ width = hash[:Width]
134
+ height = hash[:Height]
135
+ bpc = hash[:BitsPerComponent] || 8
136
+ img_size = data.bytesize
137
+
138
+ # CMYK needs 10 tags + a 4×SHORT BitsPerSample array
139
+ fields = 10
140
+ bits_array_size = 4 * 2 # 4 channels × 2 bytes each
141
+
142
+ # Size of header + IFD (before the bits array)
143
+ header_ifd_size = 2 + 2 + 4 + 2 + (fields * 12) + 4
144
+ # Where the pixel data will really start:
145
+ data_offset = header_ifd_size + bits_array_size
146
+
147
+ entries = [
148
+ [IMAGE_WIDTH, TYPE_LONG, 1, width], # ImageWidth
149
+ [IMAGE_LENGTH, TYPE_LONG, 1, height], # ImageLength
150
+ [BITS_PER_SAMPLE, TYPE_SHORT, 4, header_ifd_size], # BitsPerSample (pointer to array)
151
+ [COMPRESSION, TYPE_SHORT, 1, COMPRESSION_NONE], # Compression (1 = none)
152
+ [PHOTOMETRIC_INTERPRETATION, TYPE_SHORT, 1, PHOTO_SEPARATION], # PhotometricInterpretation (5 = Separation)
153
+ [STRIP_OFFSETS, TYPE_LONG, 1, data_offset], # StripOffsets
154
+ [SAMPLES_PER_PIXEL, TYPE_SHORT, 1, 4], # SamplesPerPixel
155
+ [STRIP_BYTE_COUNTS, TYPE_LONG, 1, img_size], # StripByteCounts
156
+ [PLANAR_CONFIGURATION, TYPE_SHORT, 1, PLANAR_CHUNKY], # PlanarConfiguration (1 = chunky)
157
+ [INK_SET, TYPE_SHORT, 1, 1] # InkSet (1 = CMYK)
158
+ ]
159
+
160
+ header = pack_tiff(entries)
161
+ # Append the 4-channel BitsPerSample array as little‐endian SHORTs:
162
+ header << [bpc, bpc, bpc, bpc].pack("S<S<S<S<")
163
+ header
164
+ end
165
+
166
+ def tiff_header_for_rgb(hash, data)
167
+ width = hash[:Width]
168
+ height = hash[:Height]
169
+ bpc = hash[:BitsPerComponent] || 8
170
+ img_size = data.bytesize
171
+
172
+ # 8 tags + a 3×SHORT BitsPerSample array
173
+ fields = 8
174
+ bits_array_size = 3 * 2 # 3 channels × 2 bytes each
175
+
176
+ # size of header + IFD before the bits array
177
+ header_ifd_size = 2 + 2 + 4 + 2 + (fields * 12) + 4
178
+ # where the pixel data really starts:
179
+ data_offset = header_ifd_size + bits_array_size
180
+
181
+ entries = [
182
+ [IMAGE_WIDTH, TYPE_LONG, 1, width], # ImageWidth
183
+ [IMAGE_LENGTH, TYPE_LONG, 1, height], # ImageLength
184
+ [BITS_PER_SAMPLE, TYPE_SHORT, 3, header_ifd_size], # BitsPerSample → pointer to our array
185
+ [COMPRESSION, TYPE_SHORT, 1, COMPRESSION_NONE], # Compression (1 = none)
186
+ [PHOTOMETRIC_INTERPRETATION, TYPE_SHORT, 1, PHOTO_RGB], # PhotometricInterpretation (2 = RGB)
187
+ [STRIP_OFFSETS, TYPE_LONG, 1, data_offset], # StripOffsets
188
+ [SAMPLES_PER_PIXEL, TYPE_SHORT, 1, 3], # SamplesPerPixel
189
+ [STRIP_BYTE_COUNTS, TYPE_LONG, 1, img_size] # StripByteCounts
190
+ ]
191
+
192
+ # pack the IFD
193
+ header = pack_tiff(entries)
194
+
195
+ # append the 3-channel BitsPerSample as little-endian SHORTs
196
+ header << [bpc, bpc, bpc].pack("S<S<S<")
197
+
198
+ header
199
+ end
200
+ end
201
+ end
202
+ # rubocop: enable Metrics/ModuleLength, Metrics/AbcSize
203
+ end
204
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ %w[vips dhash-vips].each do |dep|
4
+ require dep
5
+ rescue LoadError
6
+ warn "Missing #{dep}. Add it to your Gemfile if you're using Bidi2pdf image test helpers."
7
+ end
8
+
9
+ require_relative "images/tiff_helper"
10
+ require_relative "images/extractor"
11
+ require_relative "images/image_similarity_checker"
12
+ require_relative "matchers/contains_pdf_image"
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec::Matchers.define :contains_pdf_image do |expected, tolerance: 10|
4
+ chain :at_page do |page_number|
5
+ @page_number = page_number
6
+ end
7
+
8
+ chain :at_position do |i|
9
+ @image_number = i
10
+ end
11
+
12
+ match do |actual_pdf|
13
+ extractor = Bidi2pdf::TestHelpers::Images::Extractor.new(actual_pdf)
14
+ @images = if @page_number
15
+ @image_number ? [extractor.image_on_page(@page_number, @image_number)].compact : extractor.images_on_page(@page_number)
16
+ else
17
+ extractor.all_images
18
+ end
19
+
20
+ @checkers = @images.map { |image| Bidi2pdf::TestHelpers::Images::ImageSimilarityChecker.new(expected, image) }
21
+
22
+ @checkers.any? { |checker| checker.similar?(tolerance:) }
23
+ end
24
+
25
+ failure_message do |_actual_pdf|
26
+ "expected to find one image #{"on page #{@page_number}" if @page_number}#{" at position #{@image_number}" if @image_number} to be perceptually similar (distance ≤ #{tolerance}), " \
27
+ "but Hamming distances have been #{@checkers.map(&:distance).join(", ")}"
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module TestHelpers
5
+ # This module provides helper methods for handling PDF files in tests.
6
+ # It includes methods for debugging, storing, and managing PDF files.
7
+ module PdfFileHelper
8
+ # Executes a block with the given PDF data and handles debugging in case of test failures.
9
+ # If an expectation fails, the PDF data is saved to a file for debugging purposes.
10
+ # @param [String] pdf_data the PDF data to debug
11
+ # @yield [String] yields the PDF data to the given block
12
+ # @raise [RSpec::Expectations::ExpectationNotMetError] re-raises the exception after saving the PDF
13
+ def with_pdf_debug(pdf_data)
14
+ yield pdf_data
15
+ rescue RSpec::Expectations::ExpectationNotMetError => e
16
+ failure_output = store_pdf_file pdf_data, "test-failure"
17
+ puts "Test failed! PDF saved to: #{failure_output}"
18
+ raise e
19
+ end
20
+
21
+ # Stores the given PDF data to a file with a specified filename prefix.
22
+ # The file is saved in a temporary directory.
23
+ # @param [String] pdf_data the PDF data to store
24
+ # @param [String] filename_prefix the prefix for the generated filename (default: "test")
25
+ # @return [String] the full path to the saved PDF file
26
+ def store_pdf_file(pdf_data, filename_prefix = "test")
27
+ pdf_file = tmp_file("pdf-files", "#{filename_prefix}-#{Time.now.to_i}.pdf")
28
+ FileUtils.mkdir_p(File.dirname(pdf_file))
29
+ File.binwrite(pdf_file, pdf_data)
30
+
31
+ pdf_file
32
+ end
33
+ end
34
+
35
+ RSpec.configure do |config|
36
+ config.include PdfFileHelper, pdf: true
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This module provides helper methods for managing paths in test environments.
4
+ # It includes methods to retrieve directories and generate temporary file paths.
5
+ module Bidi2pdf
6
+ module TestHelpers
7
+ # This submodule contains path-related helper methods and configuration for tests.
8
+ module SpecPathsHelper
9
+ # Retrieves the directory path for Docker files.
10
+ # @return [String] the Docker directory path
11
+ def fixture_dir
12
+ TestHelpers.configuration.fixture_dir
13
+ end
14
+
15
+ # Retrieves the directory path for fixtures.
16
+ # @return [String] the fixture directory path
17
+ def fixture_file(*)
18
+ File.join(fixture_dir, *)
19
+ end
20
+
21
+ # Retrieves the directory path for specs.
22
+ # @return [String] the spec directory path
23
+ def spec_dir
24
+ TestHelpers.configuration.spec_dir
25
+ end
26
+
27
+ # Retrieves the directory path for temporary files.
28
+ # @return [String] the temporary directory path
29
+ def tmp_dir
30
+ TestHelpers.configuration.tmp_dir
31
+ end
32
+
33
+ # Generates a path for a temporary file by joining the temporary directory with the given parts.
34
+ # @param [Array<String>] parts the parts of the file path to join
35
+ # @return [String] the full path to the temporary file
36
+ def tmp_file(*parts)
37
+ File.join(tmp_dir, *parts)
38
+ end
39
+
40
+ # Generates a random temporary directory path.
41
+ # @param [Array<String>] dirs additional directory components to include in the path
42
+ # @param [String, nil] prefix an optional prefix for the directory name
43
+ # @return [String] the full path to the random temporary directory
44
+ def random_tmp_dir(*dirs, prefix: nil)
45
+ base_dirs = [tmp_dir] + dirs.compact
46
+ pfx = prefix || TestHelpers.configuration.prefix
47
+ File.join(*base_dirs, "#{pfx}#{SecureRandom.hex(8)}")
48
+ end
49
+ end
50
+
51
+ # Configures RSpec to include and extend SpecPathsHelper for examples with the `:pdf` metadata.
52
+ RSpec.configure do |config|
53
+ # Includes SpecPathsHelper methods in examples with `:pdf` metadata.
54
+ config.include SpecPathsHelper, pdf: true
55
+
56
+ # Extends SpecPathsHelper methods to example groups with `:pdf` metadata.
57
+ config.extend SpecPathsHelper, pdf: true
58
+ end
59
+ end
60
+ end
@@ -1,11 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
4
- require "testcontainers"
5
- rescue LoadError
6
- warn "Missing #{dep}. Add it to your Gemfile if you're using Bidi2pdf test helpers."
7
- end
8
-
9
3
  module Bidi2pdf
10
4
  module TestHelpers
11
5
  module Testcontainers
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "shared_docker_network"
4
+
5
+ module Bidi2pdf
6
+ module TestHelpers
7
+ module Testcontainers
8
+ module ChromedriverTestHelper
9
+ def session_url
10
+ chromedriver_container.session_url
11
+ end
12
+
13
+ def chromedriver_container
14
+ RSpec.configuration.chromedriver_container
15
+ end
16
+ end
17
+
18
+ module SessionTestHelper
19
+ def chrome_args
20
+ chrome_args = Bidi2pdf::Bidi::Session::DEFAULT_CHROME_ARGS.dup
21
+
22
+ # within github actions, the sandbox is not supported, when we start our own container
23
+ # some privileges are not available ???
24
+ if ENV["DISABLE_CHROME_SANDBOX"]
25
+ chrome_args << "--no-sandbox"
26
+
27
+ puts "🚨 Chrome sandbox disabled"
28
+ end
29
+ chrome_args
30
+ end
31
+
32
+ def create_session(session_url)
33
+ Bidi2pdf::Bidi::Session.new(session_url: session_url, headless: true, chrome_args: chrome_args)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ RSpec.configure do |config|
41
+ config.add_setting :chromedriver_container, default: nil
42
+
43
+ config.include Bidi2pdf::TestHelpers::Testcontainers::ChromedriverTestHelper, chromedriver: true
44
+ config.include Bidi2pdf::TestHelpers::Testcontainers::SessionTestHelper, session: true
45
+
46
+ config.before(:suite) do
47
+ if chromedriver_tests_present?
48
+ config.chromedriver_container = start_chromedriver_container(
49
+ build_dir: File.join(Bidi2pdf::TestHelpers.configuration.docker_dir, ".."),
50
+ mounts: config.respond_to?(:chromedriver_mounts) ? config.chromedriver_mounts : {},
51
+ shared_network: config.shared_network
52
+ )
53
+
54
+ puts "🚀 chromedriver container started for tests"
55
+ end
56
+ end
57
+
58
+ config.after(:suite) do
59
+ stop_container config.chromedriver_container
60
+ end
61
+ end
62
+
63
+ def stop_container(container)
64
+ if container&.running?
65
+
66
+ if ENV["SHOW_CONTAINER_LOGS"]
67
+ puts "Container logs:"
68
+ logs_std, logs_error = container.logs
69
+
70
+ puts logs_error
71
+ puts logs_std
72
+ end
73
+
74
+ puts "🧹 #{container.image} stopping container..."
75
+ container.stop
76
+ end
77
+ container&.remove
78
+ end
79
+
80
+ def chromedriver_tests_present?
81
+ test_of_kind_present? :chromedriver
82
+ end
83
+
84
+ def test_of_kind_present?(type)
85
+ RSpec.world.filtered_examples.values.flatten.any? { |example| example.metadata[type] }
86
+ end
87
+
88
+ # alias the long class name
89
+ ChromedriverTestcontainer = Bidi2pdf::TestHelpers::Testcontainers::ChromedriverContainer
90
+
91
+ def start_chromedriver_container(build_dir:, mounts:, shared_network:)
92
+ container = ChromedriverTestcontainer.new(ChromedriverTestcontainer::DEFAULT_IMAGE,
93
+ build_dir: build_dir,
94
+ docker_file: "docker/Dockerfile.chromedriver")
95
+ .with_network(shared_network)
96
+ .with_network_aliases("remote-chrome")
97
+
98
+ container.with_filesystem_binds(mounts) if mounts&.any?
99
+
100
+ container.start
101
+
102
+ container
103
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.configure do |config|
4
+ config.add_setting :shared_network, default: nil
5
+
6
+ config.before(:suite) do
7
+ examples = RSpec.world.filtered_examples.values.flatten
8
+ uses_containers = examples.any? do |ex|
9
+ ex.metadata[:nginx] || ex.metadata[:chrome] || ex.metadata[:chromedriver] || ex.metadata[:container]
10
+ end
11
+
12
+ if uses_containers
13
+ config.shared_network = Docker::Network.create("bidi2pdf-test-net-#{SecureRandom.hex(4)}")
14
+ puts "🕸️ started shared network #{config.shared_network}"
15
+ end
16
+ end
17
+
18
+ config.after(:suite) do
19
+ config.shared_network&.remove
20
+ end
21
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bidi2pdf
4
+ module TestHelpers
5
+ module TestcontainersRefinement
6
+ def id
7
+ @_id
8
+ end
9
+
10
+ def aliases
11
+ @aliases ||= []
12
+ end
13
+
14
+ def aliases=(aliases)
15
+ @aliases = aliases
16
+ end
17
+
18
+ def network
19
+ @_network
20
+ end
21
+
22
+ def with_network(network)
23
+ @_network = network
24
+ self
25
+ end
26
+
27
+ def with_network_aliases(*aliases)
28
+ self.aliases += aliases
29
+ self
30
+ end
31
+
32
+ def _container_create_options
33
+ opts = super
34
+ network_name = network&.info&.[]("Name")
35
+ opts["HostConfig"]["NetworkMode"] = network_name
36
+
37
+ if network && aliases.any?
38
+ opts["NetworkingConfig"] = {
39
+ "EndpointsConfig" => {
40
+ network_name => {
41
+ "Aliases" => aliases
42
+ }
43
+ }
44
+ }
45
+ end
46
+
47
+ opts.compact
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ Testcontainers::DockerContainer.prepend(Bidi2pdf::TestHelpers::TestcontainersRefinement)
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ %w[docker testcontainers].each do |dep|
4
+ require dep
5
+ rescue LoadError
6
+ warn "Missing #{dep}. Add it to your Gemfile if you're using Bidi2pdf test helpers."
7
+ end
8
+
9
+ module Bidi2pdf
10
+ module TestHelpers
11
+ module Testcontainers
12
+ require_relative "testcontainers/testcontainers_refinement"
13
+ require_relative "testcontainers/chromedriver_container"
14
+ require_relative "testcontainers/chromedriver_test_helper"
15
+ end
16
+ end
17
+ end
@@ -6,8 +6,15 @@ rescue LoadError
6
6
  warn "Missing #{dep}. Add it to your Gemfile if you're using Bidi2pdf test helpers."
7
7
  end
8
8
 
9
+ require "bidi2pdf/test_helpers/configuration"
10
+ require "bidi2pdf/test_helpers/pdf_file_helper"
11
+ require "bidi2pdf/test_helpers/spec_paths_helper"
9
12
  require "bidi2pdf/test_helpers/pdf_text_sanitizer"
10
13
  require "bidi2pdf/test_helpers/pdf_reader_utils"
11
14
  require "bidi2pdf/test_helpers/matchers/match_pdf_text"
12
15
  require "bidi2pdf/test_helpers/matchers/contains_pdf_text"
13
16
  require "bidi2pdf/test_helpers/matchers/have_pdf_page_count"
17
+
18
+ # don't require "bidi2pdf/test_helpers/matchers/contains_pdf_image.rb" directly, use
19
+ # require "bidi2pdf/test_helpers/images" instead, because it requires
20
+ # ruby-vips and dhash-vips, and not every one wants to use them
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bidi2pdf
4
- VERSION = "0.1.8"
4
+ VERSION = "0.1.10"
5
5
  end
@@ -1,29 +1,35 @@
1
1
  module Bidi2pdf
2
2
  module Bidi
3
3
  class EventManager
4
- @browser: Bidi2pdf::Bidi::Browser
5
- @listeners: Hash[String, Array[Proc]]
6
- @active: bool
4
+ class Listener
5
+ attr_reader block: untyped
6
+ attr_reader id: String
7
7
 
8
- def initialize: (browser: Bidi2pdf::Bidi::Browser) -> void
8
+ def initialize: (untyped block, ?String id) -> void
9
9
 
10
- def on: (String event_name) { (Hash[String, untyped]) -> void } -> void
10
+ def call: (*untyped args) -> untyped
11
11
 
12
- def off: (String event_name) -> void
12
+ def ==: (untyped other) -> bool
13
13
 
14
- def emit: (String event_name, Hash[String, untyped] params) -> void
14
+ def eql?: (untyped other) -> bool
15
15
 
16
- def start_listening: () -> void
16
+ def hash: () -> Integer
17
+ end
17
18
 
18
- def stop_listening: () -> void
19
+ @listeners: untyped
20
+ @type: untyped
19
21
 
20
- private
22
+ attr_reader type: untyped
21
23
 
22
- def register_browser_events: () -> void
24
+ def initialize: (untyped type) -> void
23
25
 
24
- def handle_event: (String event_name, Hash[String, untyped] params) -> void
26
+ def on: (*untyped event_names, &untyped block) -> Listener
25
27
 
26
- def process_event: (String event_name, Hash[String, untyped] params) -> void
28
+ def off: (untyped event_name, Listener listener) -> void
29
+
30
+ def dispatch: (untyped event_name, *untyped args) -> void
31
+
32
+ def clear: (?untyped event_name) -> void
27
33
  end
28
34
  end
29
35
  end