shortcode 1.2.1 → 2.0.0.pre

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop +1 -0
  3. data/.rubocop.yml +72 -0
  4. data/.travis.yml +7 -6
  5. data/Appraisals +3 -7
  6. data/Gemfile +2 -2
  7. data/Gemfile.lock +136 -18
  8. data/bin/appraisal +17 -0
  9. data/bin/rspec +17 -0
  10. data/bin/rubocop +17 -0
  11. data/gemfiles/rails_4.2.gemfile +1 -1
  12. data/gemfiles/rails_5.0.gemfile +1 -1
  13. data/gemfiles/rails_5.1.gemfile +1 -1
  14. data/lib/shortcode.rb +17 -41
  15. data/lib/shortcode/configuration.rb +3 -1
  16. data/lib/shortcode/parser.rb +21 -17
  17. data/lib/shortcode/presenter.rb +18 -15
  18. data/lib/shortcode/processor.rb +3 -1
  19. data/lib/shortcode/railtie.rb +2 -0
  20. data/lib/shortcode/tag.rb +11 -4
  21. data/lib/shortcode/template_binding.rb +12 -7
  22. data/lib/shortcode/transformer.rb +8 -4
  23. data/lib/shortcode/version.rb +3 -1
  24. data/shortcode.gemspec +10 -7
  25. data/spec/.rubocop.yml +17 -0
  26. data/spec/performance_spec.rb +4 -5
  27. data/spec/rails_helpers_spec.rb +25 -31
  28. data/spec/shortcode/parser_spec.rb +313 -0
  29. data/spec/shortcode/presenter_spec.rb +118 -0
  30. data/spec/shortcode/tag_spec.rb +73 -0
  31. data/spec/shortcode_spec.rb +40 -37
  32. data/spec/spec_helper.rb +6 -20
  33. data/spec/support/fixtures.rb +4 -6
  34. data/spec/support/presenters/missing_attributes_presenter.rb +3 -6
  35. data/spec/support/presenters/missing_content_presenter.rb +3 -6
  36. data/spec/support/presenters/missing_for_presenter.rb +3 -6
  37. data/spec/support/presenters/missing_initialize_presenter.rb +3 -6
  38. data/spec/support/presenters/multiple_presenter.rb +4 -5
  39. data/spec/support/presenters/my_presenter.rb +3 -4
  40. data/spec/support/presenters/other_presenter.rb +3 -4
  41. data/spec/template_parsers_spec.rb +21 -22
  42. data/spec/transformer_spec.rb +26 -36
  43. metadata +75 -24
  44. data/gemfiles/rails_4.1.gemfile +0 -8
  45. data/spec/parser_spec.rb +0 -307
  46. data/spec/presenter_spec.rb +0 -126
  47. data/spec/tag_spec.rb +0 -86
@@ -0,0 +1,313 @@
1
+ # rubocop:disable RSpec/ExampleLength
2
+ require "spec_helper"
3
+ require "parslet/rig/rspec"
4
+ require "pp"
5
+
6
+ describe Shortcode::Parser do
7
+ let(:configuration) { Shortcode.new.configuration }
8
+ let(:parser) { described_class.new(configuration) }
9
+
10
+ let(:simple_quote) { load_fixture :simple_quote }
11
+ let(:full_quote) { load_fixture :full_quote }
12
+ let(:without_quotes) { load_fixture :without_quotes }
13
+ let(:quote_with_extras) { load_fixture :quote_with_extras }
14
+ let(:simple_list) { load_fixture :simple_list }
15
+ let(:timeline_event) { load_fixture :timeline_event }
16
+ let(:timeline_info) { load_fixture :timeline_info }
17
+ let(:timeline_person) { load_fixture :timeline_person }
18
+ let(:complex_snippet) { load_fixture :complex_snippet }
19
+
20
+ let(:quotes) { [simple_quote, full_quote, quote_with_extras] }
21
+ let(:collapsible_lists) { [simple_list] }
22
+ let(:timelines) { [timeline_event, timeline_info, timeline_person] }
23
+
24
+ it "parses quote shortcodes" do
25
+ quotes.each do |string|
26
+ expect(parser).to parse(string)
27
+ end
28
+ end
29
+
30
+ describe "matching similar tags" do
31
+ context "with the smaller tag listed first" do
32
+ before do
33
+ configuration.block_tags = %i[xx xxx]
34
+ end
35
+
36
+ it "parses xx" do
37
+ expect(parser.open).to parse("[xx]")
38
+ end
39
+
40
+ it "parses xxx" do
41
+ expect(parser.open).to parse("[xxx]")
42
+ end
43
+ end
44
+
45
+ context "with the smaller tag listed last" do
46
+ before do
47
+ configuration.block_tags = %i[xxx xx]
48
+ end
49
+
50
+ it "parses xx" do
51
+ expect(parser.open).to parse("[xx]")
52
+ end
53
+
54
+ it "parses xxx" do
55
+ expect(parser.open).to parse("[xxx]")
56
+ end
57
+ end
58
+ end
59
+
60
+ context "attribute_quote_type configuration" do
61
+ before do
62
+ configuration.attribute_quote_type = "'"
63
+ end
64
+
65
+ it "parses quotes using custom quotations" do
66
+ quotes.each do |string|
67
+ # Change double quotes to single quotes in the fixture
68
+ expect(parser).to parse(string.tr('"', "'"))
69
+ end
70
+ end
71
+ end
72
+
73
+ context "use_attribute_quotes configuration" do
74
+ before do
75
+ configuration.use_attribute_quotes = false
76
+ end
77
+
78
+ it "parses shortcodes with optional quotes" do
79
+ expect(parser).to parse(without_quotes)
80
+ end
81
+ end
82
+
83
+ it "parses list shortcodes" do
84
+ collapsible_lists.each do |string|
85
+ expect(parser).to parse(string)
86
+ end
87
+ end
88
+
89
+ it "parses timeline shortcodes" do
90
+ timelines.each do |string|
91
+ expect(parser).to parse(string)
92
+ end
93
+ end
94
+
95
+ it "parses complex nested shortcodes" do
96
+ expect(parser).to parse(complex_snippet)
97
+ end
98
+
99
+ describe "parsed strings" do
100
+ context "simple_quote" do
101
+ let(:parsed_object) { parser.parse(simple_quote) }
102
+
103
+ before do
104
+ configuration.block_tags = [:quote]
105
+ end
106
+
107
+ it "created the expected object" do
108
+ expect(parsed_object[:body])
109
+ .to eq([{ open: "quote", options: [], inner: [{ text: "hello" }], close: "quote" }])
110
+ end
111
+ end
112
+
113
+ context "full_quote" do
114
+ let(:parsed_object) { parser.parse(full_quote) }
115
+
116
+ before do
117
+ configuration.block_tags = [:quote]
118
+ end
119
+
120
+ it "created the expected object" do
121
+ expect(parsed_object[:body]).to eq([{
122
+ open: "quote",
123
+ options: [
124
+ { key: "author", value: "Jamie Dyer" },
125
+ { key: "title", value: "King of England" }
126
+ ],
127
+ inner: [{ text: "A quote" }],
128
+ close: "quote"
129
+ }])
130
+ end
131
+ end
132
+
133
+ context "quote_with_extras" do
134
+ let(:parsed_object) { parser.parse(quote_with_extras) }
135
+
136
+ before do
137
+ configuration.block_tags = [:quote]
138
+ end
139
+
140
+ it "created the expected object" do
141
+ expect(parsed_object[:body]).to eq([
142
+ { text: "Blah blah " },
143
+ {
144
+ open: "quote",
145
+ options: [
146
+ { key: "author", value: "Jamie Dyer" }
147
+ ],
148
+ inner: [{ text: "A quote" }],
149
+ close: "quote"
150
+ },
151
+ { text: " <br> blah blah\n" }
152
+ ])
153
+ end
154
+ end
155
+
156
+ context "simple_list" do
157
+ let(:parsed_object) { parser.parse(simple_list) }
158
+
159
+ before do
160
+ configuration.block_tags = %i[collapsible_list item]
161
+ end
162
+
163
+ it "created the expected object" do
164
+ expect(parsed_object[:body]).to eq([{
165
+ open: "collapsible_list",
166
+ options: [],
167
+ inner: [{
168
+ open: "item",
169
+ options: [{ key: "title", value: "Example title 1" }],
170
+ inner: [{ text: "Example content 1" }],
171
+ close: "item"
172
+ },
173
+ {
174
+ open: "item",
175
+ options: [{ key: "title", value: "Example title 2" }],
176
+ inner: [{ text: "Example content 2" }],
177
+ close: "item"
178
+ },
179
+ {
180
+ open: "item",
181
+ options: [{ key: "title", value: "Example title 3" }],
182
+ inner: [{ text: "Example content 3" }],
183
+ close: "item"
184
+ }],
185
+ close: "collapsible_list"
186
+ }])
187
+ end
188
+ end
189
+
190
+ context "timeline_event" do
191
+ let(:parsed_object) { parser.parse(timeline_event) }
192
+
193
+ before do
194
+ configuration.self_closing_tags = [:timeline_event]
195
+ end
196
+
197
+ it "created the expected object" do
198
+ expect(parsed_object[:body]).to eq([{
199
+ open_close: "timeline_event",
200
+ options: [
201
+ { key: "date", value: "March 2013" },
202
+ { key: "title", value: "a title" },
203
+ { key: "link", value: "http://blah.com" }
204
+ ]
205
+ }])
206
+ end
207
+ end
208
+
209
+ context "timeline_info" do
210
+ let(:parsed_object) { parser.parse(timeline_info) }
211
+
212
+ before do
213
+ configuration.self_closing_tags = [:timeline_info]
214
+ end
215
+
216
+ it "created the expected object" do
217
+ expect(parsed_object[:body]).to eq([{
218
+ open_close: "timeline_info",
219
+ options: [
220
+ { key: "date", value: "Feb 2013" },
221
+ { key: "title", value: "Something amazing" }
222
+ ]
223
+ }])
224
+ end
225
+ end
226
+
227
+ context "timeline_person" do
228
+ let(:parsed_object) { parser.parse(timeline_person) }
229
+
230
+ before do
231
+ configuration.block_tags = [:timeline_person]
232
+ end
233
+
234
+ it "created the expected object" do
235
+ expect(parsed_object[:body]).to eq([{
236
+ open: "timeline_person",
237
+ options: [
238
+ { key: "date", value: "Jan 2012" },
239
+ { key: "title", value: "A real title" },
240
+ { key: "image", value: "/images/person.jpg" },
241
+ { key: "name", value: "Sam Smith" },
242
+ { key: "position", value: "Presedent" },
243
+ { key: "link", value: "http://blah.com" }
244
+ ],
245
+ inner: [{ text: "A bit of body copy\nwith a newline\n" }],
246
+ close: "timeline_person"
247
+ }])
248
+ end
249
+ end
250
+
251
+ context "complex_snippet" do
252
+ let(:parsed_object) { parser.parse(complex_snippet) }
253
+
254
+ before do
255
+ configuration.block_tags = %i[quote collapsible_list item]
256
+ end
257
+
258
+ it "created the expected object" do
259
+ expect(parsed_object[:body]).to eq(
260
+ [
261
+ { text: "<h3>A page title</h3>\n<p>Some text</p>\n" },
262
+ {
263
+ open: "collapsible_list",
264
+ options: [],
265
+ inner: [
266
+ {
267
+ open: "item",
268
+ options: [{ key: "title", value: "Example title 1" }],
269
+ inner: [
270
+ {
271
+ open: "quote",
272
+ options: [
273
+ { key: "author", value: "Jamie Dyer" },
274
+ { key: "title", value: "King of England" }
275
+ ],
276
+ inner: [{ text: "A quote" }],
277
+ close: "quote"
278
+ },
279
+ { text: "I'm inbetween 2 quotes!\n " },
280
+ {
281
+ open: "quote",
282
+ options: [
283
+ { key: "author", value: "Forrest Gump" },
284
+ { key: "title", value: "Idiot" }
285
+ ],
286
+ inner: [{ text: "Life is like..." }],
287
+ close: "quote"
288
+ }
289
+ ],
290
+ close: "item"
291
+ },
292
+ {
293
+ open: "item",
294
+ options: [{ key: "title", value: "Example title 2" }],
295
+ inner: [{ text: "Example content 2" }],
296
+ close: "item"
297
+ },
298
+ {
299
+ open: "item",
300
+ options: [{ key: "title", value: "Example title 3" }],
301
+ inner: [{ text: "Example content 3" }],
302
+ close: "item"
303
+ }
304
+ ],
305
+ close: "collapsible_list"
306
+ },
307
+ { text: "\n<p>Some more text</p>\n" }
308
+ ]
309
+ )
310
+ end
311
+ end
312
+ end
313
+ end
@@ -0,0 +1,118 @@
1
+ require "spec_helper"
2
+ require "parslet/rig/rspec"
3
+ require "pp"
4
+
5
+ require "support/presenters/my_presenter"
6
+ require "support/presenters/other_presenter"
7
+ require "support/presenters/multiple_presenter"
8
+ require "support/presenters/missing_for_presenter"
9
+ require "support/presenters/missing_initialize_presenter"
10
+ require "support/presenters/missing_content_presenter"
11
+ require "support/presenters/missing_attributes_presenter"
12
+
13
+ describe Shortcode::Presenter do
14
+ let(:shortcode) { Shortcode.new }
15
+ let(:configuration) { shortcode.configuration }
16
+ let(:simple_quote) { load_fixture :simple_quote }
17
+ let(:item) { load_fixture :item }
18
+
19
+ before do
20
+ configuration.block_tags = %i[quote item]
21
+ configuration.template_path = File.join File.dirname(__FILE__), "../support/templates/erb"
22
+ end
23
+
24
+ describe "using a custom presenter" do
25
+ let(:presenter_output) { load_fixture :simple_quote_presenter_output, :html }
26
+ let(:attributes_output) { load_fixture :simple_quote_presenter_attributes_output, :html }
27
+
28
+ before do
29
+ shortcode.register_presenter MyPresenter
30
+ end
31
+
32
+ it "uses the custom attributes" do
33
+ expect(shortcode.process(simple_quote).delete("\n")).to eq(presenter_output)
34
+ end
35
+
36
+ it "passes through additional attributes" do
37
+ expect(shortcode.process(simple_quote, title: "Additional attribute title").delete("\n"))
38
+ .to eq(attributes_output)
39
+ end
40
+ end
41
+
42
+ describe "using a single presenter for multiple shortcodes" do
43
+ let(:quote_presenter_output) { load_fixture :simple_quote_presenter_output, :html }
44
+ let(:item_presenter_output) { load_fixture :item_presenter_output, :html }
45
+
46
+ let(:quote_attributes_output) { load_fixture :simple_quote_presenter_attributes_output, :html }
47
+ let(:item_attributes_output) { load_fixture :item_presenter_attributes_output, :html }
48
+
49
+ before do
50
+ shortcode.register_presenter MultiplePresenter
51
+ end
52
+
53
+ it "uses the custom attributes" do
54
+ expect(shortcode.process(simple_quote).delete("\n")).to eq(quote_presenter_output)
55
+ expect(shortcode.process(item).delete("\n")).to eq(item_presenter_output)
56
+ end
57
+
58
+ it "passes through additional attributes" do
59
+ expect(shortcode.process(simple_quote, title: "Additional attribute title").delete("\n"))
60
+ .to eq(quote_attributes_output)
61
+ expect(shortcode.process(item, title: "Additional attribute title").delete("\n"))
62
+ .to eq(item_attributes_output)
63
+ end
64
+ end
65
+
66
+ context "presenter registration" do
67
+ describe "registering a single presenter" do
68
+ before do
69
+ shortcode.register_presenter MyPresenter
70
+ end
71
+
72
+ it "adds the presenter to the list" do
73
+ expect(configuration.presenters).to include(MyPresenter.for)
74
+ end
75
+ end
76
+
77
+ describe "registering multiple presenters" do
78
+ before do
79
+ shortcode.register_presenter(MyPresenter, OtherPresenter)
80
+ end
81
+
82
+ it "adds the presenter to the list" do
83
+ expect(configuration.presenters).to include(MyPresenter.for)
84
+ expect(configuration.presenters).to include(OtherPresenter.for)
85
+ end
86
+ end
87
+ end
88
+
89
+ context "presenter validation" do
90
+ describe "missing #for class method" do
91
+ it "raises an exception" do
92
+ expect { shortcode.register_presenter MissingForPresenter }
93
+ .to raise_error(ArgumentError, "The presenter must define the class method #for")
94
+ end
95
+ end
96
+
97
+ describe "missing #initialize method" do
98
+ it "raises an exception" do
99
+ expect { shortcode.register_presenter MissingInitializePresenter }
100
+ .to raise_error(ArgumentError, "The presenter must define an initialize method")
101
+ end
102
+ end
103
+
104
+ describe "missing #content method" do
105
+ it "raises an exception" do
106
+ expect { shortcode.register_presenter MissingContentPresenter }
107
+ .to raise_error(ArgumentError, "The presenter must define the method #content")
108
+ end
109
+ end
110
+
111
+ describe "missing #attributes method" do
112
+ it "raises an exception" do
113
+ expect { shortcode.register_presenter MissingAttributesPresenter }
114
+ .to raise_error(ArgumentError, "The presenter must define the method #attributes")
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,73 @@
1
+ require "spec_helper"
2
+
3
+ describe Shortcode::Tag do
4
+ let(:configuration) {
5
+ Shortcode.new.tap { |s|
6
+ s.setup do |config|
7
+ config.template_path = File.join File.dirname(__FILE__), "../support/templates/erb"
8
+ end
9
+ }.configuration
10
+ }
11
+
12
+ context "when the template file is missing" do
13
+ let(:tag) { described_class.new("doesnt_exist", configuration) }
14
+
15
+ it "raises a TemplateNotFound error when the file doesn't exists" do
16
+ expect { tag.render }.to raise_error(Shortcode::TemplateNotFound)
17
+ end
18
+ end
19
+
20
+ context "when an unsupported template parser is specified" do
21
+ before do
22
+ configuration.template_parser = :something_crazy
23
+ end
24
+
25
+ let(:tag) { described_class.new("quote", configuration) }
26
+
27
+ it "raises a TemplateNotFound error when the file doesn't exists" do
28
+ expect { tag.render }.to raise_error(Shortcode::TemplateParserNotSupported)
29
+ end
30
+ end
31
+
32
+ context "templates from strings" do
33
+ let(:tag) { described_class.new("from_string", configuration, [{ key: "string", value: "batman" }]) }
34
+
35
+ before do
36
+ configuration.templates[:from_string] = "<p><%= @attributes[:string] %></p>"
37
+ end
38
+
39
+ it "renders a template from a string" do
40
+ expect(tag.render).to eq("<p>batman</p>")
41
+ end
42
+ end
43
+
44
+ context "when the template is missing from the config" do
45
+ let(:tag) { described_class.new("missing", configuration, [{ key: "string", value: "batman" }]) }
46
+
47
+ before do
48
+ configuration.templates[:from_string] = "<p><%= @attributes[:string] %></p>"
49
+ end
50
+
51
+ it "raises an error" do
52
+ expect { tag.render }.to raise_error(Shortcode::TemplateNotFound)
53
+ end
54
+ end
55
+
56
+ context "when templates exist both in configuration and file system" do
57
+ let(:tag) { described_class.new("quote", configuration) }
58
+
59
+ before do
60
+ configuration.templates[:quote] = "<p><%= @content %></p>"
61
+ end
62
+
63
+ it "uses the configuration template when check_config_templates_first is true" do
64
+ expect(tag.markup).to eq("<p><%= @content %></p>")
65
+ end
66
+
67
+ it "uses the file system template when check_config_templates_first is false" do
68
+ configuration.check_config_templates_first = false
69
+
70
+ expect(tag.markup).to eq(File.open("spec/support/templates/erb/quote.html.erb").read)
71
+ end
72
+ end
73
+ end