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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +383 -32
  5. data/bin/brainstem +6 -0
  6. data/brainstem.gemspec +2 -0
  7. data/docs/api_doc_generator.markdown +175 -0
  8. data/docs/brainstem_executable.markdown +32 -0
  9. data/docs/docgen.png +0 -0
  10. data/docs/docgen_ascii.txt +63 -0
  11. data/docs/executable.png +0 -0
  12. data/docs/executable_ascii.txt +10 -0
  13. data/lib/brainstem/api_docs.rb +146 -0
  14. data/lib/brainstem/api_docs/abstract_collection.rb +116 -0
  15. data/lib/brainstem/api_docs/atlas.rb +158 -0
  16. data/lib/brainstem/api_docs/builder.rb +167 -0
  17. data/lib/brainstem/api_docs/controller.rb +122 -0
  18. data/lib/brainstem/api_docs/controller_collection.rb +40 -0
  19. data/lib/brainstem/api_docs/endpoint.rb +234 -0
  20. data/lib/brainstem/api_docs/endpoint_collection.rb +58 -0
  21. data/lib/brainstem/api_docs/exceptions.rb +8 -0
  22. data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +64 -0
  23. data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +76 -0
  24. data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +73 -0
  25. data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +169 -0
  26. data/lib/brainstem/api_docs/formatters/markdown/helper.rb +76 -0
  27. data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +200 -0
  28. data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +100 -0
  29. data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +232 -0
  30. data/lib/brainstem/api_docs/presenter.rb +225 -0
  31. data/lib/brainstem/api_docs/presenter_collection.rb +97 -0
  32. data/lib/brainstem/api_docs/resolver.rb +73 -0
  33. data/lib/brainstem/api_docs/sinks/abstract_sink.rb +37 -0
  34. data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +93 -0
  35. data/lib/brainstem/api_docs/sinks/stdout_sink.rb +44 -0
  36. data/lib/brainstem/cli.rb +146 -0
  37. data/lib/brainstem/cli/abstract_command.rb +97 -0
  38. data/lib/brainstem/cli/generate_api_docs_command.rb +169 -0
  39. data/lib/brainstem/concerns/controller_dsl.rb +300 -0
  40. data/lib/brainstem/concerns/controller_param_management.rb +30 -9
  41. data/lib/brainstem/concerns/formattable.rb +38 -0
  42. data/lib/brainstem/concerns/inheritable_configuration.rb +3 -2
  43. data/lib/brainstem/concerns/optional.rb +43 -0
  44. data/lib/brainstem/concerns/presenter_dsl.rb +76 -15
  45. data/lib/brainstem/controller_methods.rb +6 -3
  46. data/lib/brainstem/dsl/association.rb +6 -3
  47. data/lib/brainstem/dsl/associations_block.rb +6 -3
  48. data/lib/brainstem/dsl/base_block.rb +2 -4
  49. data/lib/brainstem/dsl/conditional.rb +7 -3
  50. data/lib/brainstem/dsl/conditionals_block.rb +4 -4
  51. data/lib/brainstem/dsl/configuration.rb +184 -8
  52. data/lib/brainstem/dsl/field.rb +6 -3
  53. data/lib/brainstem/dsl/fields_block.rb +2 -3
  54. data/lib/brainstem/help_text.txt +8 -0
  55. data/lib/brainstem/presenter.rb +27 -6
  56. data/lib/brainstem/presenter_validator.rb +5 -2
  57. data/lib/brainstem/time_classes.rb +1 -1
  58. data/lib/brainstem/version.rb +1 -1
  59. data/spec/brainstem/api_docs/abstract_collection_spec.rb +156 -0
  60. data/spec/brainstem/api_docs/atlas_spec.rb +353 -0
  61. data/spec/brainstem/api_docs/builder_spec.rb +100 -0
  62. data/spec/brainstem/api_docs/controller_collection_spec.rb +92 -0
  63. data/spec/brainstem/api_docs/controller_spec.rb +225 -0
  64. data/spec/brainstem/api_docs/endpoint_collection_spec.rb +144 -0
  65. data/spec/brainstem/api_docs/endpoint_spec.rb +346 -0
  66. data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +30 -0
  67. data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +126 -0
  68. data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +85 -0
  69. data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +261 -0
  70. data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +100 -0
  71. data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +485 -0
  72. data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +192 -0
  73. data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +170 -0
  74. data/spec/brainstem/api_docs/presenter_collection_spec.rb +84 -0
  75. data/spec/brainstem/api_docs/presenter_spec.rb +519 -0
  76. data/spec/brainstem/api_docs/resolver_spec.rb +72 -0
  77. data/spec/brainstem/api_docs/sinks/abstract_sink_spec.rb +16 -0
  78. data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +56 -0
  79. data/spec/brainstem/api_docs/sinks/stdout_sink_spec.rb +22 -0
  80. data/spec/brainstem/api_docs_spec.rb +58 -0
  81. data/spec/brainstem/cli/abstract_command_spec.rb +91 -0
  82. data/spec/brainstem/cli/generate_api_docs_command_spec.rb +125 -0
  83. data/spec/brainstem/cli_spec.rb +67 -0
  84. data/spec/brainstem/concerns/controller_dsl_spec.rb +471 -0
  85. data/spec/brainstem/concerns/controller_param_management_spec.rb +36 -16
  86. data/spec/brainstem/concerns/formattable_spec.rb +30 -0
  87. data/spec/brainstem/concerns/inheritable_configuration_spec.rb +104 -4
  88. data/spec/brainstem/concerns/optional_spec.rb +48 -0
  89. data/spec/brainstem/concerns/presenter_dsl_spec.rb +202 -31
  90. data/spec/brainstem/dsl/association_spec.rb +18 -2
  91. data/spec/brainstem/dsl/conditional_spec.rb +25 -2
  92. data/spec/brainstem/dsl/configuration_spec.rb +1 -1
  93. data/spec/brainstem/dsl/field_spec.rb +18 -2
  94. data/spec/brainstem/presenter_collection_spec.rb +10 -2
  95. data/spec/brainstem/presenter_spec.rb +32 -0
  96. data/spec/brainstem/presenter_validator_spec.rb +12 -7
  97. data/spec/dummy/rails.rb +49 -0
  98. data/spec/shared/atlas_taker.rb +18 -0
  99. data/spec/shared/formattable.rb +14 -0
  100. data/spec/spec_helper.rb +2 -0
  101. data/spec/spec_helpers/db.rb +1 -1
  102. data/spec/spec_helpers/presenters.rb +20 -14
  103. 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