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.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -1
- data/.rubocop_todo.yml +276 -7
- data/README.adoc +203 -138
- data/_config.yml +116 -0
- data/docs/ADVANCED_TOPICS.adoc +20 -0
- data/docs/BASIC_USAGE.adoc +16 -0
- data/docs/CHARACTER_VISUALIZATION.adoc +567 -0
- data/docs/CLI.adoc +493 -0
- data/docs/CUSTOMIZING_BEHAVIOR.adoc +19 -0
- data/docs/DIFF_ARCHITECTURE.adoc +435 -0
- data/docs/DIFF_FORMATTING.adoc +540 -0
- data/docs/FORMATS.adoc +447 -0
- data/docs/INDEX.adoc +222 -0
- data/docs/INPUT_VALIDATION.adoc +477 -0
- data/docs/MATCH_ARCHITECTURE.adoc +463 -0
- data/docs/MATCH_OPTIONS.adoc +719 -0
- data/docs/MODES.adoc +432 -0
- data/docs/NORMATIVE_INFORMATIVE_DIFFS.adoc +219 -0
- data/docs/OPTIONS.adoc +1387 -0
- data/docs/PREPROCESSING.adoc +491 -0
- data/docs/RSPEC.adoc +605 -0
- data/docs/RUBY_API.adoc +478 -0
- data/docs/SEMANTIC_DIFF_REPORT.adoc +528 -0
- data/docs/UNDERSTANDING_CANON.adoc +17 -0
- data/docs/VERBOSE.adoc +482 -0
- data/exe/canon +7 -0
- data/lib/canon/cli.rb +179 -0
- data/lib/canon/commands/diff_command.rb +195 -0
- data/lib/canon/commands/format_command.rb +113 -0
- data/lib/canon/comparison/base_comparator.rb +39 -0
- data/lib/canon/comparison/comparison_result.rb +79 -0
- data/lib/canon/comparison/html_comparator.rb +410 -0
- data/lib/canon/comparison/json_comparator.rb +212 -0
- data/lib/canon/comparison/match_options.rb +616 -0
- data/lib/canon/comparison/xml_comparator.rb +566 -0
- data/lib/canon/comparison/yaml_comparator.rb +93 -0
- data/lib/canon/comparison.rb +239 -0
- data/lib/canon/config.rb +172 -0
- data/lib/canon/diff/diff_block.rb +71 -0
- data/lib/canon/diff/diff_block_builder.rb +105 -0
- data/lib/canon/diff/diff_classifier.rb +46 -0
- data/lib/canon/diff/diff_context.rb +85 -0
- data/lib/canon/diff/diff_context_builder.rb +107 -0
- data/lib/canon/diff/diff_line.rb +77 -0
- data/lib/canon/diff/diff_node.rb +56 -0
- data/lib/canon/diff/diff_node_mapper.rb +148 -0
- data/lib/canon/diff/diff_report.rb +133 -0
- data/lib/canon/diff/diff_report_builder.rb +62 -0
- data/lib/canon/diff_formatter/by_line/base_formatter.rb +407 -0
- data/lib/canon/diff_formatter/by_line/html_formatter.rb +672 -0
- data/lib/canon/diff_formatter/by_line/json_formatter.rb +284 -0
- data/lib/canon/diff_formatter/by_line/simple_formatter.rb +190 -0
- data/lib/canon/diff_formatter/by_line/xml_formatter.rb +860 -0
- data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +292 -0
- data/lib/canon/diff_formatter/by_object/base_formatter.rb +199 -0
- data/lib/canon/diff_formatter/by_object/json_formatter.rb +305 -0
- data/lib/canon/diff_formatter/by_object/xml_formatter.rb +248 -0
- data/lib/canon/diff_formatter/by_object/yaml_formatter.rb +17 -0
- data/lib/canon/diff_formatter/character_map.yml +197 -0
- data/lib/canon/diff_formatter/debug_output.rb +431 -0
- data/lib/canon/diff_formatter/diff_detail_formatter.rb +551 -0
- data/lib/canon/diff_formatter/legend.rb +141 -0
- data/lib/canon/diff_formatter.rb +520 -0
- data/lib/canon/errors.rb +56 -0
- data/lib/canon/formatters/html4_formatter.rb +17 -0
- data/lib/canon/formatters/html5_formatter.rb +17 -0
- data/lib/canon/formatters/html_formatter.rb +37 -0
- data/lib/canon/formatters/html_formatter_base.rb +163 -0
- data/lib/canon/formatters/json_formatter.rb +3 -0
- data/lib/canon/formatters/xml_formatter.rb +20 -55
- data/lib/canon/formatters/yaml_formatter.rb +4 -1
- data/lib/canon/pretty_printer/html.rb +57 -0
- data/lib/canon/pretty_printer/json.rb +25 -0
- data/lib/canon/pretty_printer/xml.rb +29 -0
- data/lib/canon/rspec_matchers.rb +222 -80
- data/lib/canon/validators/base_validator.rb +49 -0
- data/lib/canon/validators/html_validator.rb +138 -0
- data/lib/canon/validators/json_validator.rb +89 -0
- data/lib/canon/validators/xml_validator.rb +53 -0
- data/lib/canon/validators/yaml_validator.rb +73 -0
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/attribute_handler.rb +80 -0
- data/lib/canon/xml/c14n.rb +36 -0
- data/lib/canon/xml/character_encoder.rb +38 -0
- data/lib/canon/xml/data_model.rb +225 -0
- data/lib/canon/xml/element_matcher.rb +196 -0
- data/lib/canon/xml/line_range_mapper.rb +158 -0
- data/lib/canon/xml/namespace_handler.rb +86 -0
- data/lib/canon/xml/node.rb +32 -0
- data/lib/canon/xml/nodes/attribute_node.rb +54 -0
- data/lib/canon/xml/nodes/comment_node.rb +23 -0
- data/lib/canon/xml/nodes/element_node.rb +56 -0
- data/lib/canon/xml/nodes/namespace_node.rb +38 -0
- data/lib/canon/xml/nodes/processing_instruction_node.rb +24 -0
- data/lib/canon/xml/nodes/root_node.rb +16 -0
- data/lib/canon/xml/nodes/text_node.rb +23 -0
- data/lib/canon/xml/processor.rb +151 -0
- data/lib/canon/xml/whitespace_normalizer.rb +72 -0
- data/lib/canon/xml/xml_base_handler.rb +188 -0
- data/lib/canon.rb +14 -3
- 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]
|