canon 0.1.8 → 0.1.9

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +112 -25
  3. data/docs/Gemfile +1 -0
  4. data/docs/_config.yml +90 -1
  5. data/docs/advanced/diff-classification.adoc +82 -2
  6. data/docs/features/match-options/index.adoc +239 -1
  7. data/lib/canon/comparison/format_detector.rb +2 -1
  8. data/lib/canon/comparison/html_comparator.rb +19 -8
  9. data/lib/canon/comparison/html_compare_profile.rb +8 -2
  10. data/lib/canon/comparison/match_options/base_resolver.rb +7 -0
  11. data/lib/canon/comparison/whitespace_sensitivity.rb +208 -0
  12. data/lib/canon/comparison/xml_comparator/child_comparison.rb +15 -7
  13. data/lib/canon/comparison/xml_comparator/node_parser.rb +10 -5
  14. data/lib/canon/comparison/xml_comparator/node_type_comparator.rb +14 -7
  15. data/lib/canon/comparison/xml_comparator.rb +48 -23
  16. data/lib/canon/comparison/xml_node_comparison.rb +25 -3
  17. data/lib/canon/diff/diff_classifier.rb +101 -2
  18. data/lib/canon/diff/formatting_detector.rb +1 -1
  19. data/lib/canon/rspec_matchers.rb +37 -8
  20. data/lib/canon/version.rb +1 -1
  21. data/lib/canon/xml/data_model.rb +24 -13
  22. metadata +3 -78
  23. data/docs/plans/2025-01-17-html-parser-selection-fix.adoc +0 -250
  24. data/false_positive_analysis.txt +0 -0
  25. data/file1.html +0 -1
  26. data/file2.html +0 -1
  27. data/old-docs/ADVANCED_TOPICS.adoc +0 -20
  28. data/old-docs/BASIC_USAGE.adoc +0 -16
  29. data/old-docs/CHARACTER_VISUALIZATION.adoc +0 -567
  30. data/old-docs/CLI.adoc +0 -497
  31. data/old-docs/CUSTOMIZING_BEHAVIOR.adoc +0 -19
  32. data/old-docs/DIFF_ARCHITECTURE.adoc +0 -435
  33. data/old-docs/DIFF_FORMATTING.adoc +0 -540
  34. data/old-docs/DIFF_PARAMETERS.adoc +0 -261
  35. data/old-docs/DOM_DIFF.adoc +0 -1017
  36. data/old-docs/ENV_CONFIG.adoc +0 -876
  37. data/old-docs/FORMATS.adoc +0 -867
  38. data/old-docs/INPUT_VALIDATION.adoc +0 -477
  39. data/old-docs/MATCHER_BEHAVIOR.adoc +0 -90
  40. data/old-docs/MATCH_ARCHITECTURE.adoc +0 -463
  41. data/old-docs/MATCH_OPTIONS.adoc +0 -912
  42. data/old-docs/MODES.adoc +0 -432
  43. data/old-docs/NORMATIVE_INFORMATIVE_DIFFS.adoc +0 -219
  44. data/old-docs/OPTIONS.adoc +0 -1387
  45. data/old-docs/PREPROCESSING.adoc +0 -491
  46. data/old-docs/README.old.adoc +0 -2831
  47. data/old-docs/RSPEC.adoc +0 -814
  48. data/old-docs/RUBY_API.adoc +0 -485
  49. data/old-docs/SEMANTIC_DIFF_REPORT.adoc +0 -646
  50. data/old-docs/SEMANTIC_TREE_DIFF.adoc +0 -765
  51. data/old-docs/STRING_COMPARE.adoc +0 -345
  52. data/old-docs/TMP.adoc +0 -3384
  53. data/old-docs/TREE_DIFF.adoc +0 -1080
  54. data/old-docs/UNDERSTANDING_CANON.adoc +0 -17
  55. data/old-docs/VERBOSE.adoc +0 -482
  56. data/old-docs/VISUALIZATION_MAP.adoc +0 -625
  57. data/old-docs/WHITESPACE_TREATMENT.adoc +0 -1155
  58. data/scripts/analyze_current_state.rb +0 -85
  59. data/scripts/analyze_false_positives.rb +0 -114
  60. data/scripts/analyze_remaining_failures.rb +0 -105
  61. data/scripts/compare_current_failures.rb +0 -95
  62. data/scripts/compare_dom_tree_diff.rb +0 -158
  63. data/scripts/compare_failures.rb +0 -151
  64. data/scripts/debug_attribute_extraction.rb +0 -66
  65. data/scripts/debug_blocks_839.rb +0 -115
  66. data/scripts/debug_meta_matching.rb +0 -52
  67. data/scripts/debug_p_matching.rb +0 -192
  68. data/scripts/debug_signature_matching.rb +0 -118
  69. data/scripts/debug_sourcecode_124.rb +0 -32
  70. data/scripts/debug_whitespace_sensitive.rb +0 -192
  71. data/scripts/extract_false_positives.rb +0 -138
  72. data/scripts/find_actual_false_positives.rb +0 -125
  73. data/scripts/investigate_all_false_positives.rb +0 -161
  74. data/scripts/investigate_batch1.rb +0 -127
  75. data/scripts/investigate_classification.rb +0 -150
  76. data/scripts/investigate_classification_detailed.rb +0 -190
  77. data/scripts/investigate_common_failures.rb +0 -342
  78. data/scripts/investigate_false_negative.rb +0 -80
  79. data/scripts/investigate_false_positive.rb +0 -83
  80. data/scripts/investigate_false_positives.rb +0 -227
  81. data/scripts/investigate_false_positives_batch.rb +0 -163
  82. data/scripts/investigate_mixed_content.rb +0 -125
  83. data/scripts/investigate_remaining_16.rb +0 -214
  84. data/scripts/run_single_test.rb +0 -29
  85. data/scripts/test_all_false_positives.rb +0 -95
  86. data/scripts/test_attribute_details.rb +0 -61
  87. data/scripts/test_both_algorithms.rb +0 -49
  88. data/scripts/test_both_simple.rb +0 -49
  89. data/scripts/test_enhanced_semantic_output.rb +0 -125
  90. data/scripts/test_readme_examples.rb +0 -131
  91. data/scripts/test_semantic_tree_diff.rb +0 -99
  92. data/scripts/test_semantic_ux_improvements.rb +0 -135
  93. data/scripts/test_single_false_positive.rb +0 -119
  94. data/scripts/test_size_limits.rb +0 -99
  95. data/test_html_1.html +0 -21
  96. data/test_html_2.html +0 -21
  97. data/test_nokogiri.rb +0 -33
  98. data/test_normalize.rb +0 -45
data/old-docs/RSPEC.adoc DELETED
@@ -1,814 +0,0 @@
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
- [source,ruby]
343
- ----
344
- RSpec.describe 'Verbose diff output' do
345
- it 'shows detailed XML diff on failure' do
346
- expect(actual_xml).to be_xml_equivalent_to(expected_xml, verbose: true)
347
- end
348
-
349
- it 'shows detailed HTML diff on failure' do
350
- expect(actual_html).to be_html_equivalent_to(expected_html, verbose: true)
351
- end
352
-
353
- it 'shows detailed JSON diff on failure' do
354
- expect(actual_json).to be_json_equivalent_to(expected_json, verbose: true)
355
- end
356
-
357
- # Combine with profile and options
358
- it 'shows verbose diff with custom options' do
359
- expect(actual_xml).to be_xml_equivalent_to(expected_xml, verbose: true)
360
- .with_profile(:spec_friendly)
361
- .with_options(comments: :strict)
362
- end
363
- end
364
- ----
365
-
366
- == Common patterns
367
- == Verbose output
368
-
369
- Use `verbose: true` to show detailed diff output on test failures.
370
-
371
- .Verbose diff examples
372
- [example]
373
- [source,ruby]
374
- ----
375
- RSpec.describe 'Verbose diff output' do
376
- it 'shows detailed XML diff on failure' do
377
- expect(actual_xml).to be_xml_equivalent_to(expected_xml, verbose: true)
378
- end
379
-
380
- it 'shows detailed HTML diff on failure' do
381
- expect(actual_html).to be_html_equivalent_to(expected_html, verbose: true)
382
- end
383
-
384
- it 'shows detailed JSON diff on failure' do
385
- expect(actual_json).to be_json_equivalent_to(expected_json, verbose: true)
386
- end
387
-
388
- # Combine with profile and options
389
- it 'shows verbose diff with custom options' do
390
- expect(actual_xml).to be_xml_equivalent_to(expected_xml, verbose: true)
391
- .with_profile(:spec_friendly)
392
- .with_options(comments: :strict)
393
- end
394
- end
395
- ----
396
-
397
- == Diff algorithms
398
-
399
- Canon supports two diff algorithms that can be selected based on your needs:
400
-
401
- * **`:dom`** (default): Positional, DOM-based matching
402
- * **`:semantic`**: Tree-based matching with operation detection
403
-
404
- === DOM algorithm
405
-
406
- The DOM algorithm performs positional matching and is the default behavior:
407
-
408
- .DOM algorithm usage
409
- [example]
410
- [source,ruby]
411
- ----
412
- RSpec.describe 'DOM algorithm' do
413
- it 'uses positional matching by default' do
414
- expect(actual_xml).to be_xml_equivalent_to(expected_xml)
415
- end
416
-
417
- it 'explicitly specifies DOM algorithm' do
418
- expect(actual_xml).to be_xml_equivalent_to(expected_xml,
419
- diff_algorithm: :dom,
420
- verbose: true
421
- )
422
- end
423
- end
424
- ----
425
-
426
- === Semantic algorithm
427
-
428
- The semantic algorithm uses tree-based matching and detects operations like
429
- insert, delete, update, move, merge, and split:
430
-
431
- .Semantic algorithm usage
432
- [example]
433
- [source,ruby]
434
- ----
435
- RSpec.describe 'Semantic algorithm' do
436
- it 'detects semantic operations' do
437
- xml1 = '<root><a>1</a><b>2</b></root>'
438
- xml2 = '<root><b>2</b><a>1</a><c>3</c></root>'
439
-
440
- expect(xml1).to be_xml_equivalent_to(xml2,
441
- diff_algorithm: :semantic,
442
- verbose: true
443
- )
444
- end
445
-
446
- it 'identifies element moves' do
447
- html1 = '<div><p>First</p><p>Second</p></div>'
448
- html2 = '<div><p>Second</p><p>First</p></div>'
449
-
450
- expect(html1).to be_html_equivalent_to(html2,
451
- diff_algorithm: :semantic,
452
- verbose: true
453
- )
454
- end
455
-
456
- it 'detects insertions and deletions' do
457
- json1 = '{"users": [{"id": 1}, {"id": 2}]}'
458
- json2 = '{"users": [{"id": 1}, {"id": 2}, {"id": 3}]}'
459
-
460
- expect(json1).to be_json_equivalent_to(json2,
461
- diff_algorithm: :semantic,
462
- verbose: true
463
- )
464
- end
465
- end
466
- ----
467
-
468
- === Global algorithm configuration
469
-
470
- Configure the diff algorithm globally:
471
-
472
- .Global diff algorithm configuration
473
- [example]
474
- [source,ruby]
475
- ----
476
- # spec_helper.rb
477
- Canon::RSpecMatchers.configure do |config|
478
- # Use semantic algorithm for XML by default
479
- config.xml.diff.algorithm = :semantic
480
-
481
- # Use DOM algorithm for HTML (default)
482
- config.html.diff.algorithm = :dom
483
-
484
- # Use semantic algorithm for JSON
485
- config.json.diff.algorithm = :semantic
486
- end
487
- ----
488
-
489
- === Per-test algorithm override
490
-
491
- Override the global algorithm configuration for specific tests:
492
-
493
- .Per-test algorithm override
494
- [example]
495
- [source,ruby]
496
- ----
497
- RSpec.describe 'Algorithm override' do
498
- # Global config uses :semantic
499
- # But this test uses :dom
500
- it 'uses DOM algorithm for this specific test' do
501
- expect(actual_xml).to be_xml_equivalent_to(expected_xml,
502
- diff_algorithm: :dom,
503
- verbose: true
504
- )
505
- end
506
-
507
- # Global config uses :dom
508
- # But this test uses :semantic
509
- it 'uses semantic algorithm to detect operations' do
510
- expect(actual_html).to be_html_equivalent_to(expected_html,
511
- diff_algorithm: :semantic,
512
- verbose: true
513
- )
514
- end
515
- end
516
- ----
517
-
518
- === Combining algorithm with other options
519
-
520
- The diff algorithm can be combined with match profiles and options:
521
-
522
- .Algorithm with profiles and options
523
- [example]
524
- [source,ruby]
525
- ----
526
- RSpec.describe 'Combined configuration' do
527
- it 'uses semantic algorithm with spec_friendly profile' do
528
- expect(actual_xml).to be_xml_equivalent_to(expected_xml,
529
- diff_algorithm: :semantic,
530
- verbose: true
531
- )
532
- .with_profile(:spec_friendly)
533
- end
534
-
535
- it 'uses semantic algorithm with custom match options' do
536
- expect(actual_xml).to be_xml_equivalent_to(expected_xml,
537
- diff_algorithm: :semantic,
538
- verbose: true
539
- )
540
- .with_options(
541
- structural_whitespace: :ignore,
542
- comments: :ignore
543
- )
544
- end
545
-
546
- it 'uses DOM algorithm with strict matching' do
547
- expect(actual_html).to be_html_equivalent_to(expected_html,
548
- diff_algorithm: :dom,
549
- verbose: true
550
- )
551
- .with_profile(:strict)
552
- end
553
- end
554
- ----
555
-
556
- == Common patterns
557
- ====
558
- [source,ruby]
559
- ----
560
- RSpec.describe 'Verbose diff output' do
561
- it 'shows detailed XML diff on failure' do
562
- expect(actual_xml).to be_xml_equivalent_to(expected_xml, verbose: true)
563
- end
564
-
565
- it 'shows detailed HTML diff on failure' do
566
- expect(actual_html).to be_html_equivalent_to(expected_html, verbose: true)
567
- end
568
-
569
- it 'shows detailed JSON diff on failure' do
570
- expect(actual_json).to be_json_equivalent_to(expected_json, verbose: true)
571
- end
572
-
573
- # Combine with profile and options
574
- it 'shows verbose diff with custom options' do
575
- expect(actual_xml).to be_xml_equivalent_to(expected_xml, verbose: true)
576
- .with_profile(:spec_friendly)
577
- .with_options(comments: :strict)
578
- end
579
- end
580
- ----
581
- ====
582
-
583
- == Common patterns
584
-
585
- === Testing XML generation
586
-
587
- .XML generation tests
588
- [example]
589
- ====
590
- [source,ruby]
591
- ----
592
- RSpec.describe 'XML generation' do
593
- let(:expected_xml) do
594
- <<~XML
595
- <document>
596
- <title>Test Document</title>
597
- <content>
598
- <p>Paragraph content</p>
599
- </content>
600
- </document>
601
- XML
602
- end
603
-
604
- it 'generates correct XML structure' do
605
- actual_xml = generate_xml(title: 'Test Document',
606
- content: '<p>Paragraph content</p>')
607
-
608
- expect(actual_xml).to be_xml_equivalent_to(expected_xml)
609
- end
610
-
611
- it 'handles attributes correctly' do
612
- actual = generate_element(id: '123', class: 'active')
613
- expected = '<element class="active" id="123"/>'
614
-
615
- # Attribute order doesn't matter with default config
616
- expect(actual).to be_xml_equivalent_to(expected)
617
- end
618
- end
619
- ----
620
- ====
621
-
622
- === Testing HTML output
623
-
624
- .HTML output tests
625
- [example]
626
- ====
627
- [source,ruby]
628
- ----
629
- RSpec.describe 'HTML generation' do
630
- let(:expected_html) do
631
- <<~HTML
632
- <!DOCTYPE html>
633
- <html>
634
- <head>
635
- <title>Test Page</title>
636
- </head>
637
- <body>
638
- <h1>Welcome</h1>
639
- <p>Content here</p>
640
- </body>
641
- </html>
642
- HTML
643
- end
644
-
645
- it 'generates correct HTML' do
646
- actual_html = generate_page(
647
- title: 'Test Page',
648
- heading: 'Welcome',
649
- content: 'Content here'
650
- )
651
-
652
- expect(actual_html).to be_html_equivalent_to(expected_html)
653
- end
654
-
655
- # Formatting differences are ignored
656
- it 'ignores whitespace differences' do
657
- compact = '<div><p>Text</p></div>'
658
- pretty = <<~HTML
659
- <div>
660
- <p>Text</p>
661
- </div>
662
- HTML
663
-
664
- expect(compact).to be_html_equivalent_to(pretty)
665
- end
666
- end
667
- ----
668
- ====
669
-
670
- === Testing JSON APIs
671
-
672
- .JSON API tests
673
- [example]
674
- ====
675
- [source,ruby]
676
- ----
677
- RSpec.describe 'API responses' do
678
- let(:expected_response) do
679
- {
680
- "user" => {
681
- "id" => 123,
682
- "name" => "John Doe",
683
- "email" => "john@example.com"
684
- },
685
- "status" => "success"
686
- }.to_json
687
- end
688
-
689
- it 'returns correct user data' do
690
- response = api_client.get_user(123)
691
-
692
- expect(response).to be_json_equivalent_to(expected_response)
693
- end
694
-
695
- # Key order doesn't matter with default config
696
- it 'ignores key ordering' do
697
- actual = '{"b":2,"a":1}'
698
- expected = '{"a":1,"b":2}'
699
-
700
- expect(actual).to be_json_equivalent_to(expected)
701
- end
702
- end
703
- ----
704
- ====
705
-
706
- === Testing configuration files
707
-
708
- .Configuration file tests
709
- [example]
710
- ====
711
- [source,ruby]
712
- ----
713
- RSpec.describe 'Configuration files' do
714
- it 'generates correct YAML config' do
715
- config = generate_config(
716
- database: { host: 'localhost', port: 5432 },
717
- logging: { level: 'info' }
718
- )
719
-
720
- expected = <<~YAML
721
- database:
722
- host: localhost
723
- port: 5432
724
- logging:
725
- level: info
726
- YAML
727
-
728
- expect(config).to be_yaml_equivalent_to(expected)
729
- end
730
-
731
- it 'handles nested structures' do
732
- actual_yaml = to_yaml(deeply: { nested: { structure: 'value' } })
733
- expected_yaml = "deeply:\n nested:\n structure: value"
734
-
735
- expect(actual_yaml).to be_yaml_equivalent_to(expected_yaml)
736
- end
737
- end
738
- ----
739
- ====
740
-
741
- == Troubleshooting
742
-
743
- === Debugging test failures
744
-
745
- When a test fails, Canon shows a detailed diff. Use verbose mode for maximum
746
- detail:
747
-
748
- .Debugging example
749
- [example]
750
- ====
751
- [source,ruby]
752
- ----
753
- it 'shows exactly what differs' do
754
- expect(actual).to be_xml_equivalent_to(expected, verbose: true)
755
- .with_profile(:strict) # Use strict to see all differences
756
- end
757
- ----
758
-
759
- The diff output will show:
760
-
761
- * Line numbers
762
- * Color-coded changes (red/green)
763
- * Whitespace visualization
764
- * Non-ASCII character warnings
765
- ====
766
-
767
- === Temporarily disabling global config
768
-
769
- .Override global config
770
- [example]
771
- ====
772
- [source,ruby]
773
- ----
774
- it 'uses different config for this test' do
775
- # Global config uses spec_friendly, but we want strict here
776
- expect(actual).to be_xml_equivalent_to(expected)
777
- .with_profile(:strict)
778
- .with_options(
779
- text_content: :strict,
780
- structural_whitespace: :strict
781
- )
782
- end
783
- ----
784
- ====
785
-
786
- === Checking what's being compared
787
-
788
- .Debug preprocessed content
789
- [example]
790
- ====
791
- [source,ruby]
792
- ----
793
- # In your test, temporarily add:
794
- result = Canon::Comparison.equivalent?(actual, expected, verbose: true)
795
-
796
- # Inspect preprocessed content
797
- puts "Preprocessed actual:"
798
- puts result[:preprocessed][0]
799
-
800
- puts "Preprocessed expected:"
801
- puts result[:preprocessed][1]
802
-
803
- puts "Differences:"
804
- pp result[:differences]
805
- ----
806
- ====
807
-
808
- == See also
809
-
810
- * link:RUBY_API[Ruby API documentation]
811
- * link:CLI[Command-line interface]
812
- * link:MATCH_OPTIONS[Match options reference]
813
- * link:MODES[Diff modes]
814
- * link:DIFF_FORMATTING[Diff formatting options]