brainstem 1.0.0.pre.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/README.md +383 -32
- data/bin/brainstem +6 -0
- data/brainstem.gemspec +2 -0
- data/docs/api_doc_generator.markdown +175 -0
- data/docs/brainstem_executable.markdown +32 -0
- data/docs/docgen.png +0 -0
- data/docs/docgen_ascii.txt +63 -0
- data/docs/executable.png +0 -0
- data/docs/executable_ascii.txt +10 -0
- data/lib/brainstem/api_docs.rb +146 -0
- data/lib/brainstem/api_docs/abstract_collection.rb +116 -0
- data/lib/brainstem/api_docs/atlas.rb +158 -0
- data/lib/brainstem/api_docs/builder.rb +167 -0
- data/lib/brainstem/api_docs/controller.rb +122 -0
- data/lib/brainstem/api_docs/controller_collection.rb +40 -0
- data/lib/brainstem/api_docs/endpoint.rb +234 -0
- data/lib/brainstem/api_docs/endpoint_collection.rb +58 -0
- data/lib/brainstem/api_docs/exceptions.rb +8 -0
- data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +64 -0
- data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +76 -0
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +73 -0
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +169 -0
- data/lib/brainstem/api_docs/formatters/markdown/helper.rb +76 -0
- data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +200 -0
- data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +100 -0
- data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +232 -0
- data/lib/brainstem/api_docs/presenter.rb +225 -0
- data/lib/brainstem/api_docs/presenter_collection.rb +97 -0
- data/lib/brainstem/api_docs/resolver.rb +73 -0
- data/lib/brainstem/api_docs/sinks/abstract_sink.rb +37 -0
- data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +93 -0
- data/lib/brainstem/api_docs/sinks/stdout_sink.rb +44 -0
- data/lib/brainstem/cli.rb +146 -0
- data/lib/brainstem/cli/abstract_command.rb +97 -0
- data/lib/brainstem/cli/generate_api_docs_command.rb +169 -0
- data/lib/brainstem/concerns/controller_dsl.rb +300 -0
- data/lib/brainstem/concerns/controller_param_management.rb +30 -9
- data/lib/brainstem/concerns/formattable.rb +38 -0
- data/lib/brainstem/concerns/inheritable_configuration.rb +3 -2
- data/lib/brainstem/concerns/optional.rb +43 -0
- data/lib/brainstem/concerns/presenter_dsl.rb +76 -15
- data/lib/brainstem/controller_methods.rb +6 -3
- data/lib/brainstem/dsl/association.rb +6 -3
- data/lib/brainstem/dsl/associations_block.rb +6 -3
- data/lib/brainstem/dsl/base_block.rb +2 -4
- data/lib/brainstem/dsl/conditional.rb +7 -3
- data/lib/brainstem/dsl/conditionals_block.rb +4 -4
- data/lib/brainstem/dsl/configuration.rb +184 -8
- data/lib/brainstem/dsl/field.rb +6 -3
- data/lib/brainstem/dsl/fields_block.rb +2 -3
- data/lib/brainstem/help_text.txt +8 -0
- data/lib/brainstem/presenter.rb +27 -6
- data/lib/brainstem/presenter_validator.rb +5 -2
- data/lib/brainstem/time_classes.rb +1 -1
- data/lib/brainstem/version.rb +1 -1
- data/spec/brainstem/api_docs/abstract_collection_spec.rb +156 -0
- data/spec/brainstem/api_docs/atlas_spec.rb +353 -0
- data/spec/brainstem/api_docs/builder_spec.rb +100 -0
- data/spec/brainstem/api_docs/controller_collection_spec.rb +92 -0
- data/spec/brainstem/api_docs/controller_spec.rb +225 -0
- data/spec/brainstem/api_docs/endpoint_collection_spec.rb +144 -0
- data/spec/brainstem/api_docs/endpoint_spec.rb +346 -0
- data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +30 -0
- data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +126 -0
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +85 -0
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +261 -0
- data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +100 -0
- data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +485 -0
- data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +192 -0
- data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +170 -0
- data/spec/brainstem/api_docs/presenter_collection_spec.rb +84 -0
- data/spec/brainstem/api_docs/presenter_spec.rb +519 -0
- data/spec/brainstem/api_docs/resolver_spec.rb +72 -0
- data/spec/brainstem/api_docs/sinks/abstract_sink_spec.rb +16 -0
- data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +56 -0
- data/spec/brainstem/api_docs/sinks/stdout_sink_spec.rb +22 -0
- data/spec/brainstem/api_docs_spec.rb +58 -0
- data/spec/brainstem/cli/abstract_command_spec.rb +91 -0
- data/spec/brainstem/cli/generate_api_docs_command_spec.rb +125 -0
- data/spec/brainstem/cli_spec.rb +67 -0
- data/spec/brainstem/concerns/controller_dsl_spec.rb +471 -0
- data/spec/brainstem/concerns/controller_param_management_spec.rb +36 -16
- data/spec/brainstem/concerns/formattable_spec.rb +30 -0
- data/spec/brainstem/concerns/inheritable_configuration_spec.rb +104 -4
- data/spec/brainstem/concerns/optional_spec.rb +48 -0
- data/spec/brainstem/concerns/presenter_dsl_spec.rb +202 -31
- data/spec/brainstem/dsl/association_spec.rb +18 -2
- data/spec/brainstem/dsl/conditional_spec.rb +25 -2
- data/spec/brainstem/dsl/configuration_spec.rb +1 -1
- data/spec/brainstem/dsl/field_spec.rb +18 -2
- data/spec/brainstem/presenter_collection_spec.rb +10 -2
- data/spec/brainstem/presenter_spec.rb +32 -0
- data/spec/brainstem/presenter_validator_spec.rb +12 -7
- data/spec/dummy/rails.rb +49 -0
- data/spec/shared/atlas_taker.rb +18 -0
- data/spec/shared/formattable.rb +14 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/spec_helpers/db.rb +1 -1
- data/spec/spec_helpers/presenters.rb +20 -14
- metadata +106 -6
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'brainstem/api_docs/formatters/markdown/endpoint_collection_formatter'
|
3
|
+
require 'brainstem/api_docs/endpoint_collection'
|
4
|
+
|
5
|
+
module Brainstem
|
6
|
+
module ApiDocs
|
7
|
+
module Formatters
|
8
|
+
module Markdown
|
9
|
+
describe EndpointCollectionFormatter do
|
10
|
+
let(:atlas) { Object.new }
|
11
|
+
let(:endpoint_collection) { EndpointCollection.new(atlas) }
|
12
|
+
let(:nodoc) { false }
|
13
|
+
|
14
|
+
subject { described_class.new(endpoint_collection) }
|
15
|
+
|
16
|
+
describe "#call" do
|
17
|
+
it "formats each endpoint" do
|
18
|
+
mock(subject).format_endpoints!
|
19
|
+
stub(subject).format_zero_text!
|
20
|
+
subject.call
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when has content" do
|
24
|
+
before do
|
25
|
+
stub(subject).output { "not empty" }
|
26
|
+
end
|
27
|
+
|
28
|
+
it "does not format zero text" do
|
29
|
+
dont_allow(subject).format_zero_text!
|
30
|
+
subject.call
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when no content" do
|
35
|
+
it "formats zero text" do
|
36
|
+
stub(subject).format_endpoints!
|
37
|
+
mock(subject).format_zero_text!
|
38
|
+
subject.call
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
describe "formatting" do
|
45
|
+
describe "#format_endpoints!" do
|
46
|
+
it "joins each documentable endpoint" do
|
47
|
+
mock(subject).all_formatted_endpoints { ["thing 1", "thing 2"] }
|
48
|
+
subject.send(:format_endpoints!)
|
49
|
+
expect(subject.output).to eq "thing 1-----\n\nthing 2"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
describe "#format_zero_text!" do
|
55
|
+
it "appends the zero text" do
|
56
|
+
subject.send(:format_zero_text!)
|
57
|
+
expect(subject.output).to eq "No endpoints were found."
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
describe "#all_formatted_endpoints" do
|
64
|
+
it "retrieves all formatted endpoints" do
|
65
|
+
documentable_collection = Object.new
|
66
|
+
mock(documentable_collection).formatted(:markdown) { [] }
|
67
|
+
stub(endpoint_collection).only_documentable { documentable_collection }
|
68
|
+
subject.send(:all_formatted_endpoints)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "rejects blank endpoints" do
|
72
|
+
stub(endpoint_collection).only_documentable
|
73
|
+
.stub!.formatted(:markdown) { [ "thing 1", "", "thing 3" ] }
|
74
|
+
|
75
|
+
expect(subject.send(:all_formatted_endpoints)).to \
|
76
|
+
eq [ "thing 1", "thing 3" ]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'brainstem/api_docs/formatters/markdown/endpoint_formatter'
|
3
|
+
require 'brainstem/api_docs/endpoint'
|
4
|
+
|
5
|
+
module Brainstem
|
6
|
+
module ApiDocs
|
7
|
+
module Formatters
|
8
|
+
module Markdown
|
9
|
+
describe EndpointFormatter do
|
10
|
+
let(:controller) { Object.new }
|
11
|
+
let(:atlas) { Object.new }
|
12
|
+
let(:endpoint) { Endpoint.new(atlas, {controller: controller }.merge(endpoint_args)) }
|
13
|
+
let(:endpoint_args) { {} }
|
14
|
+
let(:nodoc) { false }
|
15
|
+
|
16
|
+
subject { described_class.new(endpoint) }
|
17
|
+
|
18
|
+
describe "#call" do
|
19
|
+
before do
|
20
|
+
stub(endpoint).nodoc? { nodoc }
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when it is nodoc" do
|
24
|
+
let(:nodoc) { true }
|
25
|
+
|
26
|
+
before do
|
27
|
+
any_instance_of(described_class) do |instance|
|
28
|
+
dont_allow(instance).format_title!
|
29
|
+
dont_allow(instance).format_description!
|
30
|
+
dont_allow(instance).format_endpoint!
|
31
|
+
dont_allow(instance).format_params!
|
32
|
+
dont_allow(instance).format_presents!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "returns an empty output" do
|
37
|
+
expect(subject.call).to eq ""
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "when it is not nodoc" do
|
42
|
+
it "formats title, description, endpoint, params, and presents" do
|
43
|
+
any_instance_of(described_class) do |instance|
|
44
|
+
mock(instance).format_title!
|
45
|
+
mock(instance).format_description!
|
46
|
+
mock(instance).format_endpoint!
|
47
|
+
mock(instance).format_params!
|
48
|
+
mock(instance).format_presents!
|
49
|
+
end
|
50
|
+
|
51
|
+
subject.call
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "formatting" do
|
57
|
+
let(:lorem) { "lorem ipsum dolor sit amet" }
|
58
|
+
let(:default_config) { {} }
|
59
|
+
let(:show_config) { {} }
|
60
|
+
|
61
|
+
let(:configuration) { {
|
62
|
+
:_default => default_config,
|
63
|
+
:show => show_config,
|
64
|
+
} }
|
65
|
+
|
66
|
+
let(:endpoint_args) { { action: :show } }
|
67
|
+
|
68
|
+
before do
|
69
|
+
stub(controller).configuration { configuration }
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "#format_title!" do
|
73
|
+
it "prints it as an h4" do
|
74
|
+
stub(endpoint).title { lorem }
|
75
|
+
mock(subject).md_h4(lorem) { lorem }
|
76
|
+
subject.send(:format_title!)
|
77
|
+
expect(subject.output).to eq lorem
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
describe "#format_description!" do
|
83
|
+
context "when present" do
|
84
|
+
before do
|
85
|
+
stub(endpoint).description { lorem }
|
86
|
+
end
|
87
|
+
|
88
|
+
it "prints it as a p" do
|
89
|
+
mock(subject).md_p(lorem) { lorem }
|
90
|
+
subject.send(:format_description!)
|
91
|
+
expect(subject.output).to eq lorem
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "when absent" do
|
96
|
+
before do
|
97
|
+
stub(endpoint).description { "" }
|
98
|
+
end
|
99
|
+
|
100
|
+
it "prints nothing" do
|
101
|
+
dont_allow(subject).md_p
|
102
|
+
subject.send(:format_description!)
|
103
|
+
expect(subject.output).to eq ""
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
describe "#format_endpoint!" do
|
111
|
+
let(:endpoint_args) { { http_methods: %w(get post), path: "/widgets(.:format)" } }
|
112
|
+
|
113
|
+
it "formats it as code, subbing the appropriate format" do
|
114
|
+
mock(subject).md_code("GET / POST /widgets.json") { lorem }
|
115
|
+
subject.send(:format_endpoint!)
|
116
|
+
expect(subject.output).to eq lorem
|
117
|
+
end
|
118
|
+
|
119
|
+
it "includes the joined HTTP methods" do
|
120
|
+
subject.send(:format_endpoint!)
|
121
|
+
expect(subject.output).to include "GET / POST"
|
122
|
+
end
|
123
|
+
|
124
|
+
it "includes the path" do
|
125
|
+
subject.send(:format_endpoint!)
|
126
|
+
expect(subject.output).to include "/widgets"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
describe "#format_params!" do
|
132
|
+
before do
|
133
|
+
subject.send(:format_params!)
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
context "with valid params" do
|
138
|
+
let(:show_config) { {
|
139
|
+
valid_params: {
|
140
|
+
only: { info: "which ids to include", nodoc: nodoc },
|
141
|
+
sprocket_id: { info: "the id of the sprocket", root: "widget", nodoc: nodoc },
|
142
|
+
sprocket_child: { recursive: true, legacy: false, info: "it does the thing", root: "widget" },
|
143
|
+
}
|
144
|
+
} }
|
145
|
+
|
146
|
+
context "when nodoc" do
|
147
|
+
let(:nodoc) { true }
|
148
|
+
|
149
|
+
it "removes them from the list" do
|
150
|
+
expect(subject.output).to include "`widget`"
|
151
|
+
expect(subject.output).to include "`sprocket_child`"
|
152
|
+
expect(subject.output).not_to include "`sprocket_id`"
|
153
|
+
expect(subject.output).not_to include "`only`"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context "when not nodoc" do
|
158
|
+
it "outputs a header" do
|
159
|
+
expect(subject.output).to include "Valid Parameters"
|
160
|
+
end
|
161
|
+
|
162
|
+
it "spits each root item out as a list item" do
|
163
|
+
expect(subject.output.scan(/\n-/).count).to eq 2
|
164
|
+
end
|
165
|
+
|
166
|
+
it "makes the key an inline code block" do
|
167
|
+
expect(subject.output).to include "`sprocket_id`"
|
168
|
+
end
|
169
|
+
|
170
|
+
context "for non-root params" do
|
171
|
+
it "outputs sub params under a list item" do
|
172
|
+
expect(subject.output).to include "- `widget`\n - `sprocket_id` - the id of the sprocket\n - `sprocket_child`"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
it "includes the info on a hash key" do
|
177
|
+
expect(subject.output).to include "`sprocket_child` - it does the thing"
|
178
|
+
end
|
179
|
+
|
180
|
+
it "includes the recursivity if specified" do
|
181
|
+
expect(subject.output).to include "Recursive: true"
|
182
|
+
end
|
183
|
+
|
184
|
+
it "includes the legacy status if specified" do
|
185
|
+
expect(subject.output).to include "Legacy: false"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
context "with only default params" do
|
192
|
+
let(:default_config) { {
|
193
|
+
valid_params: {
|
194
|
+
sprocket_name: {
|
195
|
+
info: "the name of the sprocket",
|
196
|
+
nodoc: nodoc
|
197
|
+
}
|
198
|
+
}
|
199
|
+
} }
|
200
|
+
|
201
|
+
context "when nodoc" do
|
202
|
+
let(:nodoc) { true }
|
203
|
+
|
204
|
+
it "shows no parameters" do
|
205
|
+
expect(subject.output).to eq ""
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context "when not nodoc" do
|
210
|
+
it "falls back to the default" do
|
211
|
+
expect(subject.output).to include "`sprocket_name` - the name of the sprocket"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context "with no valid params" do
|
217
|
+
it "outputs nothing" do
|
218
|
+
expect(subject.output).to eq ""
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
describe "#format_presents!" do
|
226
|
+
let(:presenter) { Object.new }
|
227
|
+
|
228
|
+
before do
|
229
|
+
stub(endpoint).presenter_title { "Sprocket Widget" }
|
230
|
+
stub(endpoint).relative_presenter_path_from_controller(:markdown) { "../../sprocket_widget.markdown" }
|
231
|
+
end
|
232
|
+
|
233
|
+
context "when present" do
|
234
|
+
before do
|
235
|
+
stub(endpoint).presenter { presenter }
|
236
|
+
end
|
237
|
+
|
238
|
+
it "outputs a header" do
|
239
|
+
subject.send(:format_presents!)
|
240
|
+
expect(subject.output).to include "Data Model"
|
241
|
+
end
|
242
|
+
|
243
|
+
it "displays a link" do
|
244
|
+
subject.send(:format_presents!)
|
245
|
+
expect(subject.output).to include "[Sprocket Widget](../../sprocket_widget.markdown)"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
context "when not present" do
|
250
|
+
it "does not output anything" do
|
251
|
+
subject.send(:format_presents!)
|
252
|
+
expect(subject.output).not_to include "Data Model"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'brainstem/api_docs/formatters/markdown/helper'
|
3
|
+
|
4
|
+
module Brainstem
|
5
|
+
module ApiDocs
|
6
|
+
module Formatters
|
7
|
+
module Markdown
|
8
|
+
describe "Helper" do
|
9
|
+
let(:klass) { Class.new { include Helper } }
|
10
|
+
|
11
|
+
let(:lipsum) { "Lorem ipsum dolor sit amet, nulla primis tritani id qui. Eam elitr prodesset in, ne nec nibh elit impedit. Inani rationibus mnesarchum ei vix, at has eligendi scripserit. Vel vidit semper ea. Id ullum scaevola adversarium eum, vide brute mucius ut has." }
|
12
|
+
let(:lipsum_split_80) { [
|
13
|
+
"Lorem ipsum dolor sit amet, nulla primis tritani id qui. Eam elitr prodesset in,",
|
14
|
+
"ne nec nibh elit impedit. Inani rationibus mnesarchum ei vix, at has eligendi",
|
15
|
+
"scripserit. Vel vidit semper ea. Id ullum scaevola adversarium eum, vide brute",
|
16
|
+
"mucius ut has."
|
17
|
+
] }
|
18
|
+
|
19
|
+
subject { klass.new }
|
20
|
+
|
21
|
+
1.upto(5) do |num|
|
22
|
+
describe "#md_h#{num}" do
|
23
|
+
it "renders text with #{num} # and two newlines" do
|
24
|
+
expect(subject.send("md_h#{num}", "title")).to eq "#{"#" * num} title\n\n"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#md_hr" do
|
30
|
+
it "renders five dashes and two newlines" do
|
31
|
+
expect(subject.md_hr).to eq "-----\n\n"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
describe "#md_p" do
|
37
|
+
it "appends two newlines" do
|
38
|
+
expect(subject.md_p(lipsum)).to eq(lipsum + "\n\n")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
describe "#md_strong" do
|
44
|
+
it "wraps the text in asterisk pairs" do
|
45
|
+
expect(subject.md_strong("Popeye")).to eq "**Popeye**"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
describe "#md_code" do
|
51
|
+
it "renders the code between backtick blocks" do
|
52
|
+
expect(subject.md_code('var my_var = 1;')).to eq "```\nvar my_var = 1;\n```\n\n"
|
53
|
+
end
|
54
|
+
|
55
|
+
it "allows specifying a language" do
|
56
|
+
expect(subject.md_code('puts "Hi!"', 'ruby')).to eq %Q{```ruby\nputs "Hi!"\n```\n\n}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
describe "#md_inline_code" do
|
62
|
+
it "renders the code between single backticks" do
|
63
|
+
expect(subject.md_inline_code('my_var')).to eq "`my_var`"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
describe "#md_ul" do
|
69
|
+
it "evaluates the block given to it in the instance's context" do
|
70
|
+
mock(subject).md_h1('hello') { }
|
71
|
+
subject.md_ul { md_h1("hello") }
|
72
|
+
end
|
73
|
+
|
74
|
+
it "appends two newlines to the end of the list" do
|
75
|
+
expect(subject.md_ul { nil }).to eq "\n\n"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
describe "#md_li" do
|
81
|
+
it "renders the text after a dash-space with a newline" do
|
82
|
+
expect(subject.md_li("text")).to eq "- text\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
it "allows specifying the indent" do
|
86
|
+
expect(subject.md_li("text", 1)).to eq " - text\n"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
describe "#md_a" do
|
92
|
+
it "renders the text in a bracket and includes a link in parens" do
|
93
|
+
expect(subject.md_a("text", "link.md")).to eq "[text](link.md)"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,485 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'brainstem/api_docs/formatters/markdown/presenter_formatter'
|
3
|
+
require 'brainstem/api_docs/presenter'
|
4
|
+
|
5
|
+
module Brainstem
|
6
|
+
module ApiDocs
|
7
|
+
module Formatters
|
8
|
+
module Markdown
|
9
|
+
describe PresenterFormatter do
|
10
|
+
let(:presenter_args) { {} }
|
11
|
+
let(:extra_presenter_args) { {} }
|
12
|
+
let(:presenter) { Presenter.new(presenter_args.merge(extra_presenter_args)) }
|
13
|
+
let(:nodoc) { false }
|
14
|
+
|
15
|
+
subject { described_class.new(presenter) }
|
16
|
+
|
17
|
+
describe "#call" do
|
18
|
+
before do
|
19
|
+
stub(presenter).nodoc? { nodoc }
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when nodoc" do
|
23
|
+
let(:nodoc) { true }
|
24
|
+
|
25
|
+
it "returns an empty output" do
|
26
|
+
any_instance_of(described_class) do |instance|
|
27
|
+
dont_allow(instance).format_title!
|
28
|
+
dont_allow(instance).format_brainstem_keys!
|
29
|
+
dont_allow(instance).format_description!
|
30
|
+
dont_allow(instance).format_fields!
|
31
|
+
dont_allow(instance).format_filters!
|
32
|
+
dont_allow(instance).format_sort_orders!
|
33
|
+
dont_allow(instance).format_associations!
|
34
|
+
end
|
35
|
+
|
36
|
+
expect(subject.call).to eq ""
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when not nodoc" do
|
41
|
+
it "formats title, brainstem_keys, description, conditionals, fields, filters, sort_orders, and associations" do
|
42
|
+
any_instance_of(described_class) do |instance|
|
43
|
+
mock(instance).format_title!
|
44
|
+
mock(instance).format_brainstem_keys!
|
45
|
+
mock(instance).format_description!
|
46
|
+
mock(instance).format_fields!
|
47
|
+
mock(instance).format_filters!
|
48
|
+
mock(instance).format_sort_orders!
|
49
|
+
mock(instance).format_associations!
|
50
|
+
end
|
51
|
+
|
52
|
+
subject.call
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
describe "formatting" do
|
59
|
+
let(:lorem) { "lorem ipsum dolor sit amet" }
|
60
|
+
|
61
|
+
describe "#format_title!" do
|
62
|
+
context "with title" do
|
63
|
+
it "prints it as an h4" do
|
64
|
+
stub(presenter).title { lorem }
|
65
|
+
mock(subject).md_h4(lorem) { lorem }
|
66
|
+
subject.send(:format_title!)
|
67
|
+
expect(subject.output).to eq lorem
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
describe "#format_brainstem_keys!" do
|
74
|
+
before do
|
75
|
+
stub(presenter).brainstem_keys { [ "sprockets", "widgets" ] }
|
76
|
+
end
|
77
|
+
|
78
|
+
it "outputs it" do
|
79
|
+
subject.send(:format_brainstem_keys!)
|
80
|
+
expect(subject.output).to include "Top-level key: `sprockets` / `widgets`"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
describe "#format_description!" do
|
86
|
+
context "when present" do
|
87
|
+
before do
|
88
|
+
stub(presenter).description { lorem }
|
89
|
+
end
|
90
|
+
|
91
|
+
it "prints it as a p" do
|
92
|
+
mock(subject).md_p(lorem) { lorem }
|
93
|
+
subject.send(:format_description!)
|
94
|
+
expect(subject.output).to eq lorem
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when absent" do
|
99
|
+
before do
|
100
|
+
stub(presenter).description { "" }
|
101
|
+
end
|
102
|
+
|
103
|
+
it "prints nothing" do
|
104
|
+
dont_allow(subject).md_p
|
105
|
+
subject.send(:format_description!)
|
106
|
+
expect(subject.output).to eq ""
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
describe "#format_fields!" do
|
113
|
+
let(:valid_fields) { {} }
|
114
|
+
let(:conditionals) { {} }
|
115
|
+
let(:optional) { false }
|
116
|
+
|
117
|
+
let(:sprocket_name_long) { OpenStruct.new(
|
118
|
+
name: :sprocket_name,
|
119
|
+
description: lorem,
|
120
|
+
options: { via: :name },
|
121
|
+
type: :string
|
122
|
+
) }
|
123
|
+
|
124
|
+
let(:sprocket_name_short) { OpenStruct.new(
|
125
|
+
name: :sprocket_name,
|
126
|
+
type: :string,
|
127
|
+
options: { }
|
128
|
+
) }
|
129
|
+
|
130
|
+
before do
|
131
|
+
stub(sprocket_name_long).optional? { optional }
|
132
|
+
stub(sprocket_name_short).optional? { optional }
|
133
|
+
stub(presenter).conditionals { conditionals }
|
134
|
+
stub(presenter).valid_fields { valid_fields }
|
135
|
+
subject.send(:format_fields!)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "outputs a header" do
|
139
|
+
expect(subject.output).to include "Fields"
|
140
|
+
end
|
141
|
+
|
142
|
+
context "with fields present" do
|
143
|
+
context "branch node" do
|
144
|
+
context "with single branch" do
|
145
|
+
let(:valid_fields) { { sprockets: { name: sprocket_name_long } } }
|
146
|
+
|
147
|
+
it "outputs the name of the branch as a list item" do
|
148
|
+
expect(subject.output.scan(/\n-/).count).to eq 1
|
149
|
+
expect(subject.output.scan(/\n -/).count).to eq 1
|
150
|
+
expect(subject.output.scan(/\n -/).count).to eq 1
|
151
|
+
end
|
152
|
+
|
153
|
+
it "outputs the child nodes as sub-list items" do
|
154
|
+
expect(subject.output).to \
|
155
|
+
include("\n- `sprockets`\n - `sprocket_name`")
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context "with sub-branch" do
|
160
|
+
let(:valid_fields) { { sprockets: { sub_sprocket: { name: sprocket_name_long } } } }
|
161
|
+
|
162
|
+
it "outputs the name of sub-branches as a sub-list item" do
|
163
|
+
expect(subject.output.scan(/\n-/).count).to eq 1
|
164
|
+
expect(subject.output.scan(/\n -/).count).to eq 1
|
165
|
+
expect(subject.output.scan(/\n -/).count).to eq 1
|
166
|
+
expect(subject.output.scan(/\n -/).count).to eq 1
|
167
|
+
end
|
168
|
+
|
169
|
+
it "outputs the child nodes as sub-list items" do
|
170
|
+
expect(subject.output).to \
|
171
|
+
include("\n- `sprockets`\n - `sub_sprocket`\n - `sprocket_name`")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context "leaf node" do
|
177
|
+
let(:valid_fields) { { sprocket_name: sprocket_name_long } }
|
178
|
+
|
179
|
+
context "if it is not conditional" do
|
180
|
+
it "outputs each field as a list item" do
|
181
|
+
expect(subject.output.scan(/\n-/).count).to eq 1
|
182
|
+
end
|
183
|
+
|
184
|
+
it "outputs each field's name" do
|
185
|
+
expect(subject.output).to include "sprocket_name"
|
186
|
+
end
|
187
|
+
|
188
|
+
it "outputs each field's type" do
|
189
|
+
expect(subject.output).to include "`sprocket_name` (`String`)"
|
190
|
+
end
|
191
|
+
|
192
|
+
describe "optional" do
|
193
|
+
context "when true" do
|
194
|
+
let(:optional) { true }
|
195
|
+
|
196
|
+
it "says so" do
|
197
|
+
expect(subject.output).to include "only returned when requested"
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
context "when false" do
|
203
|
+
it "says nothing" do
|
204
|
+
expect(subject.output).not_to include "Only returned when requested"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe "description" do
|
210
|
+
context "when present" do
|
211
|
+
it "outputs the description" do
|
212
|
+
expect(subject.output).to include " - #{lorem}"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context "when absent" do
|
217
|
+
let(:valid_fields) { { sprocket_name: sprocket_name_short } }
|
218
|
+
|
219
|
+
it "does not include the description" do
|
220
|
+
expect(subject.output).not_to include " -"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
context "if it is conditional" do
|
228
|
+
let(:sprocket_name_long) { OpenStruct.new(
|
229
|
+
name: :sprocket_name,
|
230
|
+
description: lorem,
|
231
|
+
options: { via: :name, if: [:it_is_a_friday] },
|
232
|
+
type: :string
|
233
|
+
) }
|
234
|
+
|
235
|
+
context "if nodoc" do
|
236
|
+
let(:conditionals) { {
|
237
|
+
:it_is_a_friday => OpenStruct.new(
|
238
|
+
description: nil,
|
239
|
+
name: :it_is_a_friday,
|
240
|
+
type: :request,
|
241
|
+
options: { nodoc: true }
|
242
|
+
)
|
243
|
+
} }
|
244
|
+
|
245
|
+
it "does not include the conditional" do
|
246
|
+
expect(subject.output).not_to include "visible when"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context "if not nodoc" do
|
251
|
+
context "if the conditional has a description" do
|
252
|
+
let(:conditionals) { {
|
253
|
+
:it_is_a_friday => OpenStruct.new(
|
254
|
+
description: "it is a friday",
|
255
|
+
name: :it_is_a_friday,
|
256
|
+
type: :request,
|
257
|
+
options: {},
|
258
|
+
)
|
259
|
+
} }
|
260
|
+
|
261
|
+
it "includes the conditional" do
|
262
|
+
expect(subject.output).to include "\n - visible when it is a friday"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context "if the condition doesn't have a description" do
|
267
|
+
let(:conditionals) { {
|
268
|
+
:it_is_a_friday => OpenStruct.new(
|
269
|
+
description: nil,
|
270
|
+
name: :it_is_a_friday,
|
271
|
+
type: :request,
|
272
|
+
options: {},
|
273
|
+
)
|
274
|
+
} }
|
275
|
+
|
276
|
+
it "does not include the conditional" do
|
277
|
+
expect(subject.output).not_to include "visible when"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context "with no fields" do
|
286
|
+
it "outputs that no fields were listed" do
|
287
|
+
subject.send(:format_fields!)
|
288
|
+
expect(subject.output).to include "No fields were listed"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
describe "#format_filters!" do
|
295
|
+
let(:valid_filters) { {} }
|
296
|
+
|
297
|
+
before do
|
298
|
+
stub(presenter).valid_filters { valid_filters }
|
299
|
+
subject.send(:format_filters!)
|
300
|
+
end
|
301
|
+
|
302
|
+
context "when has filters" do
|
303
|
+
let(:valid_filters) {
|
304
|
+
{
|
305
|
+
"published" => {
|
306
|
+
value: Proc.new { nil },
|
307
|
+
info: "limits to published"
|
308
|
+
}
|
309
|
+
}
|
310
|
+
}
|
311
|
+
|
312
|
+
it "outputs a header" do
|
313
|
+
expect(subject.output).to include "Filters"
|
314
|
+
end
|
315
|
+
|
316
|
+
it "lists them" do
|
317
|
+
expect(subject.output.scan(/\n-/).count).to eq 1
|
318
|
+
expect(subject.output).to include "`published`"
|
319
|
+
expect(subject.output).to include " - limits to published"
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
context "when no filters" do
|
324
|
+
it "says nothing" do
|
325
|
+
expect(subject.output).to_not include "Filters"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
|
331
|
+
describe "format_sort_orders!" do
|
332
|
+
before do
|
333
|
+
stub(presenter).valid_sort_orders { sort_orders }
|
334
|
+
stub(presenter).default_sort_field { "alphabetical" }
|
335
|
+
stub(presenter).default_sort_direction { "asc" }
|
336
|
+
end
|
337
|
+
|
338
|
+
let(:sort_orders) { {
|
339
|
+
"created_at" => {
|
340
|
+
value: "sprockets.created_at"
|
341
|
+
},
|
342
|
+
"alphabetical" => {
|
343
|
+
value: "sprockets.name",
|
344
|
+
info: "it sorts by name"
|
345
|
+
},
|
346
|
+
} }
|
347
|
+
|
348
|
+
before do
|
349
|
+
subject.send(:format_sort_orders!)
|
350
|
+
end
|
351
|
+
|
352
|
+
context "when has sort orders defined" do
|
353
|
+
it "outputs a header" do
|
354
|
+
expect(subject.output).to include "Sort Orders"
|
355
|
+
end
|
356
|
+
|
357
|
+
it "lists the sort orders with the default first" do
|
358
|
+
expect(subject.output.scan(/\n-/).count).to eq 2
|
359
|
+
expect(subject.output).to match /alphabetical.*created_at/m
|
360
|
+
end
|
361
|
+
|
362
|
+
it "outputs the doc string" do
|
363
|
+
expect(subject.output).to include " - it sorts by name"
|
364
|
+
end
|
365
|
+
|
366
|
+
context "when it has a default sort order" do
|
367
|
+
it "inlines the default" do
|
368
|
+
expect(subject.output).to include "`alphabetical` - **default** (asc)"
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
context "when has no sort orders defined" do
|
374
|
+
let(:sort_orders) { {} }
|
375
|
+
|
376
|
+
it "says nothing" do
|
377
|
+
expect(subject.output).to_not include "Sort Orders"
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
|
383
|
+
describe "format_associations!" do
|
384
|
+
let(:associations) { {} }
|
385
|
+
let(:link) { nil }
|
386
|
+
|
387
|
+
before do
|
388
|
+
stub(presenter).valid_associations { associations }
|
389
|
+
stub(presenter).link_for_association(anything) { link }
|
390
|
+
subject.send(:format_associations!)
|
391
|
+
end
|
392
|
+
|
393
|
+
context "when has associations" do
|
394
|
+
let(:associations) {
|
395
|
+
{
|
396
|
+
"widgets" => OpenStruct.new(
|
397
|
+
name: "widgets",
|
398
|
+
description: "these are some widgets you might find relevant",
|
399
|
+
target_class: "Widget"
|
400
|
+
)
|
401
|
+
}
|
402
|
+
}
|
403
|
+
|
404
|
+
it "outputs a header" do
|
405
|
+
expect(subject.output).to include "Associations"
|
406
|
+
end
|
407
|
+
|
408
|
+
context "when has static target class" do
|
409
|
+
let(:link) { "./path" }
|
410
|
+
|
411
|
+
it "links them" do
|
412
|
+
expect(subject.output).to include "[Widget](./path)"
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
context "when has polymorphic target class or presenter was not found" do
|
417
|
+
let(:link) { nil }
|
418
|
+
|
419
|
+
it "lists them" do
|
420
|
+
expect(subject.output).to include "`widgets`"
|
421
|
+
end
|
422
|
+
|
423
|
+
it "does not render a link" do
|
424
|
+
expect(subject.output).to_not include "[Widget]"
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
describe "description" do
|
429
|
+
context "when present" do
|
430
|
+
it "lists it" do
|
431
|
+
expect(subject.output).to include "these are some widgets"
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
describe "restrict_to_only" do
|
437
|
+
context "when present and true" do
|
438
|
+
let(:associations) {
|
439
|
+
{
|
440
|
+
"widgets" => OpenStruct.new(
|
441
|
+
name: "widgets",
|
442
|
+
description: "these are some widgets you might find relevant",
|
443
|
+
options: { restrict_to_only: true }
|
444
|
+
)
|
445
|
+
}
|
446
|
+
}
|
447
|
+
|
448
|
+
it "lists it" do
|
449
|
+
expect(subject.output).to include "Restricted to queries using"
|
450
|
+
end
|
451
|
+
|
452
|
+
it "adds a period at the end of the description if there is none" do
|
453
|
+
expect(subject.output).to include "you might find relevant. Restricted to queries using"
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
context "when absent or false" do
|
458
|
+
let(:associations) {
|
459
|
+
{
|
460
|
+
"widgets" => OpenStruct.new(
|
461
|
+
name: "widgets",
|
462
|
+
options: { restrict_to_only: false }
|
463
|
+
)
|
464
|
+
}
|
465
|
+
}
|
466
|
+
|
467
|
+
it "doesn't show it" do
|
468
|
+
expect(subject.output).not_to include "Restricted to queries using"
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
context "when no associations" do
|
475
|
+
it "says nothing" do
|
476
|
+
expect(subject.output).to_not include "Associations"
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|