brainstem 1.0.0.pre.1 → 1.0.0

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 (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,192 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/introspectors/abstract_introspector'
3
+
4
+ module Brainstem
5
+ module ApiDocs
6
+ module Introspectors
7
+ describe AbstractIntrospector do
8
+ subject { AbstractIntrospector.send(:new) }
9
+
10
+ describe ".with_loaded_environment" do
11
+ it "passes along all options" do
12
+ any_instance_of(AbstractIntrospector) { |instance| stub(instance).one=(1) }
13
+ mock.proxy(AbstractIntrospector).new(one: 1) do |obj|
14
+ stub(obj) do |stub|
15
+ stub.load_environment!
16
+ end
17
+ end
18
+
19
+ AbstractIntrospector.with_loaded_environment(one: 1)
20
+ end
21
+
22
+ it "invokes #load_environment! on the instance" do
23
+ stub.proxy(AbstractIntrospector).new do |obj|
24
+ mock(obj).load_environment!
25
+ end
26
+
27
+ AbstractIntrospector.with_loaded_environment
28
+ end
29
+
30
+ it "returns the instance" do
31
+ instance = Object.new
32
+ stub(instance).load_environment!
33
+ stub.proxy(AbstractIntrospector).new { |_| instance }
34
+
35
+ expect(AbstractIntrospector.with_loaded_environment).to eq instance
36
+ end
37
+ end
38
+
39
+ describe "#initialize" do
40
+ it "is private" do
41
+ expect { AbstractIntrospector.new }.to raise_error NoMethodError
42
+ expect { AbstractIntrospector.send(:new) }.not_to raise_error
43
+ end
44
+ end
45
+
46
+
47
+ describe "#load_environment!" do
48
+ it "is not implemented" do
49
+ expect { subject.send(:load_environment!) }.to raise_error NotImplementedError
50
+ end
51
+
52
+ end
53
+
54
+ describe "#controllers" do
55
+ it "is not implemented" do
56
+ expect { subject.controllers }.to raise_error NotImplementedError
57
+ end
58
+ end
59
+
60
+ describe "#presenters" do
61
+ it "is not implemented" do
62
+ expect { subject.presenters }.to raise_error NotImplementedError
63
+ end
64
+ end
65
+
66
+ describe "#routes" do
67
+ it "is not implemented" do
68
+ expect { subject.routes }.to raise_error NotImplementedError
69
+ end
70
+ end
71
+
72
+ describe "#valid?" do
73
+ let! (:controllers_valid) { true }
74
+ let! (:presenters_valid) { true }
75
+ let! (:routes_valid) { true }
76
+
77
+ before do
78
+ stub(subject) do |s|
79
+ s.valid_controllers? { controllers_valid }
80
+ s.valid_presenters? { presenters_valid }
81
+ s.valid_routes? { routes_valid }
82
+ end
83
+ end
84
+
85
+ context "when controllers, presenters, and routes are valid" do
86
+ it "is valid" do
87
+ expect(subject.valid?).to eq true
88
+ end
89
+ end
90
+
91
+ context "when controllers are invalid" do
92
+ let(:controllers_valid) { false }
93
+
94
+ it "is not valid" do
95
+ expect(subject.valid?).to eq false
96
+ end
97
+
98
+ end
99
+
100
+ context "when presenters are invalid" do
101
+ let(:presenters_valid) { false }
102
+
103
+ it "is not valid" do
104
+ expect(subject.valid?).to eq false
105
+ end
106
+ end
107
+
108
+ context "when routes are invalid" do
109
+ let(:routes_valid) { false }
110
+
111
+ it "is not valid" do
112
+ expect(subject.valid?).to eq false
113
+ end
114
+ end
115
+ end
116
+
117
+ describe "#valid_controllers?" do
118
+ it "is valid when a collection of at least one class" do
119
+ stub(subject).controllers { [ Integer ] }
120
+ expect(subject.send(:valid_controllers?)).to eq true
121
+ end
122
+
123
+ it "is invalid when not a collection" do
124
+ stub(subject).controllers { { dog: "woof" } }
125
+ expect(subject.send(:valid_controllers?)).to eq false
126
+ end
127
+
128
+ it "is invalid when empty" do
129
+ stub(subject).controllers { [] }
130
+ expect(subject.send(:valid_controllers?)).to eq false
131
+ end
132
+ end
133
+
134
+
135
+ describe "#valid_presenters?" do
136
+ it "is valid when a collection of zero or more classes" do
137
+ stub(subject).presenters { [ Integer ] }
138
+ expect(subject.send(:valid_presenters?)).to eq true
139
+ end
140
+
141
+ it "is valid when empty" do
142
+ stub(subject).presenters { [] }
143
+ expect(subject.send(:valid_presenters?)).to eq true
144
+ end
145
+
146
+ it "is invalid when not a collection" do
147
+ stub(subject).presenters { { dog: "woof" } }
148
+ expect(subject.send(:valid_presenters?)).to eq false
149
+ end
150
+
151
+ end
152
+
153
+ describe "#valid_routes?" do
154
+ it "is valid when a collection of hashes with specific keys" do
155
+ stub(subject).routes { [
156
+ {
157
+ path: "blah",
158
+ controller: "blah",
159
+ action: "blah",
160
+ http_methods: ['blah']
161
+ }
162
+ ] }
163
+
164
+ expect(subject.send(:valid_routes?)).to eq true
165
+ end
166
+
167
+ it "is invalid when not a collection" do
168
+ stub(subject).routes { {} }
169
+ expect(subject.send(:valid_routes?)).to eq false
170
+ end
171
+
172
+ it "is invalid when empty" do
173
+ stub(subject).routes { [] }
174
+ expect(subject.send(:valid_routes?)).to eq false
175
+ end
176
+
177
+ it "is invalid if any one hash is missing a required key" do
178
+ stub(subject).routes { [
179
+ {
180
+ path: "blah",
181
+ controller: "blah",
182
+ action: "blah"
183
+ }
184
+ ] }
185
+
186
+ expect(subject.send(:valid_routes?)).to eq false
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,170 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/introspectors/rails_introspector'
3
+
4
+ module Brainstem
5
+ module ApiDocs
6
+ module Introspectors
7
+ describe RailsIntrospector do
8
+ let(:dummy_environment_file) do
9
+ File.expand_path('../../../../../spec/dummy/rails.rb', __FILE__)
10
+ end
11
+
12
+ let(:described_klass) { RailsIntrospector }
13
+ let(:default_args) { { rails_environment_file: dummy_environment_file } }
14
+
15
+ subject do
16
+ RailsIntrospector.send(:new, default_args)
17
+ end
18
+
19
+
20
+ context "when cannot find the environment file" do
21
+ describe "#load_environment!" do
22
+ subject { described_klass.send(:new) }
23
+
24
+ before do
25
+ # In the event that we've already loaded the environment through
26
+ # random ordering of specs, we want to force a reload.
27
+ #
28
+ # For some reason, this has stopped being needed.
29
+ Object.send(:remove_const, :Rails) if defined?(Rails)
30
+ end
31
+
32
+ it "raises an error" do
33
+ # Testing this within Brainstem should fail.
34
+
35
+ expect { subject.send(:load_environment!) }
36
+ .to raise_error IncorrectIntrospectorForAppException
37
+ end
38
+ end
39
+ end
40
+
41
+ context "when can find the entrypoint file" do
42
+ describe "#load_environment!" do
43
+ context "when introspector is invalid" do
44
+ before do
45
+ stub(subject).valid? { false }
46
+ end
47
+
48
+ it "raises an error" do
49
+ expect { subject.send(:load_environment!) }.to \
50
+ raise_error InvalidIntrospectorError
51
+ end
52
+ end
53
+
54
+ context "when introspector is valid" do
55
+ before do
56
+ stub(subject).valid? { true }
57
+ end
58
+
59
+ it "does not raise an error" do
60
+ expect { subject.send(:load_environment!) }.not_to raise_error
61
+ end
62
+
63
+ it "does not load the file if Rails is defined" do
64
+ # Ensure that this has been sent already.
65
+ subject.send(:load_environment!)
66
+
67
+ dont_allow(subject).rails_environment_file
68
+ mock(subject).env_already_loaded? { true }
69
+
70
+ subject.send(:load_environment!)
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "#presenters" do
76
+ before do
77
+ stub.any_instance_of(described_klass).validate!
78
+ end
79
+
80
+ subject do
81
+ described_klass.with_loaded_environment(
82
+ default_args.merge(base_presenter_class: "::FakeBasePresenter")
83
+ )
84
+ end
85
+
86
+
87
+ it "allows the specification of a custom base_presenter_class" do
88
+ expect(subject.send(:base_presenter_class).to_s)
89
+ .to eq "::FakeBasePresenter"
90
+ end
91
+
92
+ it "returns the descendants of the base presenter class" do
93
+ expect(subject.presenters).to eq [FakeDescendantPresenter]
94
+ end
95
+ end
96
+
97
+ describe "#controllers" do
98
+ before do
99
+ stub.any_instance_of(described_klass).validate!
100
+ end
101
+
102
+ subject do
103
+ described_klass.with_loaded_environment(
104
+ default_args.merge(base_controller_class: "::FakeBaseController")
105
+ )
106
+ end
107
+
108
+
109
+ it "allows the specification of a custom base_controller_class" do
110
+ expect(subject.send(:base_controller_class).to_s)
111
+ .to eq "::FakeBaseController"
112
+ end
113
+
114
+ it "returns the descendants of the base controller class" do
115
+ expect(subject.controllers).to eq [FakeDescendantController]
116
+ end
117
+ end
118
+
119
+ describe "#routes" do
120
+ let(:a_proc) { Object.new }
121
+
122
+ before do
123
+ stub.any_instance_of(described_klass).validate!
124
+ end
125
+
126
+ context "with dummy method" do
127
+ subject do
128
+ described_klass.with_loaded_environment(
129
+ default_args.merge(routes_method: a_proc)
130
+ )
131
+ end
132
+
133
+ it "allows the specification of a custom method to return the routes" do
134
+ expect(subject.send(:routes_method)).to eq a_proc
135
+ end
136
+
137
+ it "calls the routes method to return the routes" do
138
+ mock(a_proc).call
139
+ subject.routes
140
+ end
141
+ end
142
+
143
+ context "with fake (but realistic) data" do
144
+ subject do
145
+ described_klass.with_loaded_environment(default_args)
146
+ end
147
+
148
+ it "skips the entry if it does not have a valid controller" do
149
+ expect(subject.routes.count).to eq 1
150
+ end
151
+
152
+ it "adds the controller constant" do
153
+ expect(subject.routes.first[:controller]).to eq FakeDescendantController
154
+ end
155
+
156
+ it "reports the controller name" do
157
+ expect(subject.routes.first[:controller_name]).to eq "fake_descendant"
158
+ end
159
+
160
+ it "transforms the HTTP method regexp into a list of verbs" do
161
+ expect(subject.routes.first[:http_methods]).to eq %w(GET POST)
162
+ end
163
+
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/presenter_collection'
3
+
4
+ module Brainstem
5
+ module ApiDocs
6
+ describe PresenterCollection do
7
+ let(:presenter) { Object.new }
8
+ let(:target_class) { Class.new }
9
+ let(:atlas) { Object.new }
10
+ let(:options) { {} }
11
+
12
+ subject { described_class.new(atlas, options) }
13
+
14
+ describe "#find_by_target_class" do
15
+ before do
16
+ stub(presenter) do |p|
17
+ p.target_class { target_class }
18
+ end
19
+
20
+ subject << presenter
21
+ end
22
+
23
+ context "when matches presenter" do
24
+ it "returns the matching presenter" do
25
+ expect(subject.find_by_target_class(target_class)).to eq presenter
26
+ end
27
+ end
28
+
29
+ context "when does not match presenter" do
30
+ it "returns nil" do
31
+ expect(subject.find_by_target_class(Class.new)).to eq nil
32
+ end
33
+ end
34
+ end
35
+
36
+
37
+ describe "#create_from_target_class" do
38
+ let(:pclm) { Object.new }
39
+ let(:options) { { presenter_constant_lookup_method: pclm } }
40
+
41
+ context "when can find constant" do
42
+ before do
43
+ stub(target_class).to_s { "TargetClass" }
44
+ stub(pclm).call("TargetClass") { Object }
45
+ end
46
+
47
+ it "creates a new presenter, adding it to the members" do
48
+ presenter = subject.create_from_target_class(target_class)
49
+ expect(subject.first).to eq presenter
50
+ expect(presenter.const).to eq Object
51
+ expect(presenter.target_class).to eq target_class
52
+ end
53
+
54
+ end
55
+
56
+ context "when cannot find constant" do
57
+ before do
58
+ stub(target_class).to_s { "TargetClass" }
59
+ stub(pclm).call("TargetClass") { raise KeyError }
60
+ end
61
+
62
+ it "returns nil and does not append to the members" do
63
+ presenter = subject.create_from_target_class(target_class)
64
+ expect(presenter).to be_nil
65
+ end
66
+ end
67
+ end
68
+
69
+
70
+ describe "#create_from_presenter_collection" do
71
+ let(:const) { Class.new }
72
+
73
+ it "creates a new presenter, adding it to the members" do
74
+ presenter = subject.create_from_presenter_collection(target_class, const)
75
+ expect(subject.first).to eq presenter
76
+ expect(presenter.const).to eq const
77
+ expect(presenter.target_class).to eq target_class
78
+ end
79
+ end
80
+
81
+ it_behaves_like "atlas taker"
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,519 @@
1
+ require 'spec_helper'
2
+ require 'set'
3
+ require 'ostruct'
4
+ require 'brainstem/api_docs/presenter'
5
+
6
+ module Brainstem
7
+ module ApiDocs
8
+ describe Presenter do
9
+ subject { described_class.new(atlas, options) }
10
+
11
+ let(:atlas) { Object.new }
12
+ let(:target_class) { Class.new }
13
+ let(:options) { { } }
14
+ let(:nodoc) { false }
15
+
16
+ describe "#initialize" do
17
+ it "yields self if given a block" do
18
+ block = Proc.new { |s| s.target_class = target_class}
19
+ expect(described_class.new(atlas, &block).target_class).to eq target_class
20
+ end
21
+ end
22
+
23
+
24
+ describe "configured fields" do
25
+ let(:lorem) { "lorem ipsum dolor sit amet" }
26
+ let(:const) { Object.new }
27
+ let(:config) { {} }
28
+ let(:options) { { const: const } }
29
+
30
+ subject { described_class.new(atlas, options) }
31
+
32
+ before do
33
+ stub(const) do |constant|
34
+ constant.configuration { config }
35
+ constant.to_s { "Namespaced::ClassName" }
36
+ constant.possible_brainstem_keys { Set.new(%w(lorem ipsum)) }
37
+ end
38
+ end
39
+
40
+
41
+ describe "#nodoc?" do
42
+ let(:config) { { nodoc: nodoc } }
43
+
44
+ context "when nodoc in default" do
45
+ let(:nodoc) { true }
46
+
47
+ it "is true" do
48
+ expect(subject.nodoc?).to eq true
49
+ end
50
+ end
51
+
52
+ context "when not nodoc in default" do
53
+ it "is false" do
54
+ expect(subject.nodoc?).to eq false
55
+ end
56
+ end
57
+ end
58
+
59
+
60
+ describe "#title" do
61
+ let(:config) { { title: { info: lorem, nodoc: nodoc } } }
62
+
63
+ context "when nodoc" do
64
+ let(:nodoc) { true }
65
+
66
+ it "uses the last portion of the presenter's class" do
67
+ expect(subject.title).to eq "ClassName"
68
+ end
69
+ end
70
+
71
+ context "when not nodoc" do
72
+ it "uses the presenter's title" do
73
+ expect(subject.title).to eq lorem
74
+ end
75
+ end
76
+ end
77
+
78
+
79
+ describe "#brainstem_keys" do
80
+ it "retrieves from the constant, array-izes, and sorts" do
81
+ expect(subject.brainstem_keys).to eq [ "ipsum", "lorem" ]
82
+ end
83
+ end
84
+
85
+
86
+ describe "#description" do
87
+ context "with description" do
88
+ let(:config) { { description: { info: lorem, nodoc: nodoc } } }
89
+
90
+ context "when nodoc" do
91
+ let(:nodoc) { true }
92
+
93
+ it "returns empty" do
94
+ expect(subject.description).to eq ""
95
+ end
96
+ end
97
+
98
+ context "when not nodoc" do
99
+ it "returns the description" do
100
+ expect(subject.description).to eq lorem
101
+ end
102
+ end
103
+ end
104
+
105
+ context "without description" do
106
+ it "returns empty" do
107
+ expect(subject.description).to eq ""
108
+ end
109
+ end
110
+ end
111
+
112
+
113
+ describe "#valid_fields" do
114
+ let(:field) { Object.new }
115
+ before { stub(field).options { { nodoc: nodoc } } }
116
+
117
+ describe "leafs" do
118
+ let(:config) { { fields: { a_field: field } } }
119
+
120
+ context "when nodoc" do
121
+ let(:nodoc) { true }
122
+
123
+ it "rejects the field" do
124
+ expect(subject.valid_fields.count).to eq 0
125
+ end
126
+ end
127
+
128
+ context "when not nodoc" do
129
+ it "keeps the field" do
130
+ expect(subject.valid_fields.count).to eq 1
131
+ end
132
+ end
133
+ end
134
+
135
+ describe "branches" do
136
+ describe "single nesting" do
137
+ let(:config) { { fields: { nesting_one: { a_field: field } } } }
138
+
139
+ context "when all nodoc" do
140
+ let(:nodoc) { true }
141
+
142
+ it "rejects the nested field" do
143
+ expect(subject.valid_fields.count).to eq 0
144
+ end
145
+ end
146
+
147
+ context "when not all nodoc" do
148
+ it "keeps the nested field" do
149
+ expect(subject.valid_fields.count).to eq 1
150
+ end
151
+ end
152
+
153
+ end
154
+
155
+ describe "double nesting" do
156
+ let(:config) { { fields: { nesting_one: { nesting_two: { a_field: field } } } } }
157
+
158
+ context "when all nodoc" do
159
+ let(:nodoc) { true }
160
+
161
+ it "rejects the nested field" do
162
+ expect(subject.valid_fields.count).to eq 0
163
+ end
164
+ end
165
+
166
+ context "when not all nodoc" do
167
+ it "keeps the nested field" do
168
+ expect(subject.valid_fields.count).to eq 1
169
+ end
170
+ end
171
+
172
+ end
173
+ end
174
+ end
175
+
176
+
177
+ describe "#valid_filters" do
178
+ let(:info) { lorem }
179
+ let(:filter) { { info: info } }
180
+ let(:config) { { filters: { an_example: filter } } }
181
+
182
+ context "when valid" do
183
+ before do
184
+ stub(subject).documentable_filter?(:an_example, filter) { true }
185
+ end
186
+
187
+ it "retrieves from configuration" do
188
+ expect(subject.valid_filters).to eq({ an_example: filter })
189
+ end
190
+ end
191
+
192
+ context "when invalid" do
193
+ before do
194
+ stub(subject).documentable_filter?(:an_example, filter) { false }
195
+ end
196
+
197
+ it "returns an empty hash" do
198
+ expect(subject.valid_filters).to be_empty
199
+ end
200
+ end
201
+ end
202
+
203
+
204
+ describe "#documentable_filter?" do
205
+ let(:info) { lorem }
206
+ let(:filter) { { nodoc: nodoc, info: info } }
207
+
208
+ context "when nodoc" do
209
+ let(:nodoc) { true }
210
+
211
+ it "is false" do
212
+ expect(subject.documentable_filter?(:filter, filter)).to eq false
213
+ end
214
+ end
215
+
216
+ context "when doc" do
217
+ context "when description present" do
218
+ it "is true" do
219
+ expect(subject.documentable_filter?(:filter, filter)).to eq true
220
+ end
221
+ end
222
+
223
+ context "when description absent" do
224
+ let(:info) { nil }
225
+
226
+ context "when documenting empty filters" do
227
+ let(:options) { { const: const, document_empty_filters: true } }
228
+
229
+ it "is true" do
230
+ expect(subject.documentable_filter?(:filter, filter)).to eq true
231
+ end
232
+ end
233
+
234
+ context "when not documenting empty filters" do
235
+ let(:options) { { const: const, document_empty_filters: false } }
236
+
237
+ it "is false" do
238
+ expect(subject.documentable_filter?(:filter, filter)).to eq false
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
244
+
245
+
246
+ describe "#valid_sort_orders" do
247
+ let(:config) { { sort_orders: { title: { nodoc: true }, date: {} } } }
248
+
249
+ it "returns all pairs not marked nodoc" do
250
+ expect(subject.valid_sort_orders).to have_key(:date)
251
+ expect(subject.valid_sort_orders).not_to have_key(:title)
252
+ end
253
+ end
254
+
255
+
256
+ describe "#valid_associations" do
257
+ let(:info) { lorem }
258
+ let(:association) { Object.new }
259
+ let(:config) { { associations: { an_example: association } } }
260
+
261
+ context "when valid" do
262
+ before do
263
+ stub(subject).documentable_association?(:an_example, association) { true }
264
+ end
265
+
266
+ it "retrieves from configuration" do
267
+ expect(subject.valid_associations).to eq({ an_example: association })
268
+ end
269
+ end
270
+
271
+ context "when invalid" do
272
+ before do
273
+ stub(subject).documentable_association?(:an_example, association) { false }
274
+ end
275
+
276
+ it "returns an empty hash" do
277
+ expect(subject.valid_associations).to be_empty
278
+ end
279
+ end
280
+ end
281
+
282
+
283
+ describe "#documentable_association?" do
284
+ let(:desc) { lorem }
285
+ let(:association) { OpenStruct.new(options: { nodoc: nodoc }, description: desc ) }
286
+
287
+ context "when nodoc" do
288
+ let(:nodoc) { true }
289
+
290
+ it "is false" do
291
+ expect(subject.documentable_association?(:assoc, association)).to eq false
292
+ end
293
+ end
294
+
295
+ context "when doc" do
296
+ context "when description present" do
297
+ it "is true" do
298
+ expect(subject.documentable_association?(:assoc, association)).to eq true
299
+ end
300
+ end
301
+
302
+ context "when description absent" do
303
+ let(:desc) { nil }
304
+
305
+ context "when documenting empty filters" do
306
+ let(:options) { { const: const, document_empty_associations: true } }
307
+
308
+ it "is true" do
309
+ expect(subject.documentable_association?(:assoc, association)).to eq true
310
+ end
311
+ end
312
+
313
+ context "when not documenting empty filters" do
314
+ let(:options) { { const: const, document_empty_associations: false } }
315
+
316
+ it "is false" do
317
+ expect(subject.documentable_association?(:assoc, association)).to eq false
318
+ end
319
+ end
320
+ end
321
+ end
322
+ end
323
+
324
+
325
+ describe "#conditionals" do
326
+ let(:config) { { conditionals: { thing: :other_thing } } }
327
+
328
+ it "retrieves from configuration" do
329
+ expect(subject.conditionals).to eq({ thing: :other_thing })
330
+ end
331
+ end
332
+
333
+ describe "#default_sort_order" do
334
+ let(:config) { { default_sort_order: "alphabetical:asc" } }
335
+
336
+ it "retrieves from configuration" do
337
+ expect(subject.default_sort_order).to eq "alphabetical:asc"
338
+ end
339
+ end
340
+
341
+
342
+ describe "#default_sort_field" do
343
+ context "when has default sort order" do
344
+ let(:config) { { default_sort_order: "alphabetical:asc" } }
345
+
346
+ it "returns the first component" do
347
+ expect(subject.default_sort_field).to eq "alphabetical"
348
+ end
349
+ end
350
+
351
+ context "when no default sort order" do
352
+ it "returns nil" do
353
+ expect(subject.default_sort_field).to be_nil
354
+ end
355
+ end
356
+ end
357
+
358
+
359
+ describe "#default_sort_direction" do
360
+ context "when has default sort order" do
361
+ let(:config) { { default_sort_order: "alphabetical:asc" } }
362
+
363
+ it "returns the last component" do
364
+ expect(subject.default_sort_direction).to eq "asc"
365
+ end
366
+ end
367
+
368
+ context "when no default sort order" do
369
+ it "returns nil" do
370
+ expect(subject.default_sort_direction).to be_nil
371
+ end
372
+ end
373
+ end
374
+
375
+ describe "#contextual_documentation" do
376
+ let(:config) { { title: { info: info, nodoc: nodoc } } }
377
+ let(:info) { lorem }
378
+
379
+ context "when has the key" do
380
+ let(:key) { :title }
381
+
382
+ context "when not nodoc" do
383
+ context "when has info" do
384
+ it "is truthy" do
385
+ expect(subject.contextual_documentation(key)).to be_truthy
386
+ end
387
+
388
+ it "is the info" do
389
+ expect(subject.contextual_documentation(key)).to eq lorem
390
+ end
391
+ end
392
+
393
+ context "when has no info" do
394
+ let(:info) { nil }
395
+
396
+ it "is falsey" do
397
+ expect(subject.contextual_documentation(key)).to be_falsey
398
+ end
399
+ end
400
+ end
401
+
402
+ context "when nodoc" do
403
+ let(:nodoc) { true }
404
+
405
+ it "is falsey" do
406
+ expect(subject.contextual_documentation(key)).to be_falsey
407
+ end
408
+ end
409
+ end
410
+
411
+ context "when doesn't have the key" do
412
+ let(:key) { :herp }
413
+
414
+ it "is falsey" do
415
+ expect(subject.contextual_documentation(key)).to be_falsey
416
+ end
417
+ end
418
+ end
419
+ end
420
+
421
+
422
+ describe "#suggested_filename" do
423
+ before do
424
+ stub(target_class).to_s { "Abc" }
425
+ end
426
+
427
+ it "gsubs name and extension" do
428
+
429
+ instance = described_class.new(atlas,
430
+ filename_pattern: "presenters/{{name}}.{{extension}}",
431
+ target_class: target_class
432
+ )
433
+
434
+ stub(instance).extension { "xyz" }
435
+
436
+ expect(instance.suggested_filename(:xyz)).to eq "presenters/abc.xyz"
437
+ end
438
+ end
439
+
440
+
441
+ describe "#suggested_filename_link" do
442
+ before do
443
+ stub(target_class).to_s { "Abc" }
444
+ end
445
+
446
+ it "gsubs name and extension" do
447
+
448
+ instance = described_class.new(atlas,
449
+ filename_link_pattern: "presenters/{{name}}.{{extension}}.foo",
450
+ target_class: target_class
451
+ )
452
+
453
+ stub(instance).extension { "xyz" }
454
+
455
+ expect(instance.suggested_filename_link(:xyz)).to eq "presenters/abc.xyz.foo"
456
+ end
457
+ end
458
+
459
+
460
+ describe "#relative_path_to_presenter" do
461
+ let(:presenter) {
462
+ mock!
463
+ .suggested_filename_link(:markdown)
464
+ .returns("objects/sprocket_widget")
465
+ .subject
466
+ }
467
+
468
+ it "returns a relative path" do
469
+ expect(subject.relative_path_to_presenter(presenter, :markdown)).to \
470
+ eq "sprocket_widget"
471
+ end
472
+ end
473
+
474
+
475
+ describe "#link_for_association" do
476
+ let(:presenter) { Object.new }
477
+ let(:target_class) { Class.new }
478
+ let(:association) { OpenStruct.new(target_class: target_class) }
479
+
480
+ context "when can find presenter" do
481
+ before do
482
+ mock(subject).find_by_class(target_class) { presenter }
483
+ stub(subject).relative_path_to_presenter(presenter, :markdown) { "./path" }
484
+ stub(presenter).nodoc? { nodoc }
485
+ end
486
+
487
+ context "when nodoc" do
488
+ let(:nodoc) { true }
489
+
490
+ it "is nil" do
491
+ expect(subject.link_for_association(association)).to be_nil
492
+ end
493
+ end
494
+
495
+ context "when not nodoc" do
496
+ it "is the path" do
497
+ expect(subject.link_for_association(association)).to eq "./path"
498
+ end
499
+ end
500
+
501
+ end
502
+
503
+ context "when cannot find presenter" do
504
+ before do
505
+ mock(subject).find_by_class(target_class) { nil }
506
+ end
507
+
508
+ it "is nil" do
509
+ expect(subject.link_for_association(association)).to be_nil
510
+ end
511
+ end
512
+ end
513
+
514
+
515
+ it_behaves_like "formattable"
516
+ it_behaves_like "atlas taker"
517
+ end
518
+ end
519
+ end