summaryrefslogtreecommitdiff
path: root/test/rdoc/test_rdoc_markup_to_html_crossref.rb
blob: baacda8285460bc29203f66dbe3f92fc8fc76da8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
require 'rubygems'
require 'minitest/unit'
require 'rdoc/generator'
require 'rdoc/stats'
require 'rdoc/code_objects'
require 'rdoc/markup/to_html_crossref'
require 'rdoc/parser/ruby'

require 'pathname'

class TestRDocMarkupToHtmlCrossref < MiniTest::Unit::TestCase

  #
  # This method parses a source file and returns a Hash mapping
  # class names (Strings) to RDoc::Generator::Class instances
  # (classes), which can be used to create RDoc::Markup::ToHtmlCrossref
  # instances.  The unit tests only test against classes starting with
  # Ref_, so this method only includes such classes in the Hash.
  #
  def create_class_hash
    # The relative gem would help here...
    # @source_file_name must be cleaned because rdoc does not deal
    # well with paths containing "." or "..".
    curr_file = Pathname.new(__FILE__)
    @source_file_name = curr_file.dirname + "rdoc_markup_to_html_crossref_reference.rb"
    @source_file_name = @source_file_name.cleanpath.to_s

    RDoc::TopLevel.reset

    # Reset RDoc::Generator::Method so that the method sequence number starts
    # at 1, making the method sequence numbers for the methods in the Ref_
    # predicable.
    RDoc::Generator::Method.reset
    top_level = RDoc::TopLevel.new @source_file_name
    
    options = RDoc::Options.new
    options.quiet = true

    # If this is false, then RDoc::Generator::Method will attempt to create
    # an HTML file containing the method source code when being instantiated,
    # which does not work in the context of this unit test.
    #
    # RDoc::Generator::Method needs to be refactored so that this does *not*
    # happen as part of instantiation.
    options.inline_source = true

    stats = RDoc::Stats.new 0

    parser = RDoc::Parser::Ruby.new(top_level,
                                    @source_file_name,
                                    IO.read(@source_file_name),
                                    options,
                                    stats)
    top_levels = []
    top_levels.push(parser.scan())

    files, classes = RDoc::Generator::Context.build_indices(top_levels, options)

    class_hash = {}
    classes.each do |klass|
      if(klass.name.include?("Ref_"))
        class_hash[klass.name] = klass
      end
    end

    return class_hash
  end

  #
  # This method uses xref to cross-reference String reference and
  # asserts that xref.convert(reference) is equal
  # to String expected_result.
  #
  def verify_convert(xref, reference, expected_result)
    # Everything converted in the tests will be within paragraph markup, so
    # add paragraph markup to the expected result.
    actual_expected_result = "<p>\n#{expected_result}\n</p>\n"

    result = xref.convert(reference)

    # RDoc::Markup::ToHtml word-wraps lines.  It is tricky to predict where
    # a line will be wrapped except that it will happen on a space, so replace
    # all newlines with spaces in order to not have to worry about this.
    actual_expected_result.gsub!(/\n/, " ")
    result.gsub!(/\n/, " ")

    assert_equal actual_expected_result, result
  end

  #
  # This method verifies that xref generates no cross-reference link for
  # String reference.
  #
  def verify_no_crossref(xref, reference)
    if(reference[0, 1] == "\\") # Remove the markup suppression character
      expected_result = reference[1, reference.length() - 1]
    else
      expected_result = reference
    end

    verify_convert(xref, reference, expected_result)
  end

  #
  # This method verifies that xref generates a cross-reference link to
  # class_name (String) for String reference.
  #
  def verify_class_crossref(xref, reference, class_name)
    class_file_name = class_name.gsub(/::/, "/")
    
    result = "<a href=\"../classes/#{class_file_name}.html\">#{reference}</a>"

    verify_convert xref, reference, result
  end

  #
  # This method verifies that xref generates a cross-reference link to method
  # method_seq (String, e.g, "M000001") in class_name (String) for
  # String reference.
  #
  def verify_method_crossref(xref, reference, class_name, method_seq)
    class_file_name = class_name.gsub(/::/, "/")
    
    result = "<a href=\"../classes/#{class_file_name}.html##{method_seq}\">#{reference}</a>"

    verify_convert xref, reference, result
  end

  #
  # This method verifies that xref generates a cross-reference link to
  # file_name (String) for String reference.
  #
  def verify_file_crossref(xref, reference, file_name)
    generated_document_path = Pathname.new("../files/#{file_name.gsub(/\./, '_')}.html").cleanpath.to_s
    result = "<a href=\"#{generated_document_path}\">#{reference}</a>"

    verify_convert xref, reference, result
  end

  #
  # This method verifies that several invariant cross-references are
  # (or are not) generated.
  #
  def verify_invariant_crossrefs(xref)
    # bogus does not exist and so no cross-reference should be generated.
    verify_no_crossref xref, "bogus"
    verify_no_crossref xref, "\\bogus"
    
    # Ref_Class1 is in the top-level namespace, and so a cross-reference always
    # should be generated, unless markup is suppressed.
    verify_class_crossref xref, "Ref_Class1", "Ref_Class1"
    verify_no_crossref xref, "\\Ref_Class1"

    # Ref_Class2 is in the top-level namespace, and so a cross-reference always
    # should be generated for it and for its nested classes.
    verify_class_crossref xref, "Ref_Class2", "Ref_Class2"
    verify_class_crossref xref, "Ref_Class2::Ref_Class3", "Ref_Class2::Ref_Class3"
    verify_method_crossref xref, "Ref_Class2::Ref_Class3#method", "Ref_Class2::Ref_Class3", "M000001"
    verify_method_crossref xref, "Ref_Class2::Ref_Class3#method()", "Ref_Class2::Ref_Class3", "M000001"
    verify_method_crossref xref, "Ref_Class2::Ref_Class3.method()", "Ref_Class2::Ref_Class3", "M000001"
    verify_method_crossref xref, "Ref_Class2::Ref_Class3.method(*)", "Ref_Class2::Ref_Class3", "M000001"
    verify_class_crossref xref, "Ref_Class2::Ref_Class3::Helper1", "Ref_Class2::Ref_Class3::Helper1"
    verify_method_crossref xref, "Ref_Class2::Ref_Class3::Helper1#method?", "Ref_Class2::Ref_Class3::Helper1", "M000002"

    # The hyphen character is not a valid class/method separator character, so
    # rdoc just generates a class cross-reference (perhaps it should not
    # generate anything?).
    result = "<a href=\"../classes/Ref_Class2/Ref_Class3.html\">Ref_Class2::Ref_Class3</a>;method(*)"
    verify_convert xref, "Ref_Class2::Ref_Class3;method(*)", result

    # There is one Ref_Class3 nested in Ref_Class2 and one defined in the
    # top-level namespace; regardless, ::Ref_Class3 (Ref_Class3 relative
    # to the top-level namespace) always should generate a link to the
    # top-level Ref_Class3 (unless of course cross-references are suppressed).
    verify_class_crossref xref, "::Ref_Class3", "Ref_Class3"
    verify_no_crossref xref, "\\::Ref_Class3"
    verify_class_crossref xref, "::Ref_Class3::Helper1", "Ref_Class3::Helper1"
    verify_class_crossref xref, "::Ref_Class3::Helper2", "Ref_Class3::Helper2"

    #
    # Ref_Class3::Helper1 does not have method method.
    #
    verify_no_crossref xref, "::Ref_Class3::Helper1#method"
    verify_no_crossref xref, "\\::Ref_Class3::Helper1#method"

    # References to Ref_Class2 relative to the top-level namespace always should
    # generate links to Ref_Class2.
    verify_method_crossref xref, "::Ref_Class2::Ref_Class3#method", "Ref_Class2::Ref_Class3", "M000001"
    verify_method_crossref xref, "::Ref_Class2::Ref_Class3#method()", "Ref_Class2::Ref_Class3", "M000001"
    verify_method_crossref xref, "::Ref_Class2::Ref_Class3#method(*)", "Ref_Class2::Ref_Class3", "M000001"
    verify_class_crossref xref, "::Ref_Class2::Ref_Class3::Helper1", "Ref_Class2::Ref_Class3::Helper1"
    verify_no_crossref xref, "\\::Ref_Class2::Ref_Class3#method(*)"

    # Suppressing cross-references always should suppress the generation of
    # links.
    verify_no_crossref xref, "\\#method"
    verify_no_crossref xref, "\\#method()"
    verify_no_crossref xref, "\\#method(*)"

    # Links never should be generated for words solely consisting of lowercase
    # letters, because too many links would get generated by mistake (i.e., the
    # word "new" always would be a link).
    verify_no_crossref xref, "method"

    # A link always should be generated for a file name.
    verify_file_crossref xref, @source_file_name, @source_file_name

    # References should be generated correctly for a class scoped within
    # a class of the same name.
    verify_class_crossref xref, "Ref_Class4::Ref_Class4", "Ref_Class4::Ref_Class4"
  end

  def test_handle_special_CROSSREF_no_underscore
    class_hash = create_class_hash

    # Note that we instruct the ToHtmlCrossref instance to show hashes so that
    # an exception won't have to be made for words starting with a '#'.
    # I'm also not convinced that the current behavior of the rdoc code
    # is correct since, without this, it strips the leading # from all
    # words, whether or not they end up as cross-references.
    #
    # After the behavior has been sorted out, this can be changed.
    #
    # Create a variety of RDoc::Markup::ToHtmlCrossref instances, for
    # different classes, and test the cross-references generated by
    # each.
    klass = class_hash["Ref_Class1"]
    xref = RDoc::Markup::ToHtmlCrossref.new 'from_path', klass, true
    verify_invariant_crossrefs xref
    verify_class_crossref xref, "Ref_Class3", "Ref_Class3"
    verify_no_crossref xref, "Ref_Class3#method"
    verify_no_crossref xref, "#method"
    verify_class_crossref xref, "Ref_Class3::Helper1", "Ref_Class3::Helper1"
    verify_class_crossref xref, "Ref_Class3::Helper2", "Ref_Class3::Helper2"
    verify_no_crossref xref, "Helper1"
    verify_class_crossref xref, "Ref_Class4", "Ref_Class4"

    klass = class_hash["Ref_Class2"]
    xref = RDoc::Markup::ToHtmlCrossref.new 'from_path', klass, true
    verify_invariant_crossrefs xref
    verify_class_crossref xref, "Ref_Class3", "Ref_Class2::Ref_Class3"
    verify_method_crossref xref, "Ref_Class3#method", "Ref_Class2::Ref_Class3", "M000001"
    verify_no_crossref xref, "#method"
    verify_class_crossref xref, "Ref_Class3::Helper1", "Ref_Class2::Ref_Class3::Helper1"
    verify_class_crossref xref, "Ref_Class4", "Ref_Class4"

    # This one possibly is an rdoc bug...
    # Ref_Class2 has a nested Ref_Class3, but
    # Ref_Class2::Ref_Class3::Helper2 does not exist.
    # On the other hand, there is a Ref_Class3::Helper2
    # in the top-level namespace...  Should rdoc stop
    # looking if it finds one class match?
    verify_no_crossref xref, "Ref_Class3::Helper2"
    verify_no_crossref xref, "Helper1"

    klass = class_hash["Ref_Class2::Ref_Class3"]
    xref = RDoc::Markup::ToHtmlCrossref.new 'from_path', klass, true
    verify_invariant_crossrefs xref
    verify_class_crossref xref, "Ref_Class3", "Ref_Class2::Ref_Class3"
    verify_method_crossref xref, "Ref_Class3#method", "Ref_Class2::Ref_Class3", "M000001"
    verify_method_crossref xref, "#method", "Ref_Class2::Ref_Class3", "M000001"
    verify_class_crossref xref, "Ref_Class3::Helper1", "Ref_Class2::Ref_Class3::Helper1"
    verify_no_crossref xref, "Ref_Class3::Helper2"
    verify_class_crossref xref, "Helper1", "Ref_Class2::Ref_Class3::Helper1"
    verify_class_crossref xref, "Ref_Class4", "Ref_Class4"

    klass = class_hash["Ref_Class3"]
    xref = RDoc::Markup::ToHtmlCrossref.new 'from_path', klass, true
    verify_invariant_crossrefs xref
    verify_class_crossref xref, "Ref_Class3", "Ref_Class3"
    verify_no_crossref xref, "Ref_Class3#method"
    verify_no_crossref xref, "#method"
    verify_class_crossref xref, "Ref_Class3::Helper1", "Ref_Class3::Helper1"
    verify_class_crossref xref, "Ref_Class3::Helper2", "Ref_Class3::Helper2"
    verify_class_crossref xref, "Helper1", "Ref_Class3::Helper1"
    verify_class_crossref xref, "Ref_Class4", "Ref_Class4"

    klass = class_hash["Ref_Class4"]
    xref = RDoc::Markup::ToHtmlCrossref.new 'from_path', klass, true
    verify_invariant_crossrefs xref
    # A Ref_Class4 reference inside a Ref_Class4 class containing a
    # Ref_Class4 class should resolve to the contained class.
    verify_class_crossref xref, "Ref_Class4", "Ref_Class4::Ref_Class4"

    klass = class_hash["Ref_Class4::Ref_Class4"]
    xref = RDoc::Markup::ToHtmlCrossref.new 'from_path', klass, true
    verify_invariant_crossrefs xref
    # A Ref_Class4 reference inside a Ref_Class4 class contained within
    # a Ref_Class4 class should resolve to the inner Ref_Class4 class.
    verify_class_crossref xref, "Ref_Class4", "Ref_Class4::Ref_Class4"
  end
end

MiniTest::Unit.autorun