rubycut-sinatra-contrib 1.4.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 (81) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +136 -0
  3. data/Rakefile +75 -0
  4. data/ideas.md +29 -0
  5. data/lib/sinatra/capture.rb +124 -0
  6. data/lib/sinatra/config_file.rb +167 -0
  7. data/lib/sinatra/content_for.rb +125 -0
  8. data/lib/sinatra/contrib.rb +39 -0
  9. data/lib/sinatra/contrib/all.rb +2 -0
  10. data/lib/sinatra/contrib/setup.rb +53 -0
  11. data/lib/sinatra/contrib/version.rb +17 -0
  12. data/lib/sinatra/cookies.rb +331 -0
  13. data/lib/sinatra/decompile.rb +120 -0
  14. data/lib/sinatra/engine_tracking.rb +96 -0
  15. data/lib/sinatra/extension.rb +95 -0
  16. data/lib/sinatra/json.rb +130 -0
  17. data/lib/sinatra/link_header.rb +132 -0
  18. data/lib/sinatra/multi_route.rb +87 -0
  19. data/lib/sinatra/namespace.rb +284 -0
  20. data/lib/sinatra/reloader.rb +394 -0
  21. data/lib/sinatra/respond_with.rb +249 -0
  22. data/lib/sinatra/streaming.rb +267 -0
  23. data/lib/sinatra/test_helpers.rb +87 -0
  24. data/sinatra-contrib.gemspec +127 -0
  25. data/spec/capture_spec.rb +93 -0
  26. data/spec/config_file/key_value.yml +6 -0
  27. data/spec/config_file/key_value.yml.erb +6 -0
  28. data/spec/config_file/key_value_override.yml +2 -0
  29. data/spec/config_file/missing_env.yml +4 -0
  30. data/spec/config_file/with_envs.yml +7 -0
  31. data/spec/config_file/with_nested_envs.yml +11 -0
  32. data/spec/config_file_spec.rb +63 -0
  33. data/spec/content_for/different_key.erb +1 -0
  34. data/spec/content_for/different_key.erubis +1 -0
  35. data/spec/content_for/different_key.haml +2 -0
  36. data/spec/content_for/different_key.slim +2 -0
  37. data/spec/content_for/layout.erb +1 -0
  38. data/spec/content_for/layout.erubis +1 -0
  39. data/spec/content_for/layout.haml +1 -0
  40. data/spec/content_for/layout.slim +1 -0
  41. data/spec/content_for/multiple_blocks.erb +4 -0
  42. data/spec/content_for/multiple_blocks.erubis +4 -0
  43. data/spec/content_for/multiple_blocks.haml +8 -0
  44. data/spec/content_for/multiple_blocks.slim +8 -0
  45. data/spec/content_for/multiple_yields.erb +3 -0
  46. data/spec/content_for/multiple_yields.erubis +3 -0
  47. data/spec/content_for/multiple_yields.haml +3 -0
  48. data/spec/content_for/multiple_yields.slim +3 -0
  49. data/spec/content_for/passes_values.erb +1 -0
  50. data/spec/content_for/passes_values.erubis +1 -0
  51. data/spec/content_for/passes_values.haml +1 -0
  52. data/spec/content_for/passes_values.slim +1 -0
  53. data/spec/content_for/same_key.erb +1 -0
  54. data/spec/content_for/same_key.erubis +1 -0
  55. data/spec/content_for/same_key.haml +2 -0
  56. data/spec/content_for/same_key.slim +2 -0
  57. data/spec/content_for/takes_values.erb +1 -0
  58. data/spec/content_for/takes_values.erubis +1 -0
  59. data/spec/content_for/takes_values.haml +3 -0
  60. data/spec/content_for/takes_values.slim +3 -0
  61. data/spec/content_for_spec.rb +213 -0
  62. data/spec/cookies_spec.rb +802 -0
  63. data/spec/decompile_spec.rb +44 -0
  64. data/spec/extension_spec.rb +33 -0
  65. data/spec/json_spec.rb +117 -0
  66. data/spec/link_header_spec.rb +100 -0
  67. data/spec/multi_route_spec.rb +60 -0
  68. data/spec/namespace/foo.erb +1 -0
  69. data/spec/namespace/nested/foo.erb +1 -0
  70. data/spec/namespace_spec.rb +676 -0
  71. data/spec/okjson.rb +581 -0
  72. data/spec/reloader/app.rb.erb +40 -0
  73. data/spec/reloader_spec.rb +441 -0
  74. data/spec/respond_with/bar.erb +1 -0
  75. data/spec/respond_with/bar.json.erb +1 -0
  76. data/spec/respond_with/foo.html.erb +1 -0
  77. data/spec/respond_with/not_html.sass +2 -0
  78. data/spec/respond_with_spec.rb +297 -0
  79. data/spec/spec_helper.rb +7 -0
  80. data/spec/streaming_spec.rb +436 -0
  81. metadata +313 -0
@@ -0,0 +1 @@
1
+ Girl! I wanna take you to a ... bar!
@@ -0,0 +1 @@
1
+ json!
@@ -0,0 +1 @@
1
+ Hello <%= name %>!
@@ -0,0 +1,2 @@
1
+ body
2
+ color: red
@@ -0,0 +1,297 @@
1
+ require 'backports'
2
+ require 'multi_json'
3
+
4
+ require_relative 'spec_helper'
5
+ require_relative 'okjson'
6
+
7
+ describe Sinatra::RespondWith do
8
+ def provides(*args)
9
+ @provides = args
10
+ end
11
+
12
+ def respond_app(&block)
13
+ types = @provides
14
+ mock_app do
15
+ set :app_file, __FILE__
16
+ set :views, root + '/respond_with'
17
+ register Sinatra::RespondWith
18
+ respond_to(*types) if types
19
+ class_eval(&block)
20
+ end
21
+ end
22
+
23
+ def respond_to(*args, &block)
24
+ respond_app { get('/') { respond_to(*args, &block) } }
25
+ end
26
+
27
+ def respond_with(*args, &block)
28
+ respond_app { get('/') { respond_with(*args, &block) } }
29
+ end
30
+
31
+ def req(*types)
32
+ p = types.shift if types.first.is_a? String and types.first.start_with? '/'
33
+ accept = types.map { |t| Sinatra::Base.mime_type(t).to_s }.join ','
34
+ get (p || '/'), {}, 'HTTP_ACCEPT' => accept
35
+ end
36
+
37
+ describe "Helpers#respond_to" do
38
+ it 'allows defining handlers by file extensions' do
39
+ respond_to do |format|
40
+ format.html { "html!" }
41
+ format.json { "json!" }
42
+ end
43
+
44
+ req(:html).body.should == "html!"
45
+ req(:json).body.should == "json!"
46
+ end
47
+
48
+ it 'respects quality' do
49
+ respond_to do |format|
50
+ format.html { "html!" }
51
+ format.json { "json!" }
52
+ end
53
+
54
+ req("text/html;q=0.7, application/json;q=0.3").body.should == "html!"
55
+ req("text/html;q=0.3, application/json;q=0.7").body.should == "json!"
56
+ end
57
+
58
+ it 'allows using mime types' do
59
+ respond_to do |format|
60
+ format.on('text/html') { "html!" }
61
+ format.json { "json!" }
62
+ end
63
+
64
+ req(:html).body.should == "html!"
65
+ end
66
+
67
+ it 'allows using wildcards in format matchers' do
68
+ respond_to do |format|
69
+ format.on('text/*') { "text!" }
70
+ format.json { "json!" }
71
+ end
72
+
73
+ req(:html).body.should == "text!"
74
+ end
75
+
76
+ it 'allows using catch all wildcards in format matchers' do
77
+ respond_to do |format|
78
+ format.on('*/*') { "anything!" }
79
+ format.json { "json!" }
80
+ end
81
+
82
+ req(:html).body.should == "anything!"
83
+ end
84
+
85
+ it 'prefers concret over generic' do
86
+ respond_to do |format|
87
+ format.on('text/*') { "text!" }
88
+ format.on('*/*') { "anything!" }
89
+ format.json { "json!" }
90
+ end
91
+
92
+ req(:json).body.should == "json!"
93
+ req(:html).body.should == "text!"
94
+ end
95
+
96
+ it 'does not set up default handlers' do
97
+ respond_to
98
+ req.should_not be_ok
99
+ status.should == 406
100
+ end
101
+ end
102
+
103
+ describe "Helpers#respond_with" do
104
+ describe "matching" do
105
+ it 'allows defining handlers by file extensions' do
106
+ respond_with(:ignore) do |format|
107
+ format.html { "html!" }
108
+ format.json { "json!" }
109
+ end
110
+
111
+ req(:html).body.should == "html!"
112
+ req(:json).body.should == "json!"
113
+ end
114
+
115
+ it 'respects quality' do
116
+ respond_with(:ignore) do |format|
117
+ format.html { "html!" }
118
+ format.json { "json!" }
119
+ end
120
+
121
+ req("text/html;q=0.7, application/json;q=0.3").body.should == "html!"
122
+ req("text/html;q=0.3, application/json;q=0.7").body.should == "json!"
123
+ end
124
+
125
+ it 'allows using mime types' do
126
+ respond_with(:ignore) do |format|
127
+ format.on('text/html') { "html!" }
128
+ format.json { "json!" }
129
+ end
130
+
131
+ req(:html).body.should == "html!"
132
+ end
133
+
134
+ it 'allows using wildcards in format matchers' do
135
+ respond_with(:ignore) do |format|
136
+ format.on('text/*') { "text!" }
137
+ format.json { "json!" }
138
+ end
139
+
140
+ req(:html).body.should == "text!"
141
+ end
142
+
143
+ it 'allows using catch all wildcards in format matchers' do
144
+ respond_with(:ignore) do |format|
145
+ format.on('*/*') { "anything!" }
146
+ format.json { "json!" }
147
+ end
148
+
149
+ req(:html).body.should == "anything!"
150
+ end
151
+
152
+ it 'prefers concret over generic' do
153
+ respond_with(:ignore) do |format|
154
+ format.on('text/*') { "text!" }
155
+ format.on('*/*') { "anything!" }
156
+ format.json { "json!" }
157
+ end
158
+
159
+ req(:json).body.should == "json!"
160
+ req(:html).body.should == "text!"
161
+ end
162
+ end
163
+
164
+ describe "default behavior" do
165
+ it 'converts objects to json out of the box' do
166
+ respond_with 'a' => 'b'
167
+ OkJson.decode(req(:json).body).should == {'a' => 'b'}
168
+ end
169
+
170
+ it 'handles multiple routes correctly' do
171
+ respond_app do
172
+ get('/') { respond_with 'a' => 'b' }
173
+ get('/:name') { respond_with 'a' => params[:name] }
174
+ end
175
+ OkJson.decode(req('/', :json).body).should == {'a' => 'b'}
176
+ OkJson.decode(req('/b', :json).body).should == {'a' => 'b'}
177
+ OkJson.decode(req('/c', :json).body).should == {'a' => 'c'}
178
+ end
179
+
180
+ it "calls to_EXT if available" do
181
+ respond_with Struct.new(:to_pdf).new("hello")
182
+ req(:pdf).body.should == "hello"
183
+ end
184
+
185
+ it 'results in a 406 if format cannot be produced' do
186
+ respond_with({})
187
+ req(:html).should_not be_ok
188
+ status.should == 406
189
+ end
190
+ end
191
+
192
+ describe 'templates' do
193
+ it 'looks for templates with name.target.engine' do
194
+ respond_with :foo, :name => 'World'
195
+ req(:html).should be_ok
196
+ body.should == "Hello World!"
197
+ end
198
+
199
+ it 'looks for templates with name.engine for specific engines' do
200
+ respond_with :bar
201
+ req(:html).should be_ok
202
+ body.should == "Girl! I wanna take you to a ... bar!"
203
+ end
204
+
205
+ it 'does not use name.engine for engines producing other formats' do
206
+ respond_with :not_html
207
+ req(:html).should_not be_ok
208
+ status.should == 406
209
+ body.should be_empty
210
+ end
211
+
212
+ it 'falls back to #json if no template is found' do
213
+ respond_with :foo, :name => 'World'
214
+ req(:json).should be_ok
215
+ OkJson.decode(body).should == {'name' => 'World'}
216
+ end
217
+
218
+ it 'favors templates over #json' do
219
+ respond_with :bar, :name => 'World'
220
+ req(:json).should be_ok
221
+ body.should == 'json!'
222
+ end
223
+
224
+ it 'falls back to to_EXT if no template is found' do
225
+ object = {:name => 'World'}
226
+ def object.to_pdf; "hi" end
227
+ respond_with :foo, object
228
+ req(:pdf).should be_ok
229
+ body.should == "hi"
230
+ end
231
+
232
+ it 'uses yajl for json' do
233
+ respond_with :baz
234
+ req(:json).should be_ok
235
+ body.should == "\"yajl!\""
236
+ end
237
+ end
238
+
239
+ describe 'customizing' do
240
+ it 'allows customizing' do
241
+ respond_with(:foo, :name => 'World') { |f| f.html { 'html!' }}
242
+ req(:html).should be_ok
243
+ body.should == "html!"
244
+ end
245
+
246
+ it 'falls back to default behavior if none matches' do
247
+ respond_with(:foo, :name => 'World') { |f| f.json { 'json!' }}
248
+ req(:html).should be_ok
249
+ body.should == "Hello World!"
250
+ end
251
+
252
+ it 'favors generic rule over default behavior' do
253
+ respond_with(:foo, :name => 'World') { |f| f.on('*/*') { 'generic!' }}
254
+ req(:html).should be_ok
255
+ body.should == "generic!"
256
+ end
257
+ end
258
+ end
259
+
260
+ describe :respond_to do
261
+ it 'acts as global provides condition' do
262
+ respond_app do
263
+ respond_to :json, :html
264
+ get('/a') { 'ok' }
265
+ get('/b') { 'ok' }
266
+ end
267
+
268
+ req('/b', :xml).should_not be_ok
269
+ req('/b', :html).should be_ok
270
+ end
271
+
272
+ it 'still allows provides' do
273
+ respond_app do
274
+ respond_to :json, :html
275
+ get('/a') { 'ok' }
276
+ get('/b', :provides => :json) { 'ok' }
277
+ end
278
+
279
+ req('/b', :html).should_not be_ok
280
+ req('/b', :json).should be_ok
281
+ end
282
+
283
+ it 'plays well with namespaces' do
284
+ respond_app do
285
+ register Sinatra::Namespace
286
+ namespace '/a' do
287
+ respond_to :json
288
+ get { 'json' }
289
+ end
290
+ get('/b') { 'anything' }
291
+ end
292
+
293
+ req('/a', :html).should_not be_ok
294
+ req('/b', :html).should be_ok
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,7 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+ require 'sinatra/contrib'
3
+
4
+ RSpec.configure do |config|
5
+ config.expect_with :rspec, :stdlib
6
+ config.include Sinatra::TestHelpers
7
+ end
@@ -0,0 +1,436 @@
1
+ require 'backports'
2
+ require_relative 'spec_helper'
3
+
4
+ describe Sinatra::Streaming do
5
+ def stream(&block)
6
+ rack_middleware = @use
7
+ out = nil
8
+ mock_app do
9
+ rack_middleware.each { |args| use(*args) }
10
+ helpers Sinatra::Streaming
11
+ get('/') { out = stream(&block) }
12
+ end
13
+ get('/')
14
+ out
15
+ end
16
+
17
+ def use(*args)
18
+ @use << args
19
+ end
20
+
21
+ before do
22
+ @use = []
23
+ end
24
+
25
+ context 'stream test helper' do
26
+ it 'runs the given block' do
27
+ ran = false
28
+ stream { ran = true }
29
+ ran.should be_true
30
+ end
31
+
32
+ it 'returns the stream object' do
33
+ out = stream { }
34
+ out.should be_a(Sinatra::Helpers::Stream)
35
+ end
36
+
37
+ it 'fires a request against that stream' do
38
+ stream { |out| out << "Hello World!" }
39
+ last_response.should be_ok
40
+ body.should be == "Hello World!"
41
+ end
42
+
43
+ it 'passes the stream object to the block' do
44
+ passed = nil
45
+ returned = stream { |out| passed = out }
46
+ passed.should be == returned
47
+ end
48
+ end
49
+
50
+ context Sinatra::Streaming::Stream do
51
+ it 'should extend the stream object' do
52
+ out = stream { }
53
+ out.should be_a(Sinatra::Streaming::Stream)
54
+ end
55
+
56
+ it 'should not extend stream objects of other apps' do
57
+ out = nil
58
+ mock_app { get('/') { out = stream { }}}
59
+ get('/')
60
+ out.should be_a(Sinatra::Helpers::Stream)
61
+ out.should_not be_a(Sinatra::Streaming::Stream)
62
+ end
63
+ end
64
+
65
+ context EventMachine::Deferrable do
66
+ it 'allows attaching more than one callback' do
67
+ a = b = false
68
+ stream do |out|
69
+ out.callback { a = true }
70
+ out.callback { b = true }
71
+ end
72
+ a.should be_true
73
+ b.should be_true
74
+ end
75
+
76
+ it 'triggers callbacks after streaming' do
77
+ triggered = false
78
+ stream do |out|
79
+ out.callback { triggered = true }
80
+ triggered.should be_false
81
+ end
82
+ triggered.should be_true
83
+ end
84
+ end
85
+
86
+ context 'app' do
87
+ it 'is the app instance the stream was created from' do
88
+ out = stream { }
89
+ out.app.should be_a(Sinatra::Base)
90
+ end
91
+ end
92
+
93
+ context 'lineno' do
94
+ it 'defaults to 0' do
95
+ stream { }.lineno.should be == 0
96
+ end
97
+
98
+ it 'does not increase on write' do
99
+ stream do |out|
100
+ out << "many\nlines\n"
101
+ out.lineno.should be == 0
102
+ end
103
+ end
104
+
105
+ it 'is writable' do
106
+ out = stream { }
107
+ out.lineno = 10
108
+ out.lineno.should be == 10
109
+ end
110
+ end
111
+
112
+ context 'pos' do
113
+ it 'defaults to 0' do
114
+ stream { }.pos.should be == 0
115
+ end
116
+
117
+ it 'increases when writing data' do
118
+ stream do |out|
119
+ out.pos.should be == 0
120
+ out << 'hi'
121
+ out.pos.should be == 2
122
+ end
123
+ end
124
+
125
+ it 'is writable' do
126
+ out = stream { }
127
+ out.pos = 10
128
+ out.pos.should be == 10
129
+ end
130
+
131
+ it 'aliased to #tell' do
132
+ out = stream { }
133
+ out.tell.should be == 0
134
+ out.pos = 10
135
+ out.tell.should be == 10
136
+ end
137
+ end
138
+
139
+ context 'closed' do
140
+ it 'returns false while streaming' do
141
+ stream { |out| out.should_not be_closed }
142
+ end
143
+
144
+ it 'returns true after streaming' do
145
+ stream {}.should be_closed
146
+ end
147
+ end
148
+
149
+ context 'map!' do
150
+ it 'applies transformations later' do
151
+ stream do |out|
152
+ out.map! { |s| s.upcase }
153
+ out << 'ok'
154
+ end
155
+ body.should be == "OK"
156
+ end
157
+
158
+ it 'is chainable' do
159
+ stream do |out|
160
+ out.map! { |s| s.upcase }
161
+ out.map! { |s| s.reverse }
162
+ out << 'ok'
163
+ end
164
+ body.should be == "KO"
165
+ end
166
+
167
+ it 'works with middleware' do
168
+ middleware = Class.new do
169
+ def initialize(app) @app = app end
170
+ def call(env)
171
+ status, headers, body = @app.call(env)
172
+ body.map! { |s| s.upcase }
173
+ [status, headers, body]
174
+ end
175
+ end
176
+
177
+ use middleware
178
+ stream { |out| out << "ok" }
179
+ body.should be == "OK"
180
+ end
181
+
182
+ it 'modifies each value separately' do
183
+ stream do |out|
184
+ out.map! { |s| s.reverse }
185
+ out << "ab" << "cd"
186
+ end
187
+ body.should be == "badc"
188
+ end
189
+ end
190
+
191
+ context 'map' do
192
+ it 'works with middleware' do
193
+ middleware = Class.new do
194
+ def initialize(app) @app = app end
195
+ def call(env)
196
+ status, headers, body = @app.call(env)
197
+ [status, headers, body.map(&:upcase)]
198
+ end
199
+ end
200
+
201
+ use middleware
202
+ stream { |out| out << "ok" }
203
+ body.should be == "OK"
204
+ end
205
+
206
+ it 'is chainable' do
207
+ middleware = Class.new do
208
+ def initialize(app) @app = app end
209
+ def call(env)
210
+ status, headers, body = @app.call(env)
211
+ [status, headers, body.map(&:upcase).map(&:reverse)]
212
+ end
213
+ end
214
+
215
+ use middleware
216
+ stream { |out| out << "ok" }
217
+ body.should be == "KO"
218
+ end
219
+
220
+ it 'can be written as each.map' do
221
+ middleware = Class.new do
222
+ def initialize(app) @app = app end
223
+ def call(env)
224
+ status, headers, body = @app.call(env)
225
+ [status, headers, body.each.map(&:upcase)]
226
+ end
227
+ end
228
+
229
+ use middleware
230
+ stream { |out| out << "ok" }
231
+ body.should be == "OK"
232
+ end
233
+
234
+ it 'does not modify the original body' do
235
+ stream do |out|
236
+ out.map { |s| s.reverse }
237
+ out << 'ok'
238
+ end
239
+ body.should be == 'ok'
240
+ end
241
+ end
242
+
243
+ context 'write' do
244
+ it 'writes to the stream' do
245
+ stream { |out| out.write 'hi' }
246
+ body.should be == 'hi'
247
+ end
248
+
249
+ it 'returns the number of bytes' do
250
+ stream do |out|
251
+ out.write('hi').should be == 2
252
+ out.write('hello').should be == 5
253
+ end
254
+ end
255
+
256
+ it 'accepts non-string objects' do
257
+ stream do |out|
258
+ out.write(12).should be == 2
259
+ end
260
+ end
261
+
262
+ it 'should be aliased to syswrite' do
263
+ stream { |out| out.syswrite('hi').should be == 2 }
264
+ body.should be == 'hi'
265
+ end
266
+
267
+ it 'should be aliased to write_nonblock' do
268
+ stream { |out| out.write_nonblock('hi').should be == 2 }
269
+ body.should be == 'hi'
270
+ end
271
+ end
272
+
273
+ context 'print' do
274
+ it 'writes to the stream' do
275
+ stream { |out| out.print('hi') }
276
+ body.should be == 'hi'
277
+ end
278
+
279
+ it 'accepts multiple arguments' do
280
+ stream { |out| out.print(1, 2, 3, 4) }
281
+ body.should be == '1234'
282
+ end
283
+
284
+ it 'returns nil' do
285
+ stream { |out| out.print('hi').should be_nil }
286
+ end
287
+ end
288
+
289
+ context 'printf' do
290
+ it 'writes to the stream' do
291
+ stream { |out| out.printf('hi') }
292
+ body.should be == 'hi'
293
+ end
294
+
295
+ it 'interpolates the format string' do
296
+ stream { |out| out.printf("%s: %d", "answer", 42) }
297
+ body.should be == 'answer: 42'
298
+ end
299
+
300
+ it 'returns nil' do
301
+ stream { |out| out.printf('hi').should be_nil }
302
+ end
303
+ end
304
+
305
+ context 'putc' do
306
+ it 'writes the first character of a string' do
307
+ stream { |out| out.putc('hi') }
308
+ body.should be == 'h'
309
+ end
310
+
311
+ it 'writes the character corresponding to an integer' do
312
+ stream { |out| out.putc(42) }
313
+ body.should be == '*'
314
+ end
315
+
316
+ it 'returns nil' do
317
+ stream { |out| out.putc('hi').should be_nil }
318
+ end
319
+ end
320
+
321
+ context 'puts' do
322
+ it 'writes to the stream' do
323
+ stream { |out| out.puts('hi') }
324
+ body.should be == "hi\n"
325
+ end
326
+
327
+ it 'accepts multiple arguments' do
328
+ stream { |out| out.puts(1, 2, 3, 4) }
329
+ body.should be == "1\n2\n3\n4\n"
330
+ end
331
+
332
+ it 'returns nil' do
333
+ stream { |out| out.puts('hi').should be_nil }
334
+ end
335
+ end
336
+
337
+ context 'close' do
338
+ it 'sets #closed? to true' do
339
+ stream do |out|
340
+ out.close
341
+ out.should be_closed
342
+ end
343
+ end
344
+
345
+ it 'sets #closed_write? to true' do
346
+ stream do |out|
347
+ out.should_not be_closed_write
348
+ out.close
349
+ out.should be_closed_write
350
+ end
351
+ end
352
+
353
+ it 'fires callbacks' do
354
+ stream do |out|
355
+ fired = false
356
+ out.callback { fired = true }
357
+ out.close
358
+ fired.should be_true
359
+ end
360
+ end
361
+
362
+ it 'prevents from further writing' do
363
+ stream do |out|
364
+ out.close
365
+ expect { out << 'hi' }.to raise_error(IOError, 'not opened for writing')
366
+ end
367
+ end
368
+ end
369
+
370
+ context 'close_read' do
371
+ it 'raises the appropriate exception' do
372
+ expect { stream { |out| out.close_read }}.
373
+ to raise_error(IOError, "closing non-duplex IO for reading")
374
+ end
375
+ end
376
+
377
+ context 'closed_read?' do
378
+ it('returns true') { stream { |out| out.should be_closed_read }}
379
+ end
380
+
381
+ context 'rewind' do
382
+ it 'resets pos' do
383
+ stream do |out|
384
+ out << 'hi'
385
+ out.rewind
386
+ out.pos.should be == 0
387
+ end
388
+ end
389
+
390
+ it 'resets lineno' do
391
+ stream do |out|
392
+ out.lineno = 10
393
+ out.rewind
394
+ out.lineno.should be == 0
395
+ end
396
+ end
397
+ end
398
+
399
+ raises = %w[
400
+ bytes eof? eof getbyte getc gets read read_nonblock readbyte readchar
401
+ readline readlines readpartial sysread ungetbyte ungetc
402
+ ]
403
+
404
+ enum = %w[chars each_line each_byte each_char lines]
405
+ dummies = %w[flush fsync internal_encoding pid]
406
+
407
+ raises.each do |method|
408
+ context method do
409
+ it 'raises the appropriate exception' do
410
+ expect { stream { |out| out.public_send(method) }}.
411
+ to raise_error(IOError, "not opened for reading")
412
+ end
413
+ end
414
+ end
415
+
416
+ enum.each do |method|
417
+ context method do
418
+ it 'creates an Enumerator' do
419
+ stream { |out| out.public_send(method).should be_a(Enumerator) }
420
+ end
421
+
422
+ it 'calling each raises the appropriate exception' do
423
+ expect { stream { |out| out.public_send(method).each { }}}.
424
+ to raise_error(IOError, "not opened for reading")
425
+ end
426
+ end
427
+ end
428
+
429
+ dummies.each do |method|
430
+ context method do
431
+ it 'returns nil' do
432
+ stream { |out| out.public_send(method).should be_nil }
433
+ end
434
+ end
435
+ end
436
+ end