canon 0.1.3 → 0.1.5

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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -1
  3. data/.rubocop_todo.yml +276 -7
  4. data/README.adoc +203 -138
  5. data/_config.yml +116 -0
  6. data/docs/ADVANCED_TOPICS.adoc +20 -0
  7. data/docs/BASIC_USAGE.adoc +16 -0
  8. data/docs/CHARACTER_VISUALIZATION.adoc +567 -0
  9. data/docs/CLI.adoc +493 -0
  10. data/docs/CUSTOMIZING_BEHAVIOR.adoc +19 -0
  11. data/docs/DIFF_ARCHITECTURE.adoc +435 -0
  12. data/docs/DIFF_FORMATTING.adoc +540 -0
  13. data/docs/FORMATS.adoc +447 -0
  14. data/docs/INDEX.adoc +222 -0
  15. data/docs/INPUT_VALIDATION.adoc +477 -0
  16. data/docs/MATCH_ARCHITECTURE.adoc +463 -0
  17. data/docs/MATCH_OPTIONS.adoc +719 -0
  18. data/docs/MODES.adoc +432 -0
  19. data/docs/NORMATIVE_INFORMATIVE_DIFFS.adoc +219 -0
  20. data/docs/OPTIONS.adoc +1387 -0
  21. data/docs/PREPROCESSING.adoc +491 -0
  22. data/docs/RSPEC.adoc +605 -0
  23. data/docs/RUBY_API.adoc +478 -0
  24. data/docs/SEMANTIC_DIFF_REPORT.adoc +528 -0
  25. data/docs/UNDERSTANDING_CANON.adoc +17 -0
  26. data/docs/VERBOSE.adoc +482 -0
  27. data/exe/canon +7 -0
  28. data/lib/canon/cli.rb +179 -0
  29. data/lib/canon/commands/diff_command.rb +195 -0
  30. data/lib/canon/commands/format_command.rb +113 -0
  31. data/lib/canon/comparison/base_comparator.rb +39 -0
  32. data/lib/canon/comparison/comparison_result.rb +79 -0
  33. data/lib/canon/comparison/html_comparator.rb +410 -0
  34. data/lib/canon/comparison/json_comparator.rb +212 -0
  35. data/lib/canon/comparison/match_options.rb +616 -0
  36. data/lib/canon/comparison/xml_comparator.rb +566 -0
  37. data/lib/canon/comparison/yaml_comparator.rb +93 -0
  38. data/lib/canon/comparison.rb +239 -0
  39. data/lib/canon/config.rb +172 -0
  40. data/lib/canon/diff/diff_block.rb +71 -0
  41. data/lib/canon/diff/diff_block_builder.rb +105 -0
  42. data/lib/canon/diff/diff_classifier.rb +46 -0
  43. data/lib/canon/diff/diff_context.rb +85 -0
  44. data/lib/canon/diff/diff_context_builder.rb +107 -0
  45. data/lib/canon/diff/diff_line.rb +77 -0
  46. data/lib/canon/diff/diff_node.rb +56 -0
  47. data/lib/canon/diff/diff_node_mapper.rb +148 -0
  48. data/lib/canon/diff/diff_report.rb +133 -0
  49. data/lib/canon/diff/diff_report_builder.rb +62 -0
  50. data/lib/canon/diff_formatter/by_line/base_formatter.rb +407 -0
  51. data/lib/canon/diff_formatter/by_line/html_formatter.rb +672 -0
  52. data/lib/canon/diff_formatter/by_line/json_formatter.rb +284 -0
  53. data/lib/canon/diff_formatter/by_line/simple_formatter.rb +190 -0
  54. data/lib/canon/diff_formatter/by_line/xml_formatter.rb +860 -0
  55. data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +292 -0
  56. data/lib/canon/diff_formatter/by_object/base_formatter.rb +199 -0
  57. data/lib/canon/diff_formatter/by_object/json_formatter.rb +305 -0
  58. data/lib/canon/diff_formatter/by_object/xml_formatter.rb +248 -0
  59. data/lib/canon/diff_formatter/by_object/yaml_formatter.rb +17 -0
  60. data/lib/canon/diff_formatter/character_map.yml +197 -0
  61. data/lib/canon/diff_formatter/debug_output.rb +431 -0
  62. data/lib/canon/diff_formatter/diff_detail_formatter.rb +551 -0
  63. data/lib/canon/diff_formatter/legend.rb +141 -0
  64. data/lib/canon/diff_formatter.rb +520 -0
  65. data/lib/canon/errors.rb +56 -0
  66. data/lib/canon/formatters/html4_formatter.rb +17 -0
  67. data/lib/canon/formatters/html5_formatter.rb +17 -0
  68. data/lib/canon/formatters/html_formatter.rb +37 -0
  69. data/lib/canon/formatters/html_formatter_base.rb +163 -0
  70. data/lib/canon/formatters/json_formatter.rb +3 -0
  71. data/lib/canon/formatters/xml_formatter.rb +20 -55
  72. data/lib/canon/formatters/yaml_formatter.rb +4 -1
  73. data/lib/canon/pretty_printer/html.rb +57 -0
  74. data/lib/canon/pretty_printer/json.rb +25 -0
  75. data/lib/canon/pretty_printer/xml.rb +29 -0
  76. data/lib/canon/rspec_matchers.rb +222 -80
  77. data/lib/canon/validators/base_validator.rb +49 -0
  78. data/lib/canon/validators/html_validator.rb +138 -0
  79. data/lib/canon/validators/json_validator.rb +89 -0
  80. data/lib/canon/validators/xml_validator.rb +53 -0
  81. data/lib/canon/validators/yaml_validator.rb +73 -0
  82. data/lib/canon/version.rb +1 -1
  83. data/lib/canon/xml/attribute_handler.rb +80 -0
  84. data/lib/canon/xml/c14n.rb +36 -0
  85. data/lib/canon/xml/character_encoder.rb +38 -0
  86. data/lib/canon/xml/data_model.rb +225 -0
  87. data/lib/canon/xml/element_matcher.rb +196 -0
  88. data/lib/canon/xml/line_range_mapper.rb +158 -0
  89. data/lib/canon/xml/namespace_handler.rb +86 -0
  90. data/lib/canon/xml/node.rb +32 -0
  91. data/lib/canon/xml/nodes/attribute_node.rb +54 -0
  92. data/lib/canon/xml/nodes/comment_node.rb +23 -0
  93. data/lib/canon/xml/nodes/element_node.rb +56 -0
  94. data/lib/canon/xml/nodes/namespace_node.rb +38 -0
  95. data/lib/canon/xml/nodes/processing_instruction_node.rb +24 -0
  96. data/lib/canon/xml/nodes/root_node.rb +16 -0
  97. data/lib/canon/xml/nodes/text_node.rb +23 -0
  98. data/lib/canon/xml/processor.rb +151 -0
  99. data/lib/canon/xml/whitespace_normalizer.rb +72 -0
  100. data/lib/canon/xml/xml_base_handler.rb +188 -0
  101. data/lib/canon.rb +14 -3
  102. metadata +116 -21
data/docs/RSPEC.adoc ADDED
@@ -0,0 +1,605 @@
1
+ ---
2
+ layout: default
3
+ title: RSpec Matchers
4
+ nav_order: 12
5
+ parent: Basic Usage
6
+ ---
7
+ = Canon RSpec matchers
8
+ :toc:
9
+ :toclevels: 3
10
+
11
+ == Scope
12
+
13
+ This document describes how to use Canon's RSpec matchers for testing. Canon
14
+ provides semantic comparison matchers that focus on content rather than
15
+ formatting.
16
+
17
+ For Ruby API usage, see link:RUBY_API[Ruby API documentation].
18
+
19
+ For command-line usage, see link:CLI[CLI documentation].
20
+
21
+ == General
22
+
23
+ Canon's RSpec matchers use canonical (c14n) mode for comparison, ensuring that
24
+ formatting differences don't affect test results. Tests focus on semantic
25
+ equivalence rather than exact string matching.
26
+
27
+ == Installation
28
+
29
+ Add to your `spec_helper.rb` or `spec/support/canon.rb`:
30
+
31
+ [source,ruby]
32
+ ----
33
+ require 'canon/rspec_matchers'
34
+ ----
35
+
36
+ == Basic usage
37
+
38
+ === Unified matcher
39
+
40
+ The `be_serialization_equivalent_to` matcher works with all formats:
41
+
42
+ .Unified matcher syntax
43
+ [example]
44
+ ====
45
+ [source,ruby]
46
+ ----
47
+ require 'canon/rspec_matchers'
48
+
49
+ RSpec.describe 'Serialization' do
50
+ it 'compares XML' do
51
+ xml1 = '<root><a>1</a><b>2</b></root>'
52
+ xml2 = '<root> <b>2</b> <a>1</a> </root>'
53
+ expect(xml1).to be_serialization_equivalent_to(xml2, format: :xml)
54
+ end
55
+
56
+ it 'compares HTML' do
57
+ html1 = '<div><p>Hello</p></div>'
58
+ html2 = '<div> <p> Hello </p> </div>'
59
+ expect(html1).to be_serialization_equivalent_to(html2, format: :html)
60
+ end
61
+
62
+ it 'compares JSON' do
63
+ json1 = '{"a":1,"b":2}'
64
+ json2 = '{"b":2,"a":1}'
65
+ expect(json1).to be_serialization_equivalent_to(json2, format: :json)
66
+ end
67
+
68
+ it 'compares YAML' do
69
+ yaml1 = "a: 1\nb: 2"
70
+ yaml2 = "b: 2\na: 1"
71
+ expect(yaml1).to be_serialization_equivalent_to(yaml2, format: :yaml)
72
+ end
73
+ end
74
+ ----
75
+ ====
76
+
77
+ === Format-specific matchers
78
+
79
+ Canon provides dedicated matchers for each format:
80
+
81
+ .Format-specific matchers
82
+ [example]
83
+ ====
84
+ [source,ruby]
85
+ ----
86
+ RSpec.describe 'Format-specific matchers' do
87
+ it 'uses XML matcher' do
88
+ expect(actual_xml).to be_xml_equivalent_to(expected_xml)
89
+ # or legacy alias
90
+ expect(actual_xml).to be_analogous_with(expected_xml)
91
+ end
92
+
93
+ it 'uses HTML matcher' do
94
+ expect(actual_html).to be_html_equivalent_to(expected_html)
95
+ end
96
+
97
+ it 'uses JSON matcher' do
98
+ expect(actual_json).to be_json_equivalent_to(expected_json)
99
+ end
100
+
101
+ it 'uses YAML matcher' do
102
+ expect(actual_yaml).to be_yaml_equivalent_to(expected_yaml)
103
+ end
104
+ end
105
+ ----
106
+ ====
107
+
108
+ == Global configuration
109
+
110
+ Configure matchers globally in your `spec_helper.rb`:
111
+
112
+ === Syntax
113
+
114
+ [source,ruby]
115
+ ----
116
+ Canon::RSpecMatchers.configure do |config|
117
+ config.<format>.match.profile = :profile_name
118
+ config.<format>.match.options = { ... }
119
+ config.<format>.diff.mode = :mode_name
120
+ config.<format>.diff.use_color = true/false
121
+ config.<format>.diff.context_lines = n
122
+ config.<format>.diff.grouping_lines = n
123
+ end
124
+ ----
125
+
126
+ Where `<format>` is one of: `xml`, `html`, `json`, `yaml`
127
+
128
+ === Match configuration
129
+
130
+ .Match profiles
131
+ [example]
132
+ ====
133
+ [source,ruby]
134
+ ----
135
+ # spec_helper.rb
136
+ Canon::RSpecMatchers.configure do |config|
137
+ # Use spec_friendly profile for XML
138
+ config.xml.match.profile = :spec_friendly
139
+
140
+ # Use rendered profile for HTML
141
+ config.html.match.profile = :rendered
142
+
143
+ # Use strict profile for JSON
144
+ config.json.match.profile = :strict
145
+ end
146
+ ----
147
+
148
+ Available profiles: `:strict`, `:rendered`, `:spec_friendly`, `:content_only`
149
+
150
+ See link:MATCH_OPTIONS[Match options] for profile details.
151
+ ====
152
+
153
+ .Match dimension options
154
+ [example]
155
+ ====
156
+ [source,ruby]
157
+ ----
158
+ # spec_helper.rb
159
+ Canon::RSpecMatchers.configure do |config|
160
+ config.xml.match.options = {
161
+ text_content: :normalize,
162
+ structural_whitespace: :ignore,
163
+ attribute_order: :ignore,
164
+ comments: :ignore
165
+ }
166
+
167
+ config.html.match.options = {
168
+ text_content: :normalize,
169
+ structural_whitespace: :ignore
170
+ }
171
+
172
+ config.json.match.options = {
173
+ key_order: :ignore
174
+ }
175
+ end
176
+ ----
177
+
178
+ See link:MATCH_OPTIONS[Match options] for dimension reference.
179
+ ====
180
+
181
+ === Diff configuration
182
+
183
+ .Diff mode and colors
184
+ [example]
185
+ ====
186
+ [source,ruby]
187
+ ----
188
+ # spec_helper.rb
189
+ Canon::RSpecMatchers.configure do |config|
190
+ # XML with by-line mode
191
+ config.xml.diff.mode = :by_line
192
+ config.xml.diff.use_color = true
193
+
194
+ # HTML with by-line mode
195
+ config.html.diff.mode = :by_line
196
+
197
+ # JSON with by-object mode (default)
198
+ config.json.diff.mode = :by_object
199
+ config.json.diff.use_color = true
200
+ end
201
+ ----
202
+
203
+ See link:MODES[Diff modes] for mode details.
204
+ ====
205
+
206
+ .Diff formatting
207
+ [example]
208
+ ====
209
+ [source,ruby]
210
+ ----
211
+ # spec_helper.rb
212
+ Canon::RSpecMatchers.configure do |config|
213
+ # Show 5 lines of context
214
+ config.xml.diff.context_lines = 5
215
+
216
+ # Group changes within 10 lines
217
+ config.xml.diff.grouping_lines = 10
218
+
219
+ # Disable colors for CI environments
220
+ config.xml.diff.use_color = !ENV['CI']
221
+ end
222
+ ----
223
+
224
+ See link:DIFF_FORMATTING[Diff formatting] for options.
225
+ ====
226
+
227
+ === Complete configuration example
228
+
229
+ .Full configuration
230
+ [example]
231
+ ====
232
+ [source,ruby]
233
+ ----
234
+ # spec_helper.rb
235
+ require 'canon/rspec_matchers'
236
+
237
+ Canon::RSpecMatchers.configure do |config|
238
+ # XML configuration
239
+ config.xml.match.profile = :spec_friendly
240
+ config.xml.match.options = {
241
+ text_content: :normalize,
242
+ structural_whitespace: :ignore,
243
+ comments: :ignore
244
+ }
245
+ config.xml.diff.mode = :by_line
246
+ config.xml.diff.use_color = true
247
+ config.xml.diff.context_lines = 3
248
+
249
+ # HTML configuration
250
+ config.html.match.profile = :rendered
251
+ config.html.diff.mode = :by_line
252
+ config.html.diff.grouping_lines = 2
253
+
254
+ # JSON configuration
255
+ config.json.match.profile = :spec_friendly
256
+ config.json.match.options = {
257
+ key_order: :ignore
258
+ }
259
+ config.json.diff.mode = :by_object
260
+ config.json.diff.context_lines = 5
261
+
262
+ # YAML configuration
263
+ config.yaml.match.options = {
264
+ key_order: :ignore
265
+ }
266
+ end
267
+ ----
268
+ ====
269
+
270
+ == Per-test configuration
271
+
272
+ Override global configuration on a per-test basis using matcher options.
273
+
274
+ === Using match profiles
275
+
276
+ .Override with profile
277
+ [example]
278
+ ====
279
+ [source,ruby]
280
+ ----
281
+ RSpec.describe 'Override global config' do
282
+ it 'uses strict matching for this test' do
283
+ expect(actual_xml).to be_xml_equivalent_to(expected_xml)
284
+ .with_profile(:strict)
285
+ end
286
+
287
+ it 'uses rendered profile for HTML' do
288
+ expect(actual_html).to be_html_equivalent_to(expected_html)
289
+ .with_profile(:rendered)
290
+ end
291
+ end
292
+ ----
293
+ ====
294
+
295
+ === Using match options
296
+
297
+ .Override with specific dimensions
298
+ [example]
299
+ ====
300
+ [source,ruby]
301
+ ----
302
+ RSpec.describe 'Override specific dimensions' do
303
+ it 'requires strict whitespace for this test' do
304
+ expect(actual_xml).to be_xml_equivalent_to(expected_xml)
305
+ .with_options(
306
+ structural_whitespace: :strict,
307
+ text_content: :strict
308
+ )
309
+ end
310
+
311
+ it 'ignores attribute order' do
312
+ expect(actual_xml).to be_xml_equivalent_to(expected_xml)
313
+ .with_options(attribute_order: :ignore)
314
+ end
315
+ end
316
+ ----
317
+ ====
318
+
319
+ === Combining profile and options
320
+
321
+ .Profile with overrides
322
+ [example]
323
+ ====
324
+ [source,ruby]
325
+ ----
326
+ RSpec.describe 'Combine profile and options' do
327
+ it 'uses spec_friendly but checks comments' do
328
+ expect(actual_xml).to be_xml_equivalent_to(expected_xml)
329
+ .with_profile(:spec_friendly)
330
+ .with_options(comments: :strict)
331
+ end
332
+
333
+ it 'uses rendered but requires strict text' do
334
+ expect(actual_html).to be_html_equivalent_to(expected_html)
335
+ .with_profile(:rendered)
336
+ .with_options(text_content: :strict)
337
+ end
338
+ end
339
+ ----
340
+ ====
341
+
342
+ == Verbose output
343
+
344
+ Use `verbose: true` to show detailed diff output on test failures.
345
+
346
+ .Verbose diff examples
347
+ [example]
348
+ ====
349
+ [source,ruby]
350
+ ----
351
+ RSpec.describe 'Verbose diff output' do
352
+ it 'shows detailed XML diff on failure' do
353
+ expect(actual_xml).to be_xml_equivalent_to(expected_xml, verbose: true)
354
+ end
355
+
356
+ it 'shows detailed HTML diff on failure' do
357
+ expect(actual_html).to be_html_equivalent_to(expected_html, verbose: true)
358
+ end
359
+
360
+ it 'shows detailed JSON diff on failure' do
361
+ expect(actual_json).to be_json_equivalent_to(expected_json, verbose: true)
362
+ end
363
+
364
+ # Combine with profile and options
365
+ it 'shows verbose diff with custom options' do
366
+ expect(actual_xml).to be_xml_equivalent_to(expected_xml, verbose: true)
367
+ .with_profile(:spec_friendly)
368
+ .with_options(comments: :strict)
369
+ end
370
+ end
371
+ ----
372
+ ====
373
+
374
+ == Common patterns
375
+
376
+ === Testing XML generation
377
+
378
+ .XML generation tests
379
+ [example]
380
+ ====
381
+ [source,ruby]
382
+ ----
383
+ RSpec.describe 'XML generation' do
384
+ let(:expected_xml) do
385
+ <<~XML
386
+ <document>
387
+ <title>Test Document</title>
388
+ <content>
389
+ <p>Paragraph content</p>
390
+ </content>
391
+ </document>
392
+ XML
393
+ end
394
+
395
+ it 'generates correct XML structure' do
396
+ actual_xml = generate_xml(title: 'Test Document',
397
+ content: '<p>Paragraph content</p>')
398
+
399
+ expect(actual_xml).to be_xml_equivalent_to(expected_xml)
400
+ end
401
+
402
+ it 'handles attributes correctly' do
403
+ actual = generate_element(id: '123', class: 'active')
404
+ expected = '<element class="active" id="123"/>'
405
+
406
+ # Attribute order doesn't matter with default config
407
+ expect(actual).to be_xml_equivalent_to(expected)
408
+ end
409
+ end
410
+ ----
411
+ ====
412
+
413
+ === Testing HTML output
414
+
415
+ .HTML output tests
416
+ [example]
417
+ ====
418
+ [source,ruby]
419
+ ----
420
+ RSpec.describe 'HTML generation' do
421
+ let(:expected_html) do
422
+ <<~HTML
423
+ <!DOCTYPE html>
424
+ <html>
425
+ <head>
426
+ <title>Test Page</title>
427
+ </head>
428
+ <body>
429
+ <h1>Welcome</h1>
430
+ <p>Content here</p>
431
+ </body>
432
+ </html>
433
+ HTML
434
+ end
435
+
436
+ it 'generates correct HTML' do
437
+ actual_html = generate_page(
438
+ title: 'Test Page',
439
+ heading: 'Welcome',
440
+ content: 'Content here'
441
+ )
442
+
443
+ expect(actual_html).to be_html_equivalent_to(expected_html)
444
+ end
445
+
446
+ # Formatting differences are ignored
447
+ it 'ignores whitespace differences' do
448
+ compact = '<div><p>Text</p></div>'
449
+ pretty = <<~HTML
450
+ <div>
451
+ <p>Text</p>
452
+ </div>
453
+ HTML
454
+
455
+ expect(compact).to be_html_equivalent_to(pretty)
456
+ end
457
+ end
458
+ ----
459
+ ====
460
+
461
+ === Testing JSON APIs
462
+
463
+ .JSON API tests
464
+ [example]
465
+ ====
466
+ [source,ruby]
467
+ ----
468
+ RSpec.describe 'API responses' do
469
+ let(:expected_response) do
470
+ {
471
+ "user" => {
472
+ "id" => 123,
473
+ "name" => "John Doe",
474
+ "email" => "john@example.com"
475
+ },
476
+ "status" => "success"
477
+ }.to_json
478
+ end
479
+
480
+ it 'returns correct user data' do
481
+ response = api_client.get_user(123)
482
+
483
+ expect(response).to be_json_equivalent_to(expected_response)
484
+ end
485
+
486
+ # Key order doesn't matter with default config
487
+ it 'ignores key ordering' do
488
+ actual = '{"b":2,"a":1}'
489
+ expected = '{"a":1,"b":2}'
490
+
491
+ expect(actual).to be_json_equivalent_to(expected)
492
+ end
493
+ end
494
+ ----
495
+ ====
496
+
497
+ === Testing configuration files
498
+
499
+ .Configuration file tests
500
+ [example]
501
+ ====
502
+ [source,ruby]
503
+ ----
504
+ RSpec.describe 'Configuration files' do
505
+ it 'generates correct YAML config' do
506
+ config = generate_config(
507
+ database: { host: 'localhost', port: 5432 },
508
+ logging: { level: 'info' }
509
+ )
510
+
511
+ expected = <<~YAML
512
+ database:
513
+ host: localhost
514
+ port: 5432
515
+ logging:
516
+ level: info
517
+ YAML
518
+
519
+ expect(config).to be_yaml_equivalent_to(expected)
520
+ end
521
+
522
+ it 'handles nested structures' do
523
+ actual_yaml = to_yaml(deeply: { nested: { structure: 'value' } })
524
+ expected_yaml = "deeply:\n nested:\n structure: value"
525
+
526
+ expect(actual_yaml).to be_yaml_equivalent_to(expected_yaml)
527
+ end
528
+ end
529
+ ----
530
+ ====
531
+
532
+ == Troubleshooting
533
+
534
+ === Debugging test failures
535
+
536
+ When a test fails, Canon shows a detailed diff. Use verbose mode for maximum
537
+ detail:
538
+
539
+ .Debugging example
540
+ [example]
541
+ ====
542
+ [source,ruby]
543
+ ----
544
+ it 'shows exactly what differs' do
545
+ expect(actual).to be_xml_equivalent_to(expected, verbose: true)
546
+ .with_profile(:strict) # Use strict to see all differences
547
+ end
548
+ ----
549
+
550
+ The diff output will show:
551
+
552
+ * Line numbers
553
+ * Color-coded changes (red/green)
554
+ * Whitespace visualization
555
+ * Non-ASCII character warnings
556
+ ====
557
+
558
+ === Temporarily disabling global config
559
+
560
+ .Override global config
561
+ [example]
562
+ ====
563
+ [source,ruby]
564
+ ----
565
+ it 'uses different config for this test' do
566
+ # Global config uses spec_friendly, but we want strict here
567
+ expect(actual).to be_xml_equivalent_to(expected)
568
+ .with_profile(:strict)
569
+ .with_options(
570
+ text_content: :strict,
571
+ structural_whitespace: :strict
572
+ )
573
+ end
574
+ ----
575
+ ====
576
+
577
+ === Checking what's being compared
578
+
579
+ .Debug preprocessed content
580
+ [example]
581
+ ====
582
+ [source,ruby]
583
+ ----
584
+ # In your test, temporarily add:
585
+ result = Canon::Comparison.equivalent?(actual, expected, verbose: true)
586
+
587
+ # Inspect preprocessed content
588
+ puts "Preprocessed actual:"
589
+ puts result[:preprocessed][0]
590
+
591
+ puts "Preprocessed expected:"
592
+ puts result[:preprocessed][1]
593
+
594
+ puts "Differences:"
595
+ pp result[:differences]
596
+ ----
597
+ ====
598
+
599
+ == See also
600
+
601
+ * link:RUBY_API[Ruby API documentation]
602
+ * link:CLI[Command-line interface]
603
+ * link:MATCH_OPTIONS[Match options reference]
604
+ * link:MODES[Diff modes]
605
+ * link:DIFF_FORMATTING[Diff formatting options]