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.
- checksums.yaml +4 -4
- data/.rubocop +1 -0
- data/.rubocop.yml +72 -0
- data/.travis.yml +7 -6
- data/Appraisals +3 -7
- data/Gemfile +2 -2
- data/Gemfile.lock +136 -18
- data/bin/appraisal +17 -0
- data/bin/rspec +17 -0
- data/bin/rubocop +17 -0
- data/gemfiles/rails_4.2.gemfile +1 -1
- data/gemfiles/rails_5.0.gemfile +1 -1
- data/gemfiles/rails_5.1.gemfile +1 -1
- data/lib/shortcode.rb +17 -41
- data/lib/shortcode/configuration.rb +3 -1
- data/lib/shortcode/parser.rb +21 -17
- data/lib/shortcode/presenter.rb +18 -15
- data/lib/shortcode/processor.rb +3 -1
- data/lib/shortcode/railtie.rb +2 -0
- data/lib/shortcode/tag.rb +11 -4
- data/lib/shortcode/template_binding.rb +12 -7
- data/lib/shortcode/transformer.rb +8 -4
- data/lib/shortcode/version.rb +3 -1
- data/shortcode.gemspec +10 -7
- data/spec/.rubocop.yml +17 -0
- data/spec/performance_spec.rb +4 -5
- data/spec/rails_helpers_spec.rb +25 -31
- data/spec/shortcode/parser_spec.rb +313 -0
- data/spec/shortcode/presenter_spec.rb +118 -0
- data/spec/shortcode/tag_spec.rb +73 -0
- data/spec/shortcode_spec.rb +40 -37
- data/spec/spec_helper.rb +6 -20
- data/spec/support/fixtures.rb +4 -6
- data/spec/support/presenters/missing_attributes_presenter.rb +3 -6
- data/spec/support/presenters/missing_content_presenter.rb +3 -6
- data/spec/support/presenters/missing_for_presenter.rb +3 -6
- data/spec/support/presenters/missing_initialize_presenter.rb +3 -6
- data/spec/support/presenters/multiple_presenter.rb +4 -5
- data/spec/support/presenters/my_presenter.rb +3 -4
- data/spec/support/presenters/other_presenter.rb +3 -4
- data/spec/template_parsers_spec.rb +21 -22
- data/spec/transformer_spec.rb +26 -36
- metadata +75 -24
- data/gemfiles/rails_4.1.gemfile +0 -8
- data/spec/parser_spec.rb +0 -307
- data/spec/presenter_spec.rb +0 -126
- 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
|