sinatra-contrib 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +135 -0
  3. data/Rakefile +75 -0
  4. data/ideas.md +29 -0
  5. data/lib/sinatra/capture.rb +42 -0
  6. data/lib/sinatra/config_file.rb +151 -0
  7. data/lib/sinatra/content_for.rb +111 -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 +45 -0
  12. data/lib/sinatra/cookies.rb +331 -0
  13. data/lib/sinatra/decompile.rb +113 -0
  14. data/lib/sinatra/engine_tracking.rb +96 -0
  15. data/lib/sinatra/extension.rb +95 -0
  16. data/lib/sinatra/json.rb +134 -0
  17. data/lib/sinatra/link_header.rb +132 -0
  18. data/lib/sinatra/multi_route.rb +81 -0
  19. data/lib/sinatra/namespace.rb +282 -0
  20. data/lib/sinatra/reloader.rb +384 -0
  21. data/lib/sinatra/respond_with.rb +245 -0
  22. data/lib/sinatra/streaming.rb +267 -0
  23. data/lib/sinatra/test_helpers.rb +87 -0
  24. data/sinatra-contrib.gemspec +125 -0
  25. data/spec/capture_spec.rb +80 -0
  26. data/spec/config_file/key_value.yml +6 -0
  27. data/spec/config_file/missing_env.yml +4 -0
  28. data/spec/config_file/with_envs.yml +7 -0
  29. data/spec/config_file/with_nested_envs.yml +11 -0
  30. data/spec/config_file_spec.rb +44 -0
  31. data/spec/content_for/different_key.erb +1 -0
  32. data/spec/content_for/different_key.erubis +1 -0
  33. data/spec/content_for/different_key.haml +2 -0
  34. data/spec/content_for/different_key.slim +2 -0
  35. data/spec/content_for/layout.erb +1 -0
  36. data/spec/content_for/layout.erubis +1 -0
  37. data/spec/content_for/layout.haml +1 -0
  38. data/spec/content_for/layout.slim +1 -0
  39. data/spec/content_for/multiple_blocks.erb +4 -0
  40. data/spec/content_for/multiple_blocks.erubis +4 -0
  41. data/spec/content_for/multiple_blocks.haml +8 -0
  42. data/spec/content_for/multiple_blocks.slim +8 -0
  43. data/spec/content_for/multiple_yields.erb +3 -0
  44. data/spec/content_for/multiple_yields.erubis +3 -0
  45. data/spec/content_for/multiple_yields.haml +3 -0
  46. data/spec/content_for/multiple_yields.slim +3 -0
  47. data/spec/content_for/passes_values.erb +1 -0
  48. data/spec/content_for/passes_values.erubis +1 -0
  49. data/spec/content_for/passes_values.haml +1 -0
  50. data/spec/content_for/passes_values.slim +1 -0
  51. data/spec/content_for/same_key.erb +1 -0
  52. data/spec/content_for/same_key.erubis +1 -0
  53. data/spec/content_for/same_key.haml +2 -0
  54. data/spec/content_for/same_key.slim +2 -0
  55. data/spec/content_for/takes_values.erb +1 -0
  56. data/spec/content_for/takes_values.erubis +1 -0
  57. data/spec/content_for/takes_values.haml +3 -0
  58. data/spec/content_for/takes_values.slim +3 -0
  59. data/spec/content_for_spec.rb +201 -0
  60. data/spec/cookies_spec.rb +782 -0
  61. data/spec/decompile_spec.rb +44 -0
  62. data/spec/extension_spec.rb +33 -0
  63. data/spec/json_spec.rb +115 -0
  64. data/spec/link_header_spec.rb +100 -0
  65. data/spec/multi_route_spec.rb +45 -0
  66. data/spec/namespace/foo.erb +1 -0
  67. data/spec/namespace/nested/foo.erb +1 -0
  68. data/spec/namespace_spec.rb +623 -0
  69. data/spec/okjson.rb +581 -0
  70. data/spec/reloader/app.rb.erb +40 -0
  71. data/spec/reloader_spec.rb +441 -0
  72. data/spec/respond_with/bar.erb +1 -0
  73. data/spec/respond_with/bar.json.erb +1 -0
  74. data/spec/respond_with/foo.html.erb +1 -0
  75. data/spec/respond_with/not_html.sass +2 -0
  76. data/spec/respond_with_spec.rb +289 -0
  77. data/spec/spec_helper.rb +6 -0
  78. data/spec/streaming_spec.rb +436 -0
  79. metadata +256 -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,289 @@
1
+ require 'backports'
2
+ require_relative 'spec_helper'
3
+ require_relative 'okjson'
4
+
5
+ describe Sinatra::RespondWith do
6
+ def provides(*args)
7
+ @provides = args
8
+ end
9
+
10
+ def respond_app(&block)
11
+ types = @provides
12
+ mock_app do
13
+ set :app_file, __FILE__
14
+ set :views, root + '/respond_with'
15
+ register Sinatra::RespondWith
16
+ respond_to(*types) if types
17
+ class_eval(&block)
18
+ end
19
+ end
20
+
21
+ def respond_to(*args, &block)
22
+ respond_app { get('/') { respond_to(*args, &block) } }
23
+ end
24
+
25
+ def respond_with(*args, &block)
26
+ respond_app { get('/') { respond_with(*args, &block) } }
27
+ end
28
+
29
+ def req(*types)
30
+ p = types.shift if types.first.is_a? String and types.first.start_with? '/'
31
+ accept = types.map { |t| Sinatra::Base.mime_type(t).to_s }.join ','
32
+ get (p || '/'), {}, 'HTTP_ACCEPT' => accept
33
+ end
34
+
35
+ describe "Helpers#respond_to" do
36
+ it 'allows defining handlers by file extensions' do
37
+ respond_to do |format|
38
+ format.html { "html!" }
39
+ format.json { "json!" }
40
+ end
41
+
42
+ req(:html).body.should == "html!"
43
+ req(:json).body.should == "json!"
44
+ end
45
+
46
+ it 'respects quality' do
47
+ respond_to do |format|
48
+ format.html { "html!" }
49
+ format.json { "json!" }
50
+ end
51
+
52
+ req("text/html;q=0.7, application/json;q=0.3").body.should == "html!"
53
+ req("text/html;q=0.3, application/json;q=0.7").body.should == "json!"
54
+ end
55
+
56
+ it 'allows using mime types' do
57
+ respond_to do |format|
58
+ format.on('text/html') { "html!" }
59
+ format.json { "json!" }
60
+ end
61
+
62
+ req(:html).body.should == "html!"
63
+ end
64
+
65
+ it 'allows using wildcards in format matchers' do
66
+ respond_to do |format|
67
+ format.on('text/*') { "text!" }
68
+ format.json { "json!" }
69
+ end
70
+
71
+ req(:html).body.should == "text!"
72
+ end
73
+
74
+ it 'allows using catch all wildcards in format matchers' do
75
+ respond_to do |format|
76
+ format.on('*/*') { "anything!" }
77
+ format.json { "json!" }
78
+ end
79
+
80
+ req(:html).body.should == "anything!"
81
+ end
82
+
83
+ it 'prefers concret over generic' do
84
+ respond_to do |format|
85
+ format.on('text/*') { "text!" }
86
+ format.on('*/*') { "anything!" }
87
+ format.json { "json!" }
88
+ end
89
+
90
+ req(:json).body.should == "json!"
91
+ req(:html).body.should == "text!"
92
+ end
93
+
94
+ it 'does not set up default handlers' do
95
+ respond_to
96
+ req.should_not be_ok
97
+ status.should == 406
98
+ end
99
+ end
100
+
101
+ describe "Helpers#respond_with" do
102
+ describe "matching" do
103
+ it 'allows defining handlers by file extensions' do
104
+ respond_with(:ignore) do |format|
105
+ format.html { "html!" }
106
+ format.json { "json!" }
107
+ end
108
+
109
+ req(:html).body.should == "html!"
110
+ req(:json).body.should == "json!"
111
+ end
112
+
113
+ it 'respects quality' do
114
+ respond_with(:ignore) do |format|
115
+ format.html { "html!" }
116
+ format.json { "json!" }
117
+ end
118
+
119
+ req("text/html;q=0.7, application/json;q=0.3").body.should == "html!"
120
+ req("text/html;q=0.3, application/json;q=0.7").body.should == "json!"
121
+ end
122
+
123
+ it 'allows using mime types' do
124
+ respond_with(:ignore) do |format|
125
+ format.on('text/html') { "html!" }
126
+ format.json { "json!" }
127
+ end
128
+
129
+ req(:html).body.should == "html!"
130
+ end
131
+
132
+ it 'allows using wildcards in format matchers' do
133
+ respond_with(:ignore) do |format|
134
+ format.on('text/*') { "text!" }
135
+ format.json { "json!" }
136
+ end
137
+
138
+ req(:html).body.should == "text!"
139
+ end
140
+
141
+ it 'allows using catch all wildcards in format matchers' do
142
+ respond_with(:ignore) do |format|
143
+ format.on('*/*') { "anything!" }
144
+ format.json { "json!" }
145
+ end
146
+
147
+ req(:html).body.should == "anything!"
148
+ end
149
+
150
+ it 'prefers concret over generic' do
151
+ respond_with(:ignore) do |format|
152
+ format.on('text/*') { "text!" }
153
+ format.on('*/*') { "anything!" }
154
+ format.json { "json!" }
155
+ end
156
+
157
+ req(:json).body.should == "json!"
158
+ req(:html).body.should == "text!"
159
+ end
160
+ end
161
+
162
+ describe "default behavior" do
163
+ it 'converts objects to json out of the box' do
164
+ respond_with 'a' => 'b'
165
+ OkJson.decode(req(:json).body).should == {'a' => 'b'}
166
+ end
167
+
168
+ it 'handles multiple routes correctly' do
169
+ respond_app do
170
+ get('/') { respond_with 'a' => 'b' }
171
+ get('/:name') { respond_with 'a' => params[:name] }
172
+ end
173
+ OkJson.decode(req('/', :json).body).should == {'a' => 'b'}
174
+ OkJson.decode(req('/b', :json).body).should == {'a' => 'b'}
175
+ OkJson.decode(req('/c', :json).body).should == {'a' => 'c'}
176
+ end
177
+
178
+ it "calls to_EXT if available" do
179
+ respond_with Struct.new(:to_pdf).new("hello")
180
+ req(:pdf).body.should == "hello"
181
+ end
182
+
183
+ it 'results in a 406 if format cannot be produced' do
184
+ respond_with({})
185
+ req(:html).should_not be_ok
186
+ status.should == 406
187
+ end
188
+ end
189
+
190
+ describe 'templates' do
191
+ it 'looks for templates with name.target.engine' do
192
+ respond_with :foo, :name => 'World'
193
+ req(:html).should be_ok
194
+ body.should == "Hello World!"
195
+ end
196
+
197
+ it 'looks for templates with name.engine for specific engines' do
198
+ respond_with :bar
199
+ req(:html).should be_ok
200
+ body.should == "Girl! I wanna take you to a ... bar!"
201
+ end
202
+
203
+ it 'does not use name.engine for engines producing other formats' do
204
+ respond_with :not_html
205
+ req(:html).should_not be_ok
206
+ status.should == 406
207
+ body.should be_empty
208
+ end
209
+
210
+ it 'falls back to #json if no template is found' do
211
+ respond_with :foo, :name => 'World'
212
+ req(:json).should be_ok
213
+ OkJson.decode(body).should == {'name' => 'World'}
214
+ end
215
+
216
+ it 'favors templates over #json' do
217
+ respond_with :bar, :name => 'World'
218
+ req(:json).should be_ok
219
+ body.should == 'json!'
220
+ end
221
+
222
+ it 'falls back to to_EXT if no template is found' do
223
+ object = {:name => 'World'}
224
+ def object.to_pdf; "hi" end
225
+ respond_with :foo, object
226
+ req(:pdf).should be_ok
227
+ body.should == "hi"
228
+ end
229
+ end
230
+
231
+ describe 'customizing' do
232
+ it 'allows customizing' do
233
+ respond_with(:foo, :name => 'World') { |f| f.html { 'html!' }}
234
+ req(:html).should be_ok
235
+ body.should == "html!"
236
+ end
237
+
238
+ it 'falls back to default behavior if none matches' do
239
+ respond_with(:foo, :name => 'World') { |f| f.json { 'json!' }}
240
+ req(:html).should be_ok
241
+ body.should == "Hello World!"
242
+ end
243
+
244
+ it 'favors generic rule over default behavior' do
245
+ respond_with(:foo, :name => 'World') { |f| f.on('*/*') { 'generic!' }}
246
+ req(:html).should be_ok
247
+ body.should == "generic!"
248
+ end
249
+ end
250
+ end
251
+
252
+ describe :respond_to do
253
+ it 'acts as global provides condition' do
254
+ respond_app do
255
+ respond_to :json, :html
256
+ get('/a') { 'ok' }
257
+ get('/b') { 'ok' }
258
+ end
259
+
260
+ req('/b', :xml).should_not be_ok
261
+ req('/b', :html).should be_ok
262
+ end
263
+
264
+ it 'still allows provides' do
265
+ respond_app do
266
+ respond_to :json, :html
267
+ get('/a') { 'ok' }
268
+ get('/b', :provides => :json) { 'ok' }
269
+ end
270
+
271
+ req('/b', :html).should_not be_ok
272
+ req('/b', :json).should be_ok
273
+ end
274
+
275
+ it 'plays well with namespaces' do
276
+ respond_app do
277
+ register Sinatra::Namespace
278
+ namespace '/a' do
279
+ respond_to :json
280
+ get { 'json' }
281
+ end
282
+ get('/b') { 'anything' }
283
+ end
284
+
285
+ req('/a', :html).should_not be_ok
286
+ req('/b', :html).should be_ok
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,6 @@
1
+ require 'sinatra/contrib'
2
+
3
+ RSpec.configure do |config|
4
+ config.expect_with :rspec, :stdlib
5
+ config.include Sinatra::TestHelpers
6
+ 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