radiant-reader-extension 0.9.2

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 (116) hide show
  1. data/.gitignore +2 -0
  2. data/README.md +89 -0
  3. data/Rakefile +140 -0
  4. data/VERSION +1 -0
  5. data/app/controllers/admin/messages_controller.rb +20 -0
  6. data/app/controllers/admin/reader_settings_controller.rb +92 -0
  7. data/app/controllers/admin/readers_controller.rb +28 -0
  8. data/app/controllers/password_resets_controller.rb +64 -0
  9. data/app/controllers/reader_action_controller.rb +84 -0
  10. data/app/controllers/reader_activations_controller.rb +60 -0
  11. data/app/controllers/reader_sessions_controller.rb +56 -0
  12. data/app/controllers/readers_controller.rb +131 -0
  13. data/app/helpers/admin/reader_settings_helper.rb +36 -0
  14. data/app/models/message.rb +108 -0
  15. data/app/models/message_function.rb +37 -0
  16. data/app/models/message_reader.rb +13 -0
  17. data/app/models/reader.rb +146 -0
  18. data/app/models/reader_notifier.rb +34 -0
  19. data/app/models/reader_session.rb +3 -0
  20. data/app/views/admin/messages/_form.html.haml +29 -0
  21. data/app/views/admin/messages/_help.html.haml +41 -0
  22. data/app/views/admin/messages/_message_description.html.haml +3 -0
  23. data/app/views/admin/messages/edit.html.haml +16 -0
  24. data/app/views/admin/messages/new.html.haml +16 -0
  25. data/app/views/admin/reader_settings/_setting.html.haml +24 -0
  26. data/app/views/admin/reader_settings/edit.html.haml +10 -0
  27. data/app/views/admin/reader_settings/index.html.haml +35 -0
  28. data/app/views/admin/reader_settings/show.html.haml +1 -0
  29. data/app/views/admin/readers/_avatar.html.haml +3 -0
  30. data/app/views/admin/readers/_form.html.haml +50 -0
  31. data/app/views/admin/readers/_list_head.html.haml +9 -0
  32. data/app/views/admin/readers/_listed.html.haml +22 -0
  33. data/app/views/admin/readers/_password_fields.html.haml +18 -0
  34. data/app/views/admin/readers/edit.html.haml +8 -0
  35. data/app/views/admin/readers/index.html.haml +17 -0
  36. data/app/views/admin/readers/new.html.haml +7 -0
  37. data/app/views/admin/readers/remove.html.haml +18 -0
  38. data/app/views/admin/sites/_choose_reader_layout.html.haml +7 -0
  39. data/app/views/password_resets/create.html.haml +13 -0
  40. data/app/views/password_resets/edit.html.haml +71 -0
  41. data/app/views/password_resets/new.html.haml +31 -0
  42. data/app/views/reader_activations/_activation_required.html.haml +34 -0
  43. data/app/views/reader_activations/_on_activation.html.haml +4 -0
  44. data/app/views/reader_activations/show.html.haml +41 -0
  45. data/app/views/reader_notifier/message.html.haml +1 -0
  46. data/app/views/reader_sessions/_login_form.html.haml +59 -0
  47. data/app/views/reader_sessions/new.html.haml +38 -0
  48. data/app/views/readers/_contributions.html.haml +2 -0
  49. data/app/views/readers/_controls.html.haml +25 -0
  50. data/app/views/readers/_extra_controls.html.haml +0 -0
  51. data/app/views/readers/_flasher.html.haml +6 -0
  52. data/app/views/readers/_form.html.haml +73 -0
  53. data/app/views/readers/create.html.haml +28 -0
  54. data/app/views/readers/edit.html.haml +47 -0
  55. data/app/views/readers/index.html.haml +16 -0
  56. data/app/views/readers/login.html.haml +15 -0
  57. data/app/views/readers/new.html.haml +41 -0
  58. data/app/views/readers/permission_denied.html.haml +23 -0
  59. data/app/views/readers/show.html.haml +35 -0
  60. data/app/views/wrappers/_field_errors.html.haml +5 -0
  61. data/config/routes.rb +22 -0
  62. data/config/settings.rb +9 -0
  63. data/db/migrate/001_create_readers.rb +31 -0
  64. data/db/migrate/002_extend_sites.rb +17 -0
  65. data/db/migrate/003_reader_honorifics.rb +12 -0
  66. data/db/migrate/004_user_readers.rb +11 -0
  67. data/db/migrate/005_last_login.rb +15 -0
  68. data/db/migrate/007_adapt_for_authlogic.rb +27 -0
  69. data/db/migrate/20090921125653_reader_messages.rb +27 -0
  70. data/db/migrate/20090924164413_functional_messages.rb +9 -0
  71. data/db/migrate/20090925081225_standard_messages.rb +106 -0
  72. data/db/migrate/20091006102438_message_visibility.rb +9 -0
  73. data/db/migrate/20091010083503_registration_config.rb +10 -0
  74. data/db/migrate/20091019124021_message_functions.rb +9 -0
  75. data/db/migrate/20091020133533_forenames.rb +9 -0
  76. data/db/migrate/20091020135152_contacts.rb +23 -0
  77. data/db/migrate/20091111090819_ensure_functional_messages_visible.rb +9 -0
  78. data/db/migrate/20091119092936_messages_have_layout.rb +9 -0
  79. data/db/migrate/20100922152338_lock_versions.rb +9 -0
  80. data/db/migrate/20100927095703_default_settings.rb +14 -0
  81. data/db/migrate/20101004074945_unlock_version.rb +9 -0
  82. data/lib/config_extensions.rb +5 -0
  83. data/lib/controller_extensions.rb +77 -0
  84. data/lib/reader_admin_ui.rb +64 -0
  85. data/lib/reader_helper.rb +36 -0
  86. data/lib/reader_site.rb +10 -0
  87. data/lib/reader_tags.rb +297 -0
  88. data/lib/rfc822.rb +29 -0
  89. data/lib/tasks/reader_extension_tasks.rake +28 -0
  90. data/pkg/radiant-reader-extension-0.9.0.gem +0 -0
  91. data/public/images/admin/chk_off.png +0 -0
  92. data/public/images/admin/chk_on.png +0 -0
  93. data/public/images/admin/new-message.png +0 -0
  94. data/public/images/admin/new-reader.png +0 -0
  95. data/public/javascripts/admin/messages.js +13 -0
  96. data/public/stylesheets/sass/admin/reader.sass +95 -0
  97. data/radiant-reader-extension.gemspec +184 -0
  98. data/reader_extension.rb +55 -0
  99. data/spec/controllers/admin/messages_controller_spec.rb +38 -0
  100. data/spec/controllers/admin/readers_controller_spec.rb +14 -0
  101. data/spec/controllers/password_resets_controller_spec.rb +140 -0
  102. data/spec/controllers/reader_activations_controller_spec.rb +45 -0
  103. data/spec/controllers/readers_controller_spec.rb +193 -0
  104. data/spec/datasets/messages_dataset.rb +49 -0
  105. data/spec/datasets/reader_layouts_dataset.rb +26 -0
  106. data/spec/datasets/reader_sites_dataset.rb +10 -0
  107. data/spec/datasets/readers_dataset.rb +51 -0
  108. data/spec/lib/reader_admin_ui_spec.rb +35 -0
  109. data/spec/lib/reader_site_spec.rb +18 -0
  110. data/spec/matchers/reader_login_system_matcher.rb +35 -0
  111. data/spec/models/message_spec.rb +109 -0
  112. data/spec/models/reader_notifier_spec.rb +34 -0
  113. data/spec/models/reader_spec.rb +155 -0
  114. data/spec/spec.opts +5 -0
  115. data/spec/spec_helper.rb +48 -0
  116. metadata +267 -0
@@ -0,0 +1,193 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe ReadersController do
4
+ dataset :readers, :messages
5
+
6
+ before do
7
+ controller.stub!(:request).and_return(request)
8
+ Page.current_site = sites(:test) if defined? Site
9
+ request.env["HTTP_REFERER"] = 'http://test.host/referer!'
10
+ Radiant::Config['reader.allow_registration?'] = true
11
+ end
12
+
13
+ describe "with a get to new" do
14
+ it "should render the registration screen" do
15
+ get :new
16
+ response.should be_success
17
+ response.should render_template("new")
18
+ end
19
+
20
+ it "should generate a secret email field name" do
21
+
22
+ end
23
+ end
24
+
25
+ describe "with a registration" do
26
+ before do
27
+ session[:email_field] = @email_field = 'randomstring'
28
+ post :create, :reader => {:name => "registering user", :password => "password", :password_confirmation => "password"}, :randomstring => 'registrant@spanner.org'
29
+ @reader = Reader.find_by_name('registering user')
30
+ end
31
+
32
+ it "should create a new reader" do
33
+ @reader.should_not be_nil
34
+ end
35
+
36
+ it "should set the current reader" do
37
+ controller.send(:current_reader).should == @reader
38
+ end
39
+
40
+ it "should have deobfuscated the email field" do
41
+ @reader.email.should == 'registrant@spanner.org'
42
+ end
43
+
44
+ it "should have defaulted to email address as login" do
45
+ @reader.login.should == @reader.email
46
+ end
47
+
48
+ if defined? Site
49
+ it "should have assigned the new reader to the current site" do
50
+ @reader.site.should == sites(:test)
51
+ end
52
+ end
53
+
54
+ it "should redirect to the please-activate page" do
55
+ response.should be_redirect
56
+ response.should redirect_to(reader_activation_url)
57
+ end
58
+
59
+ describe "with the trap field filled in" do
60
+ before do
61
+ session[:email_field] = @email_field = 'randomstring'
62
+ post :create, :reader => {:name => "bot user", :email => 'registrant@spanner.org'}, :password => "password", :password_confirmation => "password"
63
+ @reader = Reader.find_by_name('bot user')
64
+ end
65
+ it "should not create the reader" do
66
+ @reader.should be_nil
67
+ end
68
+ it "should re-render the form" do
69
+ response.should be_success
70
+ response.should render_template('new')
71
+ end
72
+ it "should flash a notice" do
73
+ flash[:error].should_not be_nil
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "to the browser" do
79
+ describe "who has logged in" do
80
+ before do
81
+ activate_authlogic
82
+ rsession = ReaderSession.create!(readers(:normal))
83
+ # controller.stub!(:current_reader_session).and_return(rsession)
84
+ end
85
+
86
+ it "should consent to show another reader page" do
87
+ get :show, :id => reader_id(:visible)
88
+ response.should be_success
89
+ end
90
+
91
+ it "should not show the edit page for another reader" do
92
+ get :edit, :id => reader_id(:visible)
93
+ response.should be_success
94
+ flash[:error].should =~ /not allowed/
95
+ end
96
+
97
+ it "should not remove this reader" do
98
+ delete :destroy, :id => reader_id(:normal)
99
+ response.should be_redirect
100
+ response.should redirect_to(admin_readers_url)
101
+ Reader.find(reader_id(:normal)).should_not be_nil
102
+ end
103
+
104
+ it "should not remove another reader" do
105
+ delete :destroy, :id => reader_id(:visible)
106
+ response.should be_redirect
107
+ response.should redirect_to(admin_readers_url)
108
+ Reader.find(reader_id(:visible)).should_not be_nil
109
+ end
110
+ end
111
+
112
+ describe "who has not logged in" do
113
+ before do
114
+ logout_reader
115
+ end
116
+
117
+ it "should not show a reader page" do
118
+ get :show, :id => reader_id(:visible)
119
+ response.should be_redirect
120
+ response.should redirect_to(reader_login_url)
121
+ end
122
+ end
123
+ end
124
+
125
+ describe "with an update request" do
126
+ before do
127
+ login_as_reader(:normal)
128
+ end
129
+
130
+ describe "that includes the correct password" do
131
+ before do
132
+ put :update, {:id => reader_id(:normal), :reader => {:name => "New Name", :current_password => 'password'}}
133
+ @reader = readers(:normal)
134
+ end
135
+
136
+ it "should update the reader" do
137
+ @reader.name.should == 'New Name'
138
+ end
139
+
140
+ it "should redirect to the reader page" do
141
+ response.should be_redirect
142
+ response.should redirect_to(reader_url(@reader))
143
+ end
144
+
145
+ end
146
+
147
+ describe "that does not include the correct password" do
148
+ before do
149
+ put :update, {:id => reader_id(:normal), :reader => {:name => "New Name"}, :current_password => 'wrongo'}
150
+ @reader = readers(:normal)
151
+ end
152
+
153
+ it "should not update the reader" do
154
+ @reader.name.should == 'Normal'
155
+ end
156
+
157
+ it "should re-render the edit form" do
158
+ response.should be_success
159
+ response.should render_template("edit")
160
+ end
161
+ end
162
+
163
+ describe "that does not validate" do
164
+ before do
165
+ put :update, {:id => reader_id(:normal), :reader => {:login => "visible@spanner.org"}, :current_password => 'password'}
166
+ @reader = readers(:normal)
167
+ end
168
+
169
+ it "should not update the reader" do
170
+ @reader.name.should == 'Normal'
171
+ end
172
+
173
+ it "should re-render the edit form" do
174
+ response.should be_success
175
+ response.should render_template("edit")
176
+ end
177
+
178
+ end
179
+ end
180
+
181
+ describe "when registration is not allowed" do
182
+ before do
183
+ Radiant::Config['reader.allow_registration?'] = false
184
+ end
185
+
186
+ it "should not offer the registration form" do
187
+ get :new
188
+ response.should be_redirect
189
+ response.should redirect_to reader_login_url
190
+ flash[:error].should_not be_nil
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,49 @@
1
+ class MessagesDataset < Dataset::Base
2
+ datasets = [:readers, :users]
3
+ datasets << :reader_sites if defined? Site
4
+ uses *datasets
5
+
6
+ def load
7
+ create_message "Normal"
8
+ create_message "Filtered", :filter_id => 'Textile', :body => 'this is a *filtered* message'
9
+ create_message "Welcome", :filter_id => 'Textile', :body => 'Hi', :function_id => 'welcome'
10
+ create_message "Activation", :filter_id => 'Textile', :body => 'Hi?', :function_id => 'activation'
11
+ create_message "Invitation", :filter_id => 'Textile', :body => 'Hi!', :function_id => 'invitation'
12
+ create_message "Password", :filter_id => 'Textile', :body => 'Oh', :function_id => 'password_reset'
13
+ create_message "Taggy", :filter_id => 'Textile', :body => %{
14
+ To <r:recipient:name />
15
+
16
+ Ying Tong Iddle I Po.
17
+
18
+ From <r:sender:name />
19
+ }
20
+ end
21
+
22
+ helpers do
23
+ def create_message(subject, attributes={})
24
+ attributes = message_attributes(attributes.update(:subject => subject))
25
+ message = create_model Message, subject.symbolize, attributes
26
+ message.update_attribute(:created_by, users(:existing))
27
+ end
28
+
29
+ def message_attributes(attributes={})
30
+ subject = attributes[:subject] || "Message"
31
+ symbol = subject.symbolize
32
+ attributes = {
33
+ :subject => subject,
34
+ :body => "This is the #{subject} message"
35
+ }.merge(attributes)
36
+ attributes[:site] = sites(:test) if defined? Site
37
+ attributes
38
+ end
39
+
40
+ def seem_to_send(message, reader)
41
+ message = messages(message) unless message.is_a?(Message)
42
+ reader = readers(reader) unless reader.is_a?(Reader)
43
+ sending = MessageReader.find_or_create_by_message_id_and_reader_id(message.id, reader.id)
44
+ sending.sent_at = 10.minutes.ago
45
+ sending.save
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,26 @@
1
+ class ReaderLayoutsDataset < Dataset::Base
2
+ uses :reader_sites if defined? Site
3
+
4
+ def load
5
+ create_layout "Main"
6
+ create_layout "Other"
7
+ create_layout "email", :content => %{
8
+ <html>
9
+ <head><title><r:title /></title></head>
10
+ <body>
11
+ <p>header</p>
12
+ <r:content />
13
+ <p>footer</p>
14
+ </body>
15
+ <html>
16
+ }
17
+ end
18
+
19
+ helpers do
20
+ def create_layout(name, attributes={})
21
+ attributes[:site] ||= sites(:test) if Layout.reflect_on_association(:site)
22
+ create_model :layout, name.symbolize, attributes.update(:name => name)
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,10 @@
1
+ class ReaderSitesDataset < Dataset::Base
2
+ uses :home_page
3
+
4
+ def load
5
+ create_record Site, :mysite, :name => 'My Site', :domain => 'mysite.domain.com', :base_domain => 'mysite.domain.com', :position => 1, :homepage_id => page_id(:home)
6
+ create_record Site, :yoursite, :name => 'Your Site', :domain => '^yoursite', :base_domain => 'yoursite.test.com', :position => 2
7
+ create_record Site, :test, :name => 'Test host', :domain => '^test\.', :base_domain => 'test.host', :position => 6
8
+ Page.current_site = sites(:test) if defined? Site
9
+ end
10
+ end
@@ -0,0 +1,51 @@
1
+ require "authlogic/test_case"
2
+
3
+ class ReadersDataset < Dataset::Base
4
+ datasets = [:users]
5
+ datasets << :reader_sites if defined? Site
6
+ uses *datasets
7
+
8
+ def load
9
+ create_reader "Normal"
10
+ create_reader "Visible"
11
+ create_reader "User", :user_id => user_id(:existing)
12
+ create_reader "Inactive", :activated_at => nil
13
+ end
14
+
15
+ helpers do
16
+ def create_reader(name, attributes={})
17
+ attributes = reader_attributes(attributes.update(:name => name))
18
+ reader = create_model Reader, name.symbolize, attributes
19
+ end
20
+
21
+ def reader_attributes(attributes={})
22
+ name = attributes[:name] || "John Doe"
23
+ symbol = name.symbolize
24
+ attributes = {
25
+ :name => name,
26
+ :email => "#{symbol}@spanner.org",
27
+ :login => "#{symbol}@spanner.org",
28
+ :activated_at => Time.now - 1.week,
29
+ :password_salt => "golly",
30
+ :password => 'password',
31
+ :password_confirmation => 'password'
32
+ }.merge(attributes)
33
+ attributes[:site] = sites(:test) if defined? Site
34
+ attributes
35
+ end
36
+
37
+ def login_as_reader(reader)
38
+ activate_authlogic
39
+ login_reader = reader.is_a?(Reader) ? reader : readers(reader)
40
+ ReaderSession.create(login_reader)
41
+ login_reader
42
+ end
43
+
44
+ def logout_reader
45
+ if session = ReaderSession.find
46
+ session.destroy
47
+ end
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,35 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe "AdminUI extensions for readers" do
4
+ before :each do
5
+ @admin = Radiant::AdminUI.new
6
+ @admin.reader = Radiant::AdminUI.load_default_reader_regions
7
+ end
8
+
9
+ it "should be included into Radiant::AdminUI" do
10
+ Radiant::AdminUI.included_modules.should include(ReaderAdminUI)
11
+ end
12
+
13
+ it "should define a collection of Region Sets for readers" do
14
+ @admin.should respond_to('reader')
15
+ @admin.should respond_to('readers')
16
+ @admin.send('reader').should_not be_nil
17
+ @admin.send('reader').should be_kind_of(OpenStruct)
18
+ end
19
+
20
+ describe "should define default regions" do
21
+ %w{new edit remove index}.each do |action|
22
+
23
+ describe "for '#{action}'" do
24
+ before do
25
+ @reader = @admin.reader
26
+ @reader.send(action).should_not be_nil
27
+ end
28
+
29
+ it "as a RegionSet" do
30
+ @reader.send(action).should be_kind_of(Radiant::AdminUI::RegionSet)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ if defined? Site
4
+ describe 'Reader-extended site' do
5
+ dataset :reader_layouts, :reader_sites
6
+ Radiant::Config['reader.layout'] = 'This one'
7
+
8
+ it "should have a reader_layout association" do
9
+ Site.reflect_on_association(:reader_layout).should_not be_nil
10
+ end
11
+
12
+ it "should be able to set its own layout" do
13
+ site = sites(:mysite)
14
+ site.reader_layout = layouts(:other)
15
+ site.layout_for(:reader).should == 'Other'
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ module Spec
2
+ module Rails
3
+ module Matchers
4
+ class ReaderLoginRequirement
5
+ def initialize(example)
6
+ @example = example
7
+ end
8
+
9
+ def matches?(proc)
10
+ proc.call
11
+ @response = @example.response
12
+ @was_redirect = @response.redirect?
13
+ @was_redirect_to_login = @response.redirect_url_match?("/readers/login")
14
+ @was_redirect && @was_redirect_to_login
15
+ end
16
+
17
+ def failure_message
18
+ if @was_redirect
19
+ "expected to redirect to /readers/login but redirected to #{@response.redirect_url}"
20
+ else
21
+ "expected to require reader login but did not redirect"
22
+ end
23
+ end
24
+
25
+ def negative_failure_message
26
+ "expected not to require reader login"
27
+ end
28
+ end
29
+
30
+ def require_reader_login
31
+ ReaderLoginRequirement.new(self)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,109 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Message do
4
+ dataset :messages
5
+
6
+ before do
7
+
8
+ end
9
+
10
+ describe "on validation" do
11
+ before do
12
+ @message = messages(:normal)
13
+ @message.should be_valid
14
+ end
15
+
16
+ it "should require a subject" do
17
+ @message.subject = nil
18
+ @message.should_not be_valid
19
+ @message.errors.on(:subject).should_not be_empty
20
+ end
21
+
22
+ it "should require a body" do
23
+ @message.body = ""
24
+ @message.should_not be_valid
25
+ @message.errors.on(:body).should_not be_empty
26
+ end
27
+ end
28
+
29
+ describe "with a filter" do
30
+ it "should format itself" do
31
+ messages(:filtered).body.should == "this is a *filtered* message"
32
+ messages(:filtered).filtered_body.should == "<p>this is a <strong>filtered</strong> message</p>"
33
+ end
34
+ end
35
+
36
+ describe "on preview" do
37
+ before do
38
+ @preview = messages(:taggy).preview
39
+ end
40
+
41
+ it "should render a fake sending" do
42
+ @preview.should be_kind_of(TMail::Mail)
43
+ @preview.from.should == [messages(:taggy).created_by.email]
44
+ @preview.subject.should == messages(:taggy).subject
45
+ @preview.body.should =~ /From #{messages(:taggy).created_by.name}/
46
+ end
47
+ end
48
+
49
+ describe "with a reader association" do
50
+ before do
51
+ @message = messages(:normal)
52
+ @message.readers << readers(:normal)
53
+ end
54
+
55
+ describe "but unsent" do
56
+ it "should know to whom it can belong" do
57
+ @message.possible_readers.count.should == Reader.active.count
58
+ end
59
+
60
+ it "should know to whom it does belong" do
61
+ @message.readers.include?(readers(:normal)).should be_true
62
+ end
63
+
64
+ it "should report itself unsent to anyone" do
65
+ @message.delivered?.should be_false
66
+ end
67
+
68
+ it "should report itself not sent to one of its readers" do
69
+ @message.delivered_to?(readers(:normal)).should be_false
70
+ end
71
+
72
+ it "should report itself not sent to an unrelated reader" do
73
+ @message.delivered_to?(readers(:visible)).should be_false
74
+ end
75
+ end
76
+
77
+ describe "already sent to one reader" do
78
+ before do
79
+ seem_to_send(messages(:normal), readers(:normal))
80
+ @message.readers << readers(:visible)
81
+ end
82
+
83
+ it "should report itself delivered" do
84
+ @message.delivered?.should be_true
85
+ end
86
+
87
+ it "should know to whom it has been sent" do
88
+ @message.recipients.should == [readers(:normal)]
89
+ end
90
+
91
+ it "should know to whom it has yet to be sent" do
92
+ @message.undelivered_readers.should == Reader.active - @message.recipients
93
+ end
94
+
95
+ it "should report itself delivered to that reader" do
96
+ @message.delivered_to?(readers(:normal)).should be_true
97
+ end
98
+
99
+ it "should report itself not yet sent to other readers" do
100
+ @message.readers.include?(readers(:visible)).should be_true
101
+ @message.delivered_to?(readers(:visible)).should be_false
102
+ end
103
+
104
+ end
105
+
106
+
107
+ end
108
+
109
+ end