sinatra-contrib 1.3.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 (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