github-markup 6.0.0 → 6.1.1
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/.github/workflows/ci.yml +25 -6
- data/.github/workflows/stale.yml +1 -1
- data/.gitignore +1 -0
- data/CONTRIBUTING.md +12 -0
- data/Gemfile +2 -6
- data/Gemfile.lock +65 -44
- data/HISTORY.md +52 -0
- data/README.md +5 -1
- data/github-markup.gemspec +11 -6
- data/lib/github/commands/rest2html +1 -0
- data/lib/github/markup/markdown.rb +87 -3
- data/lib/github/markup/rdoc.rb +5 -1
- data/lib/github/markup.rb +1 -2
- data/lib/github/markups.rb +10 -10
- data/lib/github-markup.rb +1 -1
- data/test/coverage_test.rb +368 -0
- data/test/fixtures/cat.sh +2 -0
- data/test/fixtures/empty.sh +3 -0
- data/test/markdown_test.rb +96 -0
- data/test/markup_test.rb +11 -9
- data/test/markups/README.asciidoc.html +6 -6
- data/test/markups/README.creole.html +1 -5
- data/test/markups/README.directives.rst.html +2 -2
- data/test/markups/README.litcoffee.html +5 -5
- data/test/markups/README.long.rst.html +220 -362
- data/test/markups/README.mediawiki.html +20 -20
- data/test/markups/README.org.html +30 -39
- data/test/markups/README.pod.html +15 -19
- data/test/markups/README.rdoc.html +4 -7
- data/test/markups/README.rst.html +26 -47
- data/test/markups/README.rst.txt.html +5 -10
- data/test/markups/README.toc.asciidoc.html +10 -12
- data/test/markups/README.toc.rst.html +7 -8
- data/test/test_helper.rb +29 -0
- metadata +51 -30
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
# Exercises code paths the original markup_test.rb does not reach:
|
|
4
|
+
# - The six fallback markdown gem procs (github/markdown, redcarpet,
|
|
5
|
+
# rdiscount, maruku, kramdown, bluecloth) executed against stubbed constants
|
|
6
|
+
# - The LoadError ("no suitable markdown gem found") raised when no gem is available
|
|
7
|
+
# - try_require's rescue clause
|
|
8
|
+
# - Implementation#render's NotImplementedError default
|
|
9
|
+
# - Implementation#match? without Linguist (and the lazy regexp memoization)
|
|
10
|
+
# - GitHub::Markup.markup_impl's duplicate-symbol guard
|
|
11
|
+
# - GitHub::Markup.render falling through to the raw content
|
|
12
|
+
# - GitHub::Markup.render_s with an unknown symbol and with nil content
|
|
13
|
+
# - GitHub::Markup.preload!
|
|
14
|
+
# - GitHub::Markup.language returning nil without Linguist
|
|
15
|
+
# - CommandImplementation block-arity branches (arity 1 vs 2)
|
|
16
|
+
|
|
17
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
|
|
18
|
+
|
|
19
|
+
require_relative 'test_helper'
|
|
20
|
+
require 'github-markup'
|
|
21
|
+
require 'github/markup'
|
|
22
|
+
require 'minitest/autorun'
|
|
23
|
+
|
|
24
|
+
class CoverageTest < Minitest::Test
|
|
25
|
+
include ConstantStubbing
|
|
26
|
+
|
|
27
|
+
def test_version_constant_is_defined
|
|
28
|
+
assert_kind_of String, GitHub::Markup::VERSION
|
|
29
|
+
assert_equal GitHub::Markup::VERSION, GitHub::Markup::Version
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# --- markdown.rb fallback procs ----------------------------------------
|
|
33
|
+
|
|
34
|
+
def test_github_markdown_proc_uses_github_markdown_render
|
|
35
|
+
with_stub_const("GitHub::Markdown", fake_renderer_module(:render)) do
|
|
36
|
+
result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("github/markdown").call("hi")
|
|
37
|
+
assert_equal "github_markdown:hi", result
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_redcarpet_proc_renders_via_redcarpet
|
|
42
|
+
fake_html = Class.new
|
|
43
|
+
fake_md = Class.new do
|
|
44
|
+
def initialize(*); end
|
|
45
|
+
def render(content); "redcarpet:#{content}"; end
|
|
46
|
+
end
|
|
47
|
+
fake_module = Module.new
|
|
48
|
+
fake_module.const_set(:Render, Module.new.tap { |m| m.const_set(:HTML, fake_html) })
|
|
49
|
+
fake_module.const_set(:Markdown, fake_md)
|
|
50
|
+
with_stub_const("Redcarpet", fake_module) do
|
|
51
|
+
result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("redcarpet").call("hi")
|
|
52
|
+
assert_equal "redcarpet:hi", result
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_rdiscount_proc_renders_via_rdiscount
|
|
57
|
+
with_stub_const("RDiscount", instance_renderer_class(:to_html, prefix: "rdiscount")) do
|
|
58
|
+
result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("rdiscount").call("hi")
|
|
59
|
+
assert_equal "rdiscount:hi", result
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_maruku_proc_renders_via_maruku
|
|
64
|
+
with_stub_const("Maruku", instance_renderer_class(:to_html, prefix: "maruku")) do
|
|
65
|
+
result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("maruku").call("hi")
|
|
66
|
+
assert_equal "maruku:hi", result
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_kramdown_proc_renders_via_kramdown_document
|
|
71
|
+
fake_doc = instance_renderer_class(:to_html, prefix: "kramdown")
|
|
72
|
+
fake_module = Module.new.tap { |m| m.const_set(:Document, fake_doc) }
|
|
73
|
+
with_stub_const("Kramdown", fake_module) do
|
|
74
|
+
result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("kramdown").call("hi")
|
|
75
|
+
assert_equal "kramdown:hi", result
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_bluecloth_proc_renders_via_bluecloth
|
|
80
|
+
with_stub_const("BlueCloth", instance_renderer_class(:to_html, prefix: "bluecloth")) do
|
|
81
|
+
result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("bluecloth").call("hi")
|
|
82
|
+
assert_equal "bluecloth:hi", result
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# --- markdown.rb load failure and try_require rescue --------------------
|
|
87
|
+
|
|
88
|
+
def test_markdown_load_raises_loaderror_when_no_gem_is_available
|
|
89
|
+
md = GitHub::Markup::Markdown.new
|
|
90
|
+
def md.try_require(_); false; end
|
|
91
|
+
assert_raises(LoadError) { md.load }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_try_require_returns_false_for_missing_gem
|
|
95
|
+
md = GitHub::Markup::Markdown.new
|
|
96
|
+
refute md.send(:try_require, "github_markup_definitely_not_a_real_gem_#{Time.now.to_i}")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# --- implementation.rb default render and Linguist-less match? ---------
|
|
100
|
+
|
|
101
|
+
def test_base_implementation_render_raises_not_implemented_error
|
|
102
|
+
impl = GitHub::Markup::Implementation.new(/foo/, [])
|
|
103
|
+
assert_raises(NotImplementedError) { impl.render("README.foo", "anything") }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_match_uses_filename_extension_when_linguist_is_absent
|
|
107
|
+
impl_class = Class.new(GitHub::Markup::Implementation) do
|
|
108
|
+
def initialize; super(/md|markdown/, []); end
|
|
109
|
+
end
|
|
110
|
+
impl = impl_class.new
|
|
111
|
+
without_linguist do
|
|
112
|
+
assert impl.match?("README.md", nil)
|
|
113
|
+
# call again to cover the memoization branch in file_ext_regexp
|
|
114
|
+
assert impl.match?("README.markdown", nil)
|
|
115
|
+
refute impl.match?("README.txt", nil)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# --- markup.rb registration guard, render fallthroughs, preload! -------
|
|
120
|
+
|
|
121
|
+
def test_markup_impl_raises_when_symbol_already_registered
|
|
122
|
+
err = assert_raises(ArgumentError) do
|
|
123
|
+
GitHub::Markup.markup_impl(
|
|
124
|
+
::GitHub::Markups::MARKUP_MARKDOWN,
|
|
125
|
+
GitHub::Markup::Markdown.new
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
assert_match(/already defined/, err.message)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def test_render_returns_content_when_no_implementation_matches
|
|
132
|
+
raw = "no extension match here"
|
|
133
|
+
assert_equal raw, GitHub::Markup.render("README.unknown_ext_xyz", raw)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_render_s_returns_content_when_symbol_is_unknown
|
|
137
|
+
raw = "passthrough body"
|
|
138
|
+
assert_equal raw, GitHub::Markup.render_s(:not_a_real_markup_symbol, raw)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def test_render_s_raises_on_nil_content
|
|
142
|
+
assert_raises(ArgumentError) do
|
|
143
|
+
GitHub::Markup.render_s(::GitHub::Markups::MARKUP_MARKDOWN, nil)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_preload_calls_load_on_every_implementation
|
|
148
|
+
GitHub::Markup.preload!
|
|
149
|
+
# If preload! succeeded, every markup_impl reports a non-nil renderer or completes its load step.
|
|
150
|
+
GitHub::Markup.markup_impls.each do |impl|
|
|
151
|
+
assert_respond_to impl, :load
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def test_language_returns_nil_without_linguist
|
|
156
|
+
without_linguist do
|
|
157
|
+
assert_nil GitHub::Markup.language("README.md", "anything")
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# --- implementation.rb: Linguist-absent constructor + invalid-language raise
|
|
162
|
+
|
|
163
|
+
def test_implementation_initializes_without_linguist
|
|
164
|
+
without_linguist do
|
|
165
|
+
# Forces the else branch of `if defined?(::Linguist)` in initialize
|
|
166
|
+
impl = GitHub::Markup::Implementation.new(/foo/, ["AnythingGoesWithoutLinguist"])
|
|
167
|
+
assert_nil impl.languages
|
|
168
|
+
refute impl.match?("README.bar", nil)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def test_implementation_raises_for_unknown_linguist_language
|
|
173
|
+
err = assert_raises(RuntimeError) do
|
|
174
|
+
GitHub::Markup::Implementation.new(/foo/, ["DefinitelyNotALinguistLanguage"])
|
|
175
|
+
end
|
|
176
|
+
assert_match(/no match for language/, err.message)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# --- markups.rb wikicloth idempotent ESCAPED_TAGS<<'tt' both branches ---
|
|
180
|
+
|
|
181
|
+
def test_mediawiki_render_is_idempotent_for_escaped_tags
|
|
182
|
+
body = "==Hello==\nworld"
|
|
183
|
+
# First render adds 'tt'; second render hits the `else` branch of `unless include?('tt')`.
|
|
184
|
+
GitHub::Markup.render("README.mediawiki", body)
|
|
185
|
+
count_after_first = WikiCloth::WikiBuffer::HTMLElement::ESCAPED_TAGS.count('tt')
|
|
186
|
+
GitHub::Markup.render("README.mediawiki", body)
|
|
187
|
+
count_after_second = WikiCloth::WikiBuffer::HTMLElement::ESCAPED_TAGS.count('tt')
|
|
188
|
+
assert_equal 1, count_after_first, "first render should leave exactly one 'tt' entry"
|
|
189
|
+
assert_equal 1, count_after_second, "second render must not append a duplicate 'tt'"
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# --- command_implementation.rb block arity branches --------------------
|
|
193
|
+
|
|
194
|
+
def test_command_block_with_arity_two_receives_rendered_and_content
|
|
195
|
+
captured = nil
|
|
196
|
+
impl = GitHub::Markup::CommandImplementation.new(
|
|
197
|
+
/covarity2/, ['Text'], 'test/fixtures/cat.sh', 'covarity2'
|
|
198
|
+
) do |rendered, content|
|
|
199
|
+
captured = [rendered, content]
|
|
200
|
+
"two:#{rendered.strip}:#{content}"
|
|
201
|
+
end
|
|
202
|
+
out = impl.render('README.covarity2', 'payload')
|
|
203
|
+
assert_equal ['payload', 'payload'], captured.map(&:strip)
|
|
204
|
+
assert_equal 'two:payload:payload', out
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def test_command_block_with_arity_one_receives_only_rendered
|
|
208
|
+
captured = nil
|
|
209
|
+
impl = GitHub::Markup::CommandImplementation.new(
|
|
210
|
+
/covarity1/, ['Text'], 'test/fixtures/cat.sh', 'covarity1'
|
|
211
|
+
) do |rendered|
|
|
212
|
+
captured = rendered
|
|
213
|
+
"one:#{rendered.strip}"
|
|
214
|
+
end
|
|
215
|
+
out = impl.render('README.covarity1', 'payload')
|
|
216
|
+
assert_equal 'payload', captured.strip
|
|
217
|
+
assert_equal 'one:payload', out
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def test_command_with_no_block_returns_rendered_output
|
|
221
|
+
impl = GitHub::Markup::CommandImplementation.new(
|
|
222
|
+
/covnoblock/, ['Text'], 'test/fixtures/cat.sh', 'covnoblock'
|
|
223
|
+
)
|
|
224
|
+
assert_equal 'hello', impl.render('README.covnoblock', 'hello').strip
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def test_command_render_falls_back_to_content_when_command_returns_empty
|
|
228
|
+
impl = GitHub::Markup::CommandImplementation.new(
|
|
229
|
+
/covempty/, ['Text'], 'test/fixtures/empty.sh', 'covempty'
|
|
230
|
+
)
|
|
231
|
+
assert_equal 'fallback-body', impl.render('README.covempty', 'fallback-body')
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def test_command_raises_when_subprocess_exits_non_zero
|
|
235
|
+
impl = GitHub::Markup::CommandImplementation.new(
|
|
236
|
+
/covfail/, ['Text'], 'test/fixtures/fail.sh', 'covfail'
|
|
237
|
+
)
|
|
238
|
+
assert_raises(GitHub::Markup::CommandError) { impl.render('README.covfail', 'payload') }
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# --- commonmarker proc legacy option/extension mapping -----------------
|
|
242
|
+
|
|
243
|
+
def test_commonmarker_default_option_is_a_noop
|
|
244
|
+
capture = capture_commonmarker_call(commonmarker_opts: [:DEFAULT])
|
|
245
|
+
refute capture[:render].key?(:sourcepos)
|
|
246
|
+
refute capture[:parse].key?(:smart)
|
|
247
|
+
refute capture[:extension].key?(:footnotes)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def test_commonmarker_sourcepos_option_sets_render_sourcepos
|
|
251
|
+
capture = capture_commonmarker_call(commonmarker_opts: [:SOURCEPOS])
|
|
252
|
+
assert_equal true, capture[:render][:sourcepos]
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def test_commonmarker_hardbreaks_option_enables_render_hardbreaks
|
|
256
|
+
capture = capture_commonmarker_call(commonmarker_opts: [:HARDBREAKS])
|
|
257
|
+
assert_equal true, capture[:render][:hardbreaks]
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def test_commonmarker_nobreaks_option_disables_render_hardbreaks
|
|
261
|
+
# Combine with :HARDBREAKS so the assertion observes a transition true -> false
|
|
262
|
+
capture = capture_commonmarker_call(commonmarker_opts: [:HARDBREAKS, :NOBREAKS])
|
|
263
|
+
assert_equal false, capture[:render][:hardbreaks]
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def test_commonmarker_smart_option_sets_parse_smart
|
|
267
|
+
capture = capture_commonmarker_call(commonmarker_opts: [:SMART])
|
|
268
|
+
assert_equal true, capture[:parse][:smart]
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def test_commonmarker_github_pre_lang_option_sets_render_github_pre_lang
|
|
272
|
+
capture = capture_commonmarker_call(commonmarker_opts: [:GITHUB_PRE_LANG])
|
|
273
|
+
assert_equal true, capture[:render][:github_pre_lang]
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def test_commonmarker_unsafe_option_enables_render_unsafe
|
|
277
|
+
capture = capture_commonmarker_call(commonmarker_opts: [:UNSAFE])
|
|
278
|
+
assert_equal true, capture[:render][:unsafe]
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def test_commonmarker_footnotes_option_enables_extension_footnotes
|
|
282
|
+
capture = capture_commonmarker_call(commonmarker_opts: [:FOOTNOTES])
|
|
283
|
+
assert_equal true, capture[:extension][:footnotes]
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def test_commonmarker_full_info_string_option_sets_render_full_info_string
|
|
287
|
+
capture = capture_commonmarker_call(commonmarker_opts: [:FULL_INFO_STRING])
|
|
288
|
+
assert_equal true, capture[:render][:full_info_string]
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def test_commonmarker_accepts_legacy_no_op_options_without_raising
|
|
292
|
+
[
|
|
293
|
+
:VALIDATE_UTF8,
|
|
294
|
+
:LIBERAL_HTML_TAG,
|
|
295
|
+
:TABLE_PREFER_STYLE_ATTRIBUTES,
|
|
296
|
+
:STRIKETHROUGH_DOUBLE_TILDE,
|
|
297
|
+
].each do |opt|
|
|
298
|
+
capture = capture_commonmarker_call(commonmarker_opts: [opt])
|
|
299
|
+
refute capture[:render].key?(:sourcepos), "#{opt} should not alter render options"
|
|
300
|
+
refute capture[:parse].key?(:smart), "#{opt} should not alter parse options"
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def test_commonmarker_unknown_option_raises_argument_error
|
|
305
|
+
err = assert_raises(ArgumentError) do
|
|
306
|
+
capture_commonmarker_call(commonmarker_opts: [:TOTALLY_FAKE_OPT])
|
|
307
|
+
end
|
|
308
|
+
assert_match(/unknown commonmarker option:.*TOTALLY_FAKE_OPT/, err.message)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def test_commonmarker_header_ids_extension_sets_empty_string_prefix
|
|
312
|
+
capture = capture_commonmarker_call(commonmarker_exts: [:header_ids])
|
|
313
|
+
assert_equal "", capture[:extension][:header_ids]
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def test_commonmarker_unknown_extension_raises_argument_error
|
|
317
|
+
err = assert_raises(ArgumentError) do
|
|
318
|
+
capture_commonmarker_call(commonmarker_exts: [:not_a_real_ext])
|
|
319
|
+
end
|
|
320
|
+
assert_match(/unknown commonmarker extension:.*not_a_real_ext/, err.message)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
private
|
|
324
|
+
|
|
325
|
+
# Invokes the commonmarker MARKDOWN_GEMS proc against a stubbed Commonmarker
|
|
326
|
+
# constant and returns the parse/render/extension options the proc passed to
|
|
327
|
+
# Commonmarker.to_html.
|
|
328
|
+
def capture_commonmarker_call(options)
|
|
329
|
+
captured = {}
|
|
330
|
+
fake = Module.new
|
|
331
|
+
fake.define_singleton_method(:to_html) do |content, **kwargs|
|
|
332
|
+
captured[:content] = content
|
|
333
|
+
opts = kwargs.fetch(:options, {})
|
|
334
|
+
captured[:parse] = opts[:parse] || {}
|
|
335
|
+
captured[:render] = opts[:render] || {}
|
|
336
|
+
captured[:extension] = opts[:extension] || {}
|
|
337
|
+
"stub-html"
|
|
338
|
+
end
|
|
339
|
+
with_stub_const("Commonmarker", fake) do
|
|
340
|
+
GitHub::Markup::Markdown::MARKDOWN_GEMS
|
|
341
|
+
.fetch("commonmarker")
|
|
342
|
+
.call("payload", options: options)
|
|
343
|
+
end
|
|
344
|
+
captured
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def without_linguist
|
|
348
|
+
had_const = Object.const_defined?(:Linguist, false)
|
|
349
|
+
original = Object.const_get(:Linguist) if had_const
|
|
350
|
+
Object.send(:remove_const, :Linguist) if had_const
|
|
351
|
+
yield
|
|
352
|
+
ensure
|
|
353
|
+
Object.const_set(:Linguist, original) if had_const
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def fake_renderer_module(method_name)
|
|
357
|
+
Module.new do
|
|
358
|
+
define_singleton_method(method_name) { |content| "github_markdown:#{content}" }
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def instance_renderer_class(method_name, prefix:)
|
|
363
|
+
Class.new do
|
|
364
|
+
define_method(:initialize) { |content| @__coverage_content = "#{prefix}:#{content}" }
|
|
365
|
+
define_method(method_name) { @__coverage_content }
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
require "github/markup"
|
|
5
|
+
require "commonmarker"
|
|
6
|
+
require "minitest/autorun"
|
|
7
|
+
require "nokogiri"
|
|
8
|
+
|
|
9
|
+
class MarkdownTest < Minitest::Test
|
|
10
|
+
include ConstantStubbing
|
|
11
|
+
|
|
12
|
+
def test_uses_legacy_api_when_modern_interface_is_unavailable
|
|
13
|
+
legacy = Module.new
|
|
14
|
+
legacy.define_singleton_method(:render_html) do |content, _options, _extensions|
|
|
15
|
+
"<p>legacy #{content}</p>\n"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
with_stub_const("Commonmarker", Module.new) do
|
|
19
|
+
with_stub_const("CommonMarker", legacy) do
|
|
20
|
+
actual = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("commonmarker").call("payload")
|
|
21
|
+
assert_equal "<p>legacy payload</p>\n", actual
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_renders_through_both_public_entry_points
|
|
27
|
+
assert_equal "<p>hello</p>\n", GitHub::Markup.render("raw.md", "hello")
|
|
28
|
+
assert_equal "<p>hello</p>\n", GitHub::Markup.render_s(GitHub::Markups::MARKUP_MARKDOWN, "hello")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_preserves_code_language
|
|
32
|
+
document = Nokogiri::HTML.fragment(GitHub::Markup.render("raw.md", "```ruby\nputs 1\n```\n"))
|
|
33
|
+
assert_equal "ruby", document.at_css("pre")["lang"]
|
|
34
|
+
assert_equal "puts 1\n", document.at_css("code").text
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_preserves_unsafe_html_and_footnotes_without_mutating_frozen_options
|
|
38
|
+
options = {commonmarker_opts: [:UNSAFE, :FOOTNOTES].freeze}.freeze
|
|
39
|
+
html = GitHub::Markup.render("raw.md", "hello <b>world</b>[^1]\n\n[^1]: note\n", options: options)
|
|
40
|
+
document = Nokogiri::HTML.fragment(html)
|
|
41
|
+
|
|
42
|
+
assert_equal "world", document.at_css("b").text
|
|
43
|
+
assert_equal "1", document.at_css("sup a").text
|
|
44
|
+
assert_includes document.at_css("li p").text, "note"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_nobreaks_preserves_the_installed_backends_behavior
|
|
48
|
+
actual = GitHub::Markup.render("raw.md", "first\nsecond\n", options: {commonmarker_opts: [:NOBREAKS]})
|
|
49
|
+
expected = legacy_commonmarker? ? "<p>first second</p>\n" : "<p>first\nsecond</p>\n"
|
|
50
|
+
assert_equal expected, actual
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_conflicting_break_flags_preserve_the_installed_backends_precedence
|
|
54
|
+
actual = GitHub::Markup.render("raw.md", "first\nsecond\n", options: {commonmarker_opts: [:HARDBREAKS, :NOBREAKS]})
|
|
55
|
+
expected = legacy_commonmarker? ? "<p>first<br />\nsecond</p>\n" : "<p>first\nsecond</p>\n"
|
|
56
|
+
assert_equal expected, actual
|
|
57
|
+
|
|
58
|
+
reverse = GitHub::Markup.render("raw.md", "first\nsecond\n", options: {commonmarker_opts: [:NOBREAKS, :HARDBREAKS]})
|
|
59
|
+
assert_equal "<p>first<br />\nsecond</p>\n", reverse
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_table_style_option_and_frozen_extension_list
|
|
63
|
+
content = "| left | right |\n| :--- | ---: |\n| a | b |\n"
|
|
64
|
+
options = {commonmarker_opts: [:TABLE_PREFER_STYLE_ATTRIBUTES], commonmarker_exts: [:table].freeze}
|
|
65
|
+
document = Nokogiri::HTML.fragment(GitHub::Markup.render("raw.md", content, options: options))
|
|
66
|
+
|
|
67
|
+
assert_equal ["left", "right"], document.css("th").map(&:text)
|
|
68
|
+
attribute = legacy_commonmarker? ? "style" : "align"
|
|
69
|
+
expected = legacy_commonmarker? ? "text-align: left" : "left"
|
|
70
|
+
assert_equal expected, document.at_css("th")[attribute]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_double_tilde_option_preserves_the_installed_backends_behavior
|
|
74
|
+
options = {commonmarker_opts: [:STRIKETHROUGH_DOUBLE_TILDE], commonmarker_exts: [:strikethrough]}
|
|
75
|
+
actual = GitHub::Markup.render("raw.md", "~one~ ~~two~~\n", options: options)
|
|
76
|
+
expected = legacy_commonmarker? ? "<p>~one~ <del>two</del></p>\n" : "<p><del>one</del> <del>two</del></p>\n"
|
|
77
|
+
assert_equal expected, actual
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_extensions_follow_the_installed_parsers_capabilities
|
|
81
|
+
options = {commonmarker_exts: [:header_ids]}
|
|
82
|
+
if legacy_commonmarker?
|
|
83
|
+
error = assert_raises(ArgumentError) { GitHub::Markup.render("raw.md", "# Title", options: options) }
|
|
84
|
+
assert_match(/extension header_ids not found/, error.message)
|
|
85
|
+
else
|
|
86
|
+
document = Nokogiri::HTML.fragment(GitHub::Markup.render("raw.md", "# Title", options: options))
|
|
87
|
+
assert_equal "title", document.at_css("a[id]")["id"]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def legacy_commonmarker?
|
|
94
|
+
Gem.loaded_specs.fetch("commonmarker").version < Gem::Version.new("1")
|
|
95
|
+
end
|
|
96
|
+
end
|
data/test/markup_test.rb
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
|
|
4
4
|
|
|
5
|
+
require_relative 'test_helper'
|
|
6
|
+
require 'github-markup'
|
|
5
7
|
require 'github/markup'
|
|
6
8
|
require 'minitest/autorun'
|
|
7
|
-
require '
|
|
9
|
+
require 'html_pipeline'
|
|
8
10
|
require 'nokogiri'
|
|
9
11
|
require 'nokogiri/diff'
|
|
10
12
|
|
|
@@ -34,17 +36,17 @@ def assert_html_equal(expected, actual, msg = nil)
|
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
class MarkupTest < Minitest::Test
|
|
37
|
-
class MarkupFilter <
|
|
38
|
-
def call
|
|
39
|
-
filename = context[:filename]
|
|
39
|
+
class MarkupFilter < HTMLPipeline::ConvertFilter
|
|
40
|
+
def call(_text, context: {})
|
|
41
|
+
filename = context[:filename] || @context[:filename]
|
|
40
42
|
GitHub::Markup.render(filename, File.read(filename)).strip.force_encoding("utf-8")
|
|
41
43
|
end
|
|
42
44
|
end
|
|
43
45
|
|
|
44
|
-
Pipeline =
|
|
45
|
-
MarkupFilter,
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
Pipeline = HTMLPipeline.new(
|
|
47
|
+
convert_filter: MarkupFilter.new,
|
|
48
|
+
sanitization_config: HTMLPipeline::SanitizationFilter::DEFAULT_CONFIG
|
|
49
|
+
)
|
|
48
50
|
|
|
49
51
|
Dir['test/markups/README.*'].each do |readme|
|
|
50
52
|
next if readme =~ /html$/
|
|
@@ -55,7 +57,7 @@ class MarkupTest < Minitest::Test
|
|
|
55
57
|
source = File.read(readme)
|
|
56
58
|
expected_file = "#{readme}.html"
|
|
57
59
|
expected = File.read(expected_file).rstrip
|
|
58
|
-
actual = Pipeline.
|
|
60
|
+
actual = Pipeline.call("", context: { filename: readme })[:output].to_s
|
|
59
61
|
|
|
60
62
|
if source != expected
|
|
61
63
|
assert(source != actual, "#{markup} did not render anything")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<h1>Document Title</h1>
|
|
2
2
|
<div>
|
|
3
|
-
<h2>First Section</h2>
|
|
3
|
+
<h2 id="first-section">First Section</h2>
|
|
4
4
|
<div>
|
|
5
5
|
<div>
|
|
6
6
|
<ul>
|
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
<p>Refer to <a href="#another-section">Another Section</a> or <a href="#another-section-1">Another Section</a>.</p>
|
|
17
17
|
</div>
|
|
18
18
|
<div>
|
|
19
|
-
<p>Navigate from README.asciidoc to <a
|
|
19
|
+
<p>Navigate from README.asciidoc to <a>another document</a>.</p>
|
|
20
20
|
</div>
|
|
21
21
|
</div>
|
|
22
22
|
</div>
|
|
23
23
|
<div>
|
|
24
|
-
<h2>Another Section</h2>
|
|
24
|
+
<h2 id="another-section">Another Section</h2>
|
|
25
25
|
<div>
|
|
26
26
|
<div>
|
|
27
27
|
<table>
|
|
@@ -43,17 +43,17 @@ Here is some source code.
|
|
|
43
43
|
<div>
|
|
44
44
|
<ul>
|
|
45
45
|
<li>
|
|
46
|
-
<p
|
|
46
|
+
<p>❏ todo</p>
|
|
47
47
|
</li>
|
|
48
48
|
<li>
|
|
49
|
-
<p
|
|
49
|
+
<p>✓ done</p>
|
|
50
50
|
</li>
|
|
51
51
|
</ul>
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
54
54
|
</div>
|
|
55
55
|
<div>
|
|
56
|
-
<h2>Another Section</h2>
|
|
56
|
+
<h2 id="another-section-1">Another Section</h2>
|
|
57
57
|
<div>
|
|
58
58
|
<div>
|
|
59
59
|
<p>content</p>
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
<h1>H1</h1><h2>H2</h2><p>paragraph of text that will be turned into a paragraph element. It can go over several lines with line breaks, it will be turned into a contiguous paragraph element.</p><p>You can force a linebreak in your paragraph text<br
|
|
2
|
-
<li>a list element<ul><li>sub list element</li></ul>
|
|
3
|
-
</li>
|
|
4
|
-
<li>2nd list element</li>
|
|
5
|
-
</ul><pre>pre formatted text
|
|
1
|
+
<h1>H1</h1><h2>H2</h2><p>paragraph of text that will be turned into a paragraph element. It can go over several lines with line breaks, it will be turned into a contiguous paragraph element.</p><p>You can force a linebreak in your paragraph text<br/>thusly.</p><ul><li>a list element<ul><li>sub list element</li></ul></li><li>2nd list element</li></ul><pre>pre formatted text
|
|
6
2
|
|
|
7
3
|
$ ls -la
|
|
8
4
|
total 56
|
|
@@ -34,9 +34,9 @@ any conformant parser.</p>
|
|
|
34
34
|
<pre>
|
|
35
35
|
.. github display [on | off]
|
|
36
36
|
</pre>
|
|
37
|
-
<p>If you pass this the
|
|
37
|
+
<p>If you pass this the "on" value, then all unknown directives
|
|
38
38
|
will be displayed as literal code blocks. If you pass this
|
|
39
|
-
the
|
|
39
|
+
the "off" value, then unknown directives will not be displayed.</p>
|
|
40
40
|
<p>In addition to controlling the display of literal code blocks,
|
|
41
41
|
this also allows you to show comments specifically for github.</p>
|
|
42
42
|
<p>For example, somebody could place this at the top of their file:</p>
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
<p>Taken from <a href="https://github.com/jashkenas/coffee-script/blob/master/test/literate.litcoffee">https://github.com/jashkenas/coffee-script/blob/master/test/literate.litcoffee</a></p>
|
|
4
4
|
</blockquote>
|
|
5
5
|
<p>comment comment</p>
|
|
6
|
-
<pre><code>test
|
|
6
|
+
<pre><code>test "basic literate CoffeeScript parsing", ->
|
|
7
7
|
ok yes
|
|
8
8
|
</code></pre>
|
|
9
9
|
<p>now with a...</p>
|
|
10
|
-
<pre><code>test
|
|
10
|
+
<pre><code>test "broken up indentation", ->
|
|
11
11
|
</code></pre>
|
|
12
12
|
<p>... broken up ...</p>
|
|
13
13
|
<pre><code> do ->
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
<pre><code> ok yes
|
|
17
17
|
</code></pre>
|
|
18
18
|
<p>Code must be separated from text by a blank line.</p>
|
|
19
|
-
<pre><code>test
|
|
19
|
+
<pre><code>test "code blocks must be preceded by a blank line", ->
|
|
20
20
|
</code></pre>
|
|
21
21
|
<p>The next line is part of the text and will not be executed.
|
|
22
22
|
fail()</p>
|
|
23
23
|
<pre><code> ok yes
|
|
24
24
|
</code></pre>
|
|
25
25
|
<p>Code in <code>backticks is not parsed</code> and...</p>
|
|
26
|
-
<pre><code>test
|
|
26
|
+
<pre><code>test "comments in indented blocks work", ->
|
|
27
27
|
do ->
|
|
28
28
|
do ->
|
|
29
29
|
# Regular comment.
|
|
@@ -51,5 +51,5 @@ like links and unordered lists, are fine:</p>
|
|
|
51
51
|
</li>
|
|
52
52
|
</ul>
|
|
53
53
|
<p>Tabs work too:</p>
|
|
54
|
-
<p>test
|
|
54
|
+
<p>test "tabbed code", ->
|
|
55
55
|
ok yes</p>
|