summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/controllers/admin_controller_test.rb39
-rw-r--r--test/controllers/events_controller_test.rb170
-rw-r--r--test/controllers/nodes_controller_test.rb400
-rw-r--r--test/controllers/page_translations_controller_test.rb97
-rw-r--r--test/controllers/related_assets_controller_test.rb91
-rw-r--r--test/controllers/revisions_controller_test.rb167
-rw-r--r--test/controllers/shared_previews_controller_test.rb35
-rw-r--r--test/controllers/users_controller_test.rb2
-rw-r--r--test/models/concerns/rrule_humanizer_test.rb99
-rw-r--r--test/models/event_test.rb12
-rw-r--r--test/models/helpers/admin_helper_test.rb10
-rw-r--r--test/models/helpers/content_helper_test.rb4
-rw-r--r--test/models/helpers/datetime_helper_test.rb19
-rw-r--r--test/models/helpers/events_helper_test.rb21
-rw-r--r--test/models/helpers/nodes_helper_test.rb32
-rw-r--r--test/models/node_test.rb369
-rw-r--r--test/models/page_test.rb229
17 files changed, 1658 insertions, 138 deletions
diff --git a/test/controllers/admin_controller_test.rb b/test/controllers/admin_controller_test.rb
index 9bbf29b..00a51e2 100644
--- a/test/controllers/admin_controller_test.rb
+++ b/test/controllers/admin_controller_test.rb
@@ -1,8 +1,41 @@
1require 'test_helper' 1require 'test_helper'
2 2
3class AdminControllerTest < ActionController::TestCase 3class AdminControllerTest < ActionController::TestCase
4 # Replace this with your real tests. 4 test "current drafts includes nodes with only an autosave" do
5 test "the truth" do 5 node = Node.root.children.create!(:slug => "admin_autosave_only")
6 assert true 6 node.lock_for_editing!(User.find_by_login("aaron"))
7 node.autosave!({title: "in progress"}, User.find_by_login("aaron"))
8 node.save_draft!(User.find_by_login("aaron"))
9 node.publish_draft!
10 node.lock_for_editing!(User.find_by_login("aaron"))
11 node.autosave!({title: "editing again"}, User.find_by_login("aaron"))
12
13 login_as :quentin
14 get :index
15 assert_includes assigns(:drafts), node
16 end
17
18 test "dashboard_search returns matching tags and nodes grouped separately" do
19 node = Node.root.children.create!(:slug => "dashboard_search_test")
20 node.find_or_create_draft(User.find_by_login("aaron"))
21 node.draft.update(:title => "Biometrics Workshop")
22 node.draft.tag_list = "biometrics-workshop"
23 node.draft.save!
24
25 login_as :quentin
26 get :dashboard_search, params: { :search_term => "biometr" }, :format => :json
27
28 json = JSON.parse(response.body)
29 assert json["tags"].any? { |t| t["name"] == "biometrics-workshop" }
30 assert json["nodes"].any? { |n| n["title"] == "Biometrics Workshop" }
31 end
32
33 test "dashboard_search returns empty results for a blank term" do
34 login_as :quentin
35 get :dashboard_search, params: { :search_term => "" }, :format => :json
36
37 json = JSON.parse(response.body)
38 assert_equal [], json["tags"]
39 assert_equal [], json["nodes"]
7 end 40 end
8end 41end
diff --git a/test/controllers/events_controller_test.rb b/test/controllers/events_controller_test.rb
index 14e534e..46f3f4f 100644
--- a/test/controllers/events_controller_test.rb
+++ b/test/controllers/events_controller_test.rb
@@ -1,45 +1,133 @@
1require 'test_helper' 1require 'test_helper'
2 2
3class EventsControllerTest < ActionController::TestCase 3class EventsControllerTest < ActionController::TestCase
4 # test "should get index" do 4
5 # get :index 5 test "should get index" do
6 # assert_response :success 6 login_as :quentin
7 # assert_not_nil assigns(:events) 7 node = create_node_with_published_page
8 # end 8 Event.create!(node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour)
9 # 9
10 # test "should get new" do 10 get :index
11 # get :new 11 assert_response :success
12 # assert_response :success 12 assert_not_nil assigns(:events)
13 # end 13 end
14 # 14
15 # test "should create event" do 15 test "should get new" do
16 # assert_difference('Event.count') do 16 login_as :quentin
17 # post :create, params: { :event => { } } 17 get :new
18 # end 18 assert_response :success
19 # 19 end
20 # assert_redirected_to event_path(assigns(:event)) 20
21 # end 21 test "new pre-fills tag_list and explains it via flash when auto_tag_source is given" do
22 # 22 login_as :quentin
23 # test "should show event" do 23 node = create_node_with_published_page
24 # get :show, params: { :id => events(:one).to_param } 24
25 # assert_response :success 25 get :new, params: { node_id: node.id, tag_list: "open-day", auto_tag_source: "erfa-detail" }
26 # end 26
27 # 27 assert_response :success
28 # test "should get edit" do 28 assert_equal "open-day", assigns(:event).tag_list.to_s
29 # get :edit, params: { :id => events(:one).to_param } 29 assert_match "open-day", flash[:notice]
30 # assert_response :success 30 assert_match "erfa-detail", flash[:notice]
31 # end 31 end
32 # 32
33 # test "should update event" do 33 test "new does not flash without an auto_tag_source" do
34 # put :update, params: { :id => events(:one).to_param, :event => { } } 34 login_as :quentin
35 # assert_redirected_to event_path(assigns(:event)) 35
36 # end 36 get :new, params: { tag_list: "open-day" }
37 # 37
38 # test "should destroy event" do 38 assert_response :success
39 # assert_difference('Event.count', -1) do 39 assert_nil flash[:notice]
40 # delete :destroy, params: { :id => events(:one).to_param } 40 end
41 # end 41
42 # 42 test "new with no params renders a blank, unflashed form" do
43 # assert_redirected_to events_path 43 login_as :quentin
44 # end 44
45 get :new
46
47 assert_response :success
48 assert_nil assigns(:event).tag_list.presence
49 assert_nil flash[:notice]
50 end
51
52 test "should show event" do
53 login_as :quentin
54 node = create_node_with_published_page
55 event = Event.create!(node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour)
56
57 get :show, params: { id: event.id }
58 assert_response :success
59 end
60
61 test "should get edit" do
62 login_as :quentin
63 node = create_node_with_published_page
64 event = Event.create!(node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour)
65
66 get :edit, params: { id: event.id }
67 assert_response :success
68 end
69
70 test "should create event attached to a node" do
71 login_as :quentin
72 node = create_node_with_published_page
73
74 assert_difference('Event.count') do
75 post :create, params: {
76 event: {
77 node_id: node.id,
78 start_time: Time.now,
79 end_time: Time.now + 1.hour,
80 tag_list: "open-day"
81 }
82 }
83 end
84
85 assert_redirected_to edit_node_path(node)
86 assert_equal 'Event was successfully created.', flash[:notice]
87 end
88
89 test "should not create an event without a title or a node_id" do
90 login_as :quentin
91
92 assert_no_difference('Event.count') do
93 post :create, params: { event: { start_time: Time.now, end_time: Time.now + 1.hour } }
94 end
95
96 assert_response :success # re-renders :new, not a redirect
97 end
98
99 test "should honour return_to on create" do
100 login_as :quentin
101 node = create_node_with_published_page
102
103 post :create, params: {
104 event: { node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour },
105 return_to: node_path(node)
106 }
107
108 assert_redirected_to node_path(node)
109 end
110
111 test "should update event" do
112 login_as :quentin
113 node = create_node_with_published_page
114 event = Event.create!(node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour)
115
116 put :update, params: { id: event.id, event: { title: "Updated title" } }
117
118 assert_redirected_to events_path
119 assert_equal "Updated title", event.reload.title
120 end
121
122 test "should destroy event" do
123 login_as :quentin
124 node = create_node_with_published_page
125 event = Event.create!(node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour)
126
127 assert_difference('Event.count', -1) do
128 delete :destroy, params: { id: event.id }
129 end
130
131 assert_redirected_to events_url
132 end
45end 133end
diff --git a/test/controllers/nodes_controller_test.rb b/test/controllers/nodes_controller_test.rb
index 53799f1..81e3f45 100644
--- a/test/controllers/nodes_controller_test.rb
+++ b/test/controllers/nodes_controller_test.rb
@@ -9,13 +9,13 @@ class NodesControllerTest < ActionController::TestCase
9 get :index 9 get :index
10 assert_response :success 10 assert_response :success
11 end 11 end
12 12
13 def test_new 13 def test_new
14 login_as :quentin 14 login_as :quentin
15 get :new 15 get :new
16 assert_response :success 16 assert_response :success
17 end 17 end
18 18
19 test "create generic node with parent_id provided" do 19 test "create generic node with parent_id provided" do
20 login_as :quentin 20 login_as :quentin
21 before_count = Node.count 21 before_count = Node.count
@@ -33,7 +33,7 @@ class NodesControllerTest < ActionController::TestCase
33 assert_equal Node.last.parent_id, Node.root.id 33 assert_equal Node.last.parent_id, Node.root.id
34 assert_equal 1, Node.last.level 34 assert_equal 1, Node.last.level
35 end 35 end
36 36
37 test "create update node" do 37 test "create update node" do
38 login_as :quentin 38 login_as :quentin
39 post( 39 post(
@@ -45,7 +45,7 @@ class NodesControllerTest < ActionController::TestCase
45 ) 45 )
46 assert_response :redirect 46 assert_response :redirect
47 end 47 end
48 48
49 test "create top level node" do 49 test "create top level node" do
50 login_as :quentin 50 login_as :quentin
51 before_count = Node.count 51 before_count = Node.count
@@ -62,77 +62,79 @@ class NodesControllerTest < ActionController::TestCase
62 assert_equal expected, Node.last.unique_name 62 assert_equal expected, Node.last.unique_name
63 assert_equal 1, Node.last.level 63 assert_equal 1, Node.last.level
64 end 64 end
65 65
66 test "creating a top_level node without a title should not work" do 66 test "creating a top_level node without a title should not work" do
67 login_as :quentin 67 login_as :quentin
68 68
69 assert_no_difference "Node.count" do 69 assert_no_difference "Node.count" do
70 post(:create, params: { :kind => "top_level" } ) 70 post(:create, params: { :kind => "top_level" } )
71 end 71 end
72 end 72 end
73 73
74 test "creating a generic node without a parent_id should not work" do 74 test "creating a generic node without a parent_id should not work" do
75 login_as :quentin 75 login_as :quentin
76 76
77 assert_no_difference "Node.count" do 77 assert_no_difference "Node.count" do
78 post(:create, params: { :kind => "generic" } ) 78 post(:create, params: { :kind => "generic" } )
79 end 79 end
80 end 80 end
81 81
82 test "editing a node" do 82 test "editing a node" do
83 login_as :quentin 83 login_as :quentin
84 84
85 node = Node.find_by_unique_name("fourth_child") 85 node = Node.find_by_unique_name("fourth_child")
86 node.pages.create 86 node.pages.create
87 node.draft = node.pages.last 87 node.draft = node.pages.last
88 node.save 88 node.save
89 89
90 assert_equal 1, node.pages.length 90 assert_equal 1, node.pages.length
91 91
92 draft = node.find_or_create_draft( User.first ) 92 draft = node.find_or_create_draft( User.first )
93 draft.title = "Hello" 93 draft.title = "Hello"
94 draft.body = "World" 94 draft.body = "World"
95 draft.save 95 draft.save
96 node.publish_draft! 96 node.publish_draft!
97 97
98 get :edit, params: { :id => node.id } 98 get :edit, params: { :id => node.id }
99 assert_response :success 99 assert_response :success
100 assert_select("#page_title[value='Hello']") 100 assert_select("#page_title[value='Hello']")
101 assert_select("#page_body", "World") 101 assert_select("#page_body", "World")
102 102
103 node.reload 103 node.reload
104 assert_equal 2, node.pages.length 104 assert_equal 1, node.pages.length
105 assert_equal "Hello", node.find_or_create_draft( User.first ).title 105 assert_equal "Hello", node.find_or_create_draft( User.first ).title
106 assert_equal "World", node.find_or_create_draft( User.first ).body 106 assert_equal "World", node.find_or_create_draft( User.first ).body
107 end 107 end
108 108
109 test "editing a locked node raises LockedByAnotherUser Exception" do 109 test "editing a locked node raises LockedByAnotherUser Exception" do
110 login_as :quentin 110 login_as :quentin
111 111
112 node = create_node_with_draft 112 node = create_node_with_draft
113 node.lock_owner = User.last 113 node.lock_owner = User.last
114 node.save 114 node.save
115 115
116 assert node.locked? 116 assert node.locked?
117 117
118 get :edit, params: { :id => node.id } 118 get :edit, params: { :id => node.id }
119 assert_response :redirect 119 assert_response :redirect
120 assert flash[:error] =~ /Page is locked by another user/ 120 assert flash[:error] =~ /Page is locked by another user/
121 end 121 end
122 122
123 def test_update_a_draft 123 def test_update_a_draft
124 test_node = Node.root.children.create! :slug => "test_node" 124 test_node = Node.root.children.create! :slug => "test_node"
125 login_as :quentin 125 login_as :quentin
126 get :edit, params: { :id => test_node.id }
126 put :update, params: { :id => test_node.id, :page => {:title => "Hello", :body => "There"} } 127 put :update, params: { :id => test_node.id, :page => {:title => "Hello", :body => "There"} }
127 test_node.reload 128 test_node.reload
128 assert_equal "Hello", test_node.draft.title 129 assert_equal "Hello", test_node.draft.title
129 assert_equal "There", test_node.draft.body 130 assert_equal "There", test_node.draft.body
130 end 131 end
131 132
132 def test_update_a_draft_with_changing_the_template 133 def test_update_a_draft_with_changing_the_template
133 test_node = Node.root.children.create! :slug => "test_node" 134 test_node = Node.root.children.create! :slug => "test_node"
134 135
135 login_as :quentin 136 login_as :quentin
137 get :edit, params: { :id => test_node.id }
136 put :update, params: { 138 put :update, params: {
137 :id => test_node.id, 139 :id => test_node.id,
138 :page => { 140 :page => {
@@ -148,19 +150,19 @@ class NodesControllerTest < ActionController::TestCase
148 assert_equal "There", test_node.head.body 150 assert_equal "There", test_node.head.body
149 assert_equal "Foobar", test_node.head.template_name 151 assert_equal "Foobar", test_node.head.template_name
150 end 152 end
151 153
152 test "publish draft with staged_slug unqueal slug" do 154 test "publish draft with staged_slug unqueal slug" do
153 login_as :quentin 155 login_as :quentin
154 156
155 test_node = Node.root.children.create! :slug => "test_node", :staged_slug => "peter_pan" 157 test_node = Node.root.children.create! :slug => "test_node", :staged_slug => "peter_pan"
156 158
157 put :publish, params: { :id => test_node.id } 159 put :publish, params: { :id => test_node.id }
158 160
159 test_node.reload 161 test_node.reload
160 assert_equal "peter_pan", test_node.slug 162 assert_equal "peter_pan", test_node.slug
161 assert_equal "peter_pan", test_node.unique_name 163 assert_equal "peter_pan", test_node.unique_name
162 end 164 end
163 165
164 test "publish draft with staged_slug with more levels of nodes" do 166 test "publish draft with staged_slug with more levels of nodes" do
165 login_as :quentin 167 login_as :quentin
166 168
@@ -168,12 +170,12 @@ class NodesControllerTest < ActionController::TestCase
168 test_node2 = test_node.children.create! :slug => "test_node2" 170 test_node2 = test_node.children.create! :slug => "test_node2"
169 171
170 put :publish, params: { :id => test_node.id } 172 put :publish, params: { :id => test_node.id }
171 173
172 test_node.reload; test_node2.reload 174 test_node.reload; test_node2.reload
173 assert_equal "peter_pan/test_node2", test_node2.unique_name 175 assert_equal "peter_pan/test_node2", test_node2.unique_name
174 assert_equal "peter_pan", test_node.unique_name 176 assert_equal "peter_pan", test_node.unique_name
175 end 177 end
176 178
177 test "publish draft with staged_parent_id" do 179 test "publish draft with staged_parent_id" do
178 login_as :quentin 180 login_as :quentin
179 181
@@ -187,77 +189,77 @@ class NodesControllerTest < ActionController::TestCase
187 assert_equal "parent/test_node", test_node.unique_name 189 assert_equal "parent/test_node", test_node.unique_name
188 assert_equal "parent/test_node/test_node2", test_node2.unique_name 190 assert_equal "parent/test_node/test_node2", test_node2.unique_name
189 end 191 end
190 192
191 test "publish draft with staged_parent_id and staged_slug" do 193 test "publish draft with staged_parent_id and staged_slug" do
192 login_as :quentin 194 login_as :quentin
193 195
194 parent = Node.root.children.create! :slug => "parent" 196 parent = Node.root.children.create! :slug => "parent"
195 197
196 test_node = Node.root.children.create!( 198 test_node = Node.root.children.create!(
197 :slug => "test_node", 199 :slug => "test_node",
198 :staged_parent_id => parent.id, 200 :staged_parent_id => parent.id,
199 :staged_slug => "peter_pan" 201 :staged_slug => "peter_pan"
200 ) 202 )
201 203
202 test_node2 = test_node.children.create! :slug => "test_node2" 204 test_node2 = test_node.children.create! :slug => "test_node2"
203 205
204 put :publish, params: { :id => test_node.id } 206 put :publish, params: { :id => test_node.id }
205 207
206 test_node.reload; test_node2.reload 208 test_node.reload; test_node2.reload
207 assert_equal "parent/peter_pan", test_node.unique_name 209 assert_equal "parent/peter_pan", test_node.unique_name
208 assert_equal "parent/peter_pan/test_node2", test_node2.unique_name 210 assert_equal "parent/peter_pan/test_node2", test_node2.unique_name
209 end 211 end
210 212
211 test "show node with empty draft" do 213 test "show node with empty draft" do
212 login_as :quentin 214 login_as :quentin
213 assert_not_nil node = create_node_with_draft 215 assert_not_nil node = create_node_with_draft
214 get :show, params: { :id => node.id } 216 get :show, params: { :id => node.id }
215 assert_response :success 217 assert_response :success
216 end 218 end
217 219
218 test "show node with published draft" do 220 test "show node with published draft" do
219 login_as :quentin 221 login_as :quentin
220 node = create_node_with_published_page 222 node = create_node_with_published_page
221 get :show, params: { :id => node.id } 223 get :show, params: { :id => node.id }
222 assert_response :success 224 assert_response :success
223 assert_select "td", :text => "Test", :count => 3 225 assert_select "div.node_content", :text => "Test", :count => 2
224 end 226 end
225 227
226 test "unlocking a locked node" do 228 test "unlocking a locked node" do
227 login_as :quentin 229 login_as :quentin
228 node = create_node_with_published_page 230 node = create_node_with_published_page
229 node.find_or_create_draft User.first 231 node.find_or_create_draft User.first
230 232
231 assert node.locked? 233 assert node.locked?
232 234
233 put :unlock, params: { :id => node.id } 235 put :unlock, params: { :id => node.id }
234 assert_response :redirect 236 assert_response :redirect
235 assert !node.reload.locked? 237 assert !node.reload.locked?
236 end 238 end
237 239
238 test "unlocking an already unlocked node" do 240 test "unlocking an already unlocked node" do
239 login_as :quentin 241 login_as :quentin
240 node = create_node_with_published_page 242 node = create_node_with_published_page
241 243
242 put :unlock, params: { :id => node.id } 244 put :unlock, params: { :id => node.id }
243 assert_response :redirect 245 assert_response :redirect
244 assert_equal "Already unlocked", flash[:notice] 246 assert_equal "Already unlocked", flash[:notice]
245 end 247 end
246 248
247 test "updating a node by changing its parent" do 249 test "updating a node by changing its parent" do
248 Node.root.descendants.destroy_all 250 Node.root.descendants.destroy_all
249 login_as :quentin 251 login_as :quentin
250 node = create_node_with_published_page 252 node = create_node_with_published_page
251 node.find_or_create_draft User.first 253 node.find_or_create_draft User.first
252 254
253 other_node = Node.root.children.create( :slug => "other" ) 255 other_node = Node.root.children.create( :slug => "other" )
254 256
255 node.staged_parent_id = other_node.id 257 node.staged_parent_id = other_node.id
256 node.publish_draft! 258 node.publish_draft!
257 259
258 assert Node.valid? 260 assert Node.valid?
259 end 261 end
260 262
261 test "editing the initial draft sets the author to current_user" do 263 test "editing the initial draft sets the author to current_user" do
262 login_as :quentin 264 login_as :quentin
263 Node.root.descendants.destroy_all 265 Node.root.descendants.destroy_all
@@ -266,7 +268,7 @@ class NodesControllerTest < ActionController::TestCase
266 node.reload 268 node.reload
267 assert_equal "quentin", node.draft.user.login 269 assert_equal "quentin", node.draft.user.login
268 end 270 end
269 271
270 test "updating the author of a node with existing head" do 272 test "updating the author of a node with existing head" do
271 login_as :quentin 273 login_as :quentin
272 Node.root.descendants.destroy_all 274 Node.root.descendants.destroy_all
@@ -275,70 +277,73 @@ class NodesControllerTest < ActionController::TestCase
275 node.find_or_create_draft users(:quentin) 277 node.find_or_create_draft users(:quentin)
276 assert node.draft.valid? 278 assert node.draft.valid?
277 assert node.valid? 279 assert node.valid?
278 280
279 put :update, params: { :id => node.id, :page => {:user_id => users(:aaron).id} } 281 put :update, params: { :id => node.id, :page => {:user_id => users(:aaron).id} }
280 assert_response :redirect 282 assert_response :redirect
281 assert_equal "aaron", node.reload.draft.user.login 283 assert_equal "aaron", node.reload.draft.user.login
282 end 284 end
283 285
284 test "updating an existing page should not modify published_at" do 286 test "updating an existing page should not modify published_at" do
285 login_as :quentin 287 login_as :quentin
286 Node.root.descendants.destroy_all 288 Node.root.descendants.destroy_all
287 node = create_node_with_published_page 289 node = create_node_with_published_page
288 290
289 get :edit, params: { :id => node.id } 291 get :edit, params: { :id => node.id }
290 assert_response :success 292 assert_response :success
291 293
294 put :update, params: { :id => node.id, :page => { :title => "updated" } }
292 put :publish, params: { :id => node.id } 295 put :publish, params: { :id => node.id }
293 296
294 node.reload 297 node.reload
295 assert_equal node.pages[0].published_at, node.pages[1].published_at 298 assert_equal node.pages[0].published_at, node.pages[1].published_at
296 end 299 end
297 300
298 test "updating an exisiting page should not alter the author" do 301 test "updating an exisiting page should not alter the author" do
299 login_as :aaron 302 login_as :aaron
300 Node.root.descendants.destroy_all 303 Node.root.descendants.destroy_all
301 node = create_node_with_published_page 304 node = create_node_with_published_page
302 get :edit, params: { :id => node.id } 305 get :edit, params: { :id => node.id }
303 306
307 put :update, params: { :id => node.id, :page => { :title => "updated" } }
304 put :publish, params: { :id => node.id } 308 put :publish, params: { :id => node.id }
305 309
306 node.reload 310 node.reload
307 assert_equal node.pages[0].user, node.pages[1].user 311 assert_equal node.pages[0].user, node.pages[1].user
308 end 312 end
309 313
310 test "editor and author are the same on a new node" do 314 test "editor and author are the same on a new node" do
311 login_as :quentin 315 login_as :quentin
312 node = create_node_with_draft 316 node = create_node_with_draft
313 get :edit, params: { :id => node.id } 317 get :edit, params: { :id => node.id }
314 318
315 node.reload 319 node.reload
316 assert_equal "quentin", node.draft.user.login 320 assert_equal "quentin", node.draft.user.login
317 assert_equal "quentin", node.draft.editor.login 321 assert_equal "quentin", node.draft.editor.login
318 end 322 end
319 323
320 test "creating new draft alters the editor but keeps the author" do 324 test "creating new draft alters the editor but keeps the author" do
321 node = create_node_with_published_page 325 node = create_node_with_published_page
322 assert_equal "quentin", node.head.user.login 326 assert_equal "quentin", node.head.user.login
323 327
324 login_as :aaron 328 login_as :aaron
325 get :edit, params: {:id => node.id } 329 get :edit, params: { :id => node.id }
326 330 put :update, params: { :id => node.id, :page => { :title => "updated" } }
331
327 node.reload 332 node.reload
328 assert_equal "quentin", node.head.user.login 333 assert_equal "quentin", node.head.user.login
329 assert_equal "aaron", node.draft.editor.login 334 assert_equal "aaron", node.draft.editor.login
330 end 335 end
331 336
332 test "unlocking and relocking changes editor if done by another user" do 337 test "unlocking and relocking changes editor if done by another user" do
333 node = create_node_with_published_page 338 node = create_node_with_published_page
334 draft = node.find_or_create_draft users(:quentin) 339 draft = node.find_or_create_draft users(:quentin)
335 assert_equal draft.user.login, draft.editor.login 340 assert_equal draft.user.login, draft.editor.login
336 assert node.locked? 341 assert node.locked?
337 node.unlock! 342 node.unlock!
338 343
339 login_as :aaron 344 login_as :aaron
340 get :edit, params: { :id => node.id } 345 get :edit, params: { :id => node.id }
341 346
342 node.reload 347 node.reload
343 assert_equal "quentin", node.draft.user.login 348 assert_equal "quentin", node.draft.user.login
344 assert_equal "aaron", node.draft.editor.login 349 assert_equal "aaron", node.draft.editor.login
@@ -350,6 +355,7 @@ class NodesControllerTest < ActionController::TestCase
350 355
351 login_as :quentin 356 login_as :quentin
352 get :index 357 get :index
358 assert_response :success
353 end 359 end
354 360
355 test "no dangling pages remain after node removal" do 361 test "no dangling pages remain after node removal" do
@@ -364,7 +370,7 @@ class NodesControllerTest < ActionController::TestCase
364 370
365 test "can remove a node with an event" do 371 test "can remove a node with an event" do
366 node = create_node_with_published_page 372 node = create_node_with_published_page
367 Event.create!( 373 event = Event.create!(
368 :start_time => "2009-01-01T15:23:42".to_time, 374 :start_time => "2009-01-01T15:23:42".to_time,
369 :end_time => "2009-01-01T20:05:23".to_time, 375 :end_time => "2009-01-01T20:05:23".to_time,
370 :url => "http://events.ccc.de/congress/2082", 376 :url => "http://events.ccc.de/congress/2082",
@@ -373,10 +379,272 @@ class NodesControllerTest < ActionController::TestCase
373 :allday => true, 379 :allday => true,
374 :node_id => node.id 380 :node_id => node.id
375 ) 381 )
382 event_id = event.id
383 assert_operator Occurrence.where(event_id: event_id).count, :>, 0, "expected the event to have generated at least one occurrence before destroy"
384
376 node.destroy 385 node.destroy
377 386
387 assert_equal 0, Occurrence.where(event_id: event_id).count
388
378 login_as :quentin 389 login_as :quentin
379 get :index 390 get :index
391 assert_response :success
392 end
393
394 test "show renders events row and add-link for zero-event chapter node" do
395 login_as :quentin
396 node = create_node_with_published_page
397 node.head.tag_list = "erfa-detail"
398 node.head.save!
399
400 get :show, params: { id: node.id }
401 assert_response :success
402 assert_select "a", text: "add event"
403 assert_select "a[href*='tag_list=open-day']"
404 assert_select "a[href*='auto_tag_source=erfa-detail']"
405 end
406
407 test "show renders events row without a tag default for untagged node" do
408 login_as :quentin
409 node = create_node_with_published_page
410
411 get :show, params: { id: node.id }
412 assert_response :success
413 assert_select "a", text: "add event"
414 assert_select "a[href*='tag_list=']", count: 0
415 end
416
417 test "show never renders a destroy link for events" do
418 login_as :quentin
419 node = create_node_with_published_page
420 Event.create!(node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour)
421
422 get :show, params: { id: node.id }
423 assert_response :success
424 assert_select "form.button_to.destructive", count: 0
425 end
426
427 test "reverting from nodes#show returns to the show page, not the editor, even if a draft remains" do
428 user = User.find_by_login("aaron")
429 node = Node.root.children.create!(:slug => "revert_return_to_test")
430 node.lock_for_editing!(user)
431 node.autosave!({:title => "v1"}, user)
432 node.save_draft!(user)
433 node.publish_draft!
434 node.lock_for_editing!(user)
435 node.autosave!({:title => "v2"}, user)
436 node.save_draft!(user)
437 node.lock_for_editing!(user)
438 node.autosave!({:title => "v3"}, user)
439 # state D: head, draft, and autosave all present, locked by aaron
440
441 login_as :aaron
442 put :revert, params: { :id => node.id, :return_to => node_path(node) }
443 assert_redirected_to node_path(node)
444 node.reload
445 assert node.draft.present?
446 assert node.autosave.blank?
447 end
448
449 test "reverting from nodes#edit without return_to still lands back in the editor when a draft remains" do
450 user = User.find_by_login("aaron")
451 node = Node.root.children.create!(:slug => "revert_default_test")
452 node.lock_for_editing!(user)
453 node.autosave!({:title => "v1"}, user)
454 node.save_draft!(user)
455 node.publish_draft!
456 node.lock_for_editing!(user)
457 node.autosave!({:title => "v2"}, user)
458 node.save_draft!(user)
459 node.lock_for_editing!(user)
460 node.autosave!({:title => "v3"}, user)
461
462 login_as :aaron
463 put :revert, params: { :id => node.id }
464 assert_redirected_to edit_node_path(node)
465 end
466
467 test "nodes#show does not offer to destroy the only draft of a never-published node" do
468 node = Node.root.children.create!(:slug => "draft_only_test")
469 login_as :quentin
470 get :show, params: { :id => node.id }
471 assert_response :success
472 assert_select "form.destructive", :count => 0
473 end
474
475 test "drafts includes a never-published node with only a draft" do
476 node = Node.root.children.create!(:slug => "drafts_action_test")
477 login_as :quentin
478 get :drafts
479 assert_includes assigns(:nodes), node
480 end
481
482 test "chapters filters by kind, matching head or draft, and shows both by default" do
483 erfa_node = Node.root.children.create!(:slug => "chapters_erfa_test")
484 erfa_node.find_or_create_draft(@user1)
485 erfa_node.draft.tag_list = "erfa-detail"
486 erfa_node.draft.save!
487 erfa_node.publish_draft!
488
489 chaostreff_node = Node.root.children.create!(:slug => "chapters_chaostreff_test")
490 chaostreff_node.find_or_create_draft(@user1)
491 chaostreff_node.draft.tag_list = "chaostreff-detail"
492 chaostreff_node.draft.save!
493 chaostreff_node.publish_draft!
494
495 login_as :quentin
496
497 get :chapters, params: { :kinds => "erfa" }
498 assert_includes assigns(:nodes), erfa_node
499 assert_not_includes assigns(:nodes), chaostreff_node
500
501 get :chapters
502 assert_includes assigns(:nodes), erfa_node
503 assert_includes assigns(:nodes), chaostreff_node
380 end 504 end
381 505
506 test "recent combined with a search term does not raise an ambiguous column error" do
507 login_as :quentin
508 get :recent, params: { :q => "Zombies" }
509 assert_response :success
510 end
511
512 test "drafts combined with a search term does not raise an ambiguous column error" do
513 login_as :quentin
514 get :drafts, params: { :q => "Zombies" }
515 assert_response :success
516 end
517
518 test "mine combined with a search term does not raise an ambiguous column error" do
519 login_as :quentin
520 get :mine, params: { :q => "Zombies" }
521 assert_response :success
522 end
523
524 test "mine shows each matching node only once, even with several revisions by the same user" do
525 login_as :quentin
526 user = User.find_by_login("quentin")
527 node = Node.root.children.create!(:slug => "dedup_test")
528 node.lock_for_editing!(user)
529 node.autosave!({:title => "v1"}, user)
530 node.save_draft!(user)
531 node.publish_draft!
532 node.lock_for_editing!(user)
533 node.autosave!({:title => "v2"}, user)
534 node.save_draft!(user)
535 node.publish_draft!
536 # three pages now exist on this node, all touched by quentin --
537 # without DISTINCT, the join would return this node three times
538
539 get :mine
540 matches = assigns(:nodes).select { |n| n.id == node.id }
541 assert_equal 1, matches.length
542 end
543
544 test "chapters combined with a search term does not raise an ambiguous column error" do
545 login_as :quentin
546 get :chapters, params: { :q => "Zombies" }
547 assert_response :success
548 end
549
550 test "tags path filters by an arbitrary raw tag, generalizing chapters" do
551 presse_node = Node.root.children.create!(:slug => "tags_path_presse_test")
552 presse_node.find_or_create_draft(@user1)
553 presse_node.draft.tag_list = "pressemitteilung"
554 presse_node.draft.save!
555 presse_node.publish_draft!
556
557 erfa_node = Node.root.children.create!(:slug => "tags_path_erfa_test")
558 erfa_node.find_or_create_draft(@user1)
559 erfa_node.draft.tag_list = "erfa-detail"
560 erfa_node.draft.save!
561 erfa_node.publish_draft!
562
563 login_as :quentin
564 get :tags, params: { :tags => "pressemitteilung" }
565
566 assert_includes assigns(:nodes), presse_node
567 assert_not_includes assigns(:nodes), erfa_node
568
569 assert_select "h1", "Nodes tagged: pressemitteilung"
570 assert_select "h1", :text => "Chapters", :count => 0
571 end
572
573 test "tags path with multiple tags matches any of them, not all" do
574 erfa_node = Node.root.children.create!(:slug => "tags_path_multi_erfa_test")
575 erfa_node.find_or_create_draft(@user1)
576 erfa_node.draft.tag_list = "erfa-detail"
577 erfa_node.draft.save!
578 erfa_node.publish_draft!
579
580 chaostreff_node = Node.root.children.create!(:slug => "tags_path_multi_chaostreff_test")
581 chaostreff_node.find_or_create_draft(@user1)
582 chaostreff_node.draft.tag_list = "chaostreff-detail"
583 chaostreff_node.draft.save!
584 chaostreff_node.publish_draft!
585
586 login_as :quentin
587 get :tags, params: {:tags => "erfa-detail,chaostreff-detail" }
588
589 assert_includes assigns(:nodes), erfa_node
590 assert_includes assigns(:nodes), chaostreff_node
591 end
592
593 test "chapters renders the curated heading" do
594 login_as :quentin
595 get :chapters
596 assert_select "h1", "Chapters"
597 end
598
599 test "sitemap collapses configured paths but leaves others open" do
600 club = Node.root.children.create!(:slug => "club")
601 erfas = club.children.create!(:slug => "erfas")
602 erfas.children.create!(:slug => "one_chapter")
603 other = Node.root.children.create!(:slug => "sitemap_controller_open_test")
604 other.children.create!(:slug => "sitemap_controller_open_child")
605
606 login_as :quentin
607 get :sitemap
608 assert_response :success
609
610 doc = Nokogiri::HTML::DocumentFragment.parse(response.body)
611
612 erfas_node_div = doc.css('.sitemap_node').find { |div| div.at_css('.field_hint')&.text&.include?(erfas.unique_name) }
613 other_node_div = doc.css('.sitemap_node').find { |div| div.at_css('.field_hint')&.text&.include?(other.unique_name) }
614
615 erfas_details = erfas_node_div.next_element
616 other_details = other_node_div.next_element
617
618 assert_equal 'details', erfas_details.name
619 assert_equal 'details', other_details.name
620 assert_not erfas_details.key?('open')
621 assert other_details.key?('open')
622 end
623
624 test "sitemap shows how many descendants a collapsed branch is hiding" do
625 club = Node.root.children.create!(:slug => "club")
626 erfas = club.children.create!(:slug => "erfas")
627 erfas.children.create!(:slug => "one_chapter")
628 erfas.children.create!(:slug => "another_chapter")
629
630 login_as :quentin
631 get :sitemap
632 assert_response :success
633
634 doc = Nokogiri::HTML::DocumentFragment.parse(response.body)
635 erfas_node_div = doc.css('.sitemap_node').find { |div| div.at_css('.field_hint')&.text&.include?(erfas.unique_name) }
636 erfas_details = erfas_node_div.next_element
637
638 assert_equal 'details', erfas_details.name
639 assert_match "2 descendants", erfas_details.at_css('summary').text
640 end
641
642 test "sitemap shows Show and Create Child, not Revisions" do
643 node = Node.root.children.create!(:slug => "sitemap_actions_test")
644 login_as :quentin
645 get :sitemap
646 assert_select ".sitemap_node_actions", :text => /Show/
647 assert_select ".sitemap_node_actions", :text => /Create Child/
648 assert_select ".sitemap_node_actions", :text => /Revisions/, :count => 0
649 end
382end 650end
diff --git a/test/controllers/page_translations_controller_test.rb b/test/controllers/page_translations_controller_test.rb
new file mode 100644
index 0000000..8c899fd
--- /dev/null
+++ b/test/controllers/page_translations_controller_test.rb
@@ -0,0 +1,97 @@
1require 'test_helper'
2
3class PageTranslationsControllerTest < ActionController::TestCase
4 test "index lists the default locale's existing translation and flags a missing one" do
5 login_as :quentin
6 node = Node.root.children.create!(:slug => "translations_index_test")
7 node.publish_draft!
8
9 get :index, params: { :node_id => node.id }
10
11 assert_response :success
12 assert_equal [:en], assigns(:translations).map { |t| t[:locale] }
13 assert_equal false, assigns(:translations).first[:exists]
14 end
15
16 test "update creates a first-time translation on a fresh draft, leaving head untouched" do
17 login_as :quentin
18 node = Node.root.children.create!(:slug => "translations_create_test")
19 Globalize.with_locale(:de) { node.draft.update!(:title => "Deutscher Titel") }
20 node.publish_draft!
21 node.lock_for_editing!(users(:quentin))
22
23 patch :update, params: { :node_id => node.id, :translation_locale => "en", :page => { :title => "English Title" } }
24
25 node.reload
26 assert_not_nil node.draft
27 assert_includes node.draft.translated_locales, :en
28 assert_not_includes node.head.translated_locales, :en
29 end
30
31 test "no route exists for the default locale under the translations resource" do
32 login_as :quentin
33 node = Node.root.children.create!(:slug => "translations_route_constraint_test")
34
35 assert_raises(ActionController::UrlGenerationError) do
36 patch :update, params: { :node_id => node.id, :translation_locale => "de", :page => { :title => "x" } }
37 end
38 end
39
40 test "update with Save + Unlock + Exit unlocks the node and redirects to nodes#show" do
41 login_as :quentin
42 node = Node.root.children.create!(:slug => "translations_exit_test")
43 node.lock_for_editing!(users(:quentin))
44
45 patch :update, params: { :node_id => node.id, :translation_locale => "en", :page => { :title => "x" }, :commit => "Save + Unlock + Exit" }
46
47 assert_nil node.reload.lock_owner
48 assert_redirected_to node_path(node)
49 end
50
51 test "update saves the translation onto the draft" do
52 login_as :quentin
53 node = Node.root.children.create!(:slug => "translations_update_test")
54 Globalize.with_locale(:en) { node.draft.update!(:title => "Original") }
55 node.lock_for_editing!(users(:quentin))
56
57 patch :update, params: { :node_id => node.id, :translation_locale => "en", :page => { :title => "Revised" } }
58
59 assert_equal "Revised", Globalize.with_locale(:en) { node.draft.reload.title }
60 end
61
62 test "destroy refuses to remove the only remaining translation" do
63 login_as :quentin
64 node = Node.root.children.create!(:slug => "translations_destroy_last_test")
65 Globalize.with_locale(:en) { node.draft.update!(:title => "Only translation") }
66 node.draft.translations.where(:locale => :de).delete_all
67
68 delete :destroy, params: { :node_id => node.id, :translation_locale => "en" }
69
70 assert_equal "Can't remove the only remaining translation.", flash[:error]
71 end
72
73 test "destroy is a safe no-op, not a false success, when the translation doesn't exist" do
74 login_as :quentin
75 node = Node.root.children.create!(:slug => "translations_destroy_missing_test")
76
77 delete :destroy, params: { :node_id => node.id, :translation_locale => "en" }
78
79 assert_match(/No EN translation exists/, flash[:error])
80 end
81
82 test "autosave writes the translation without creating a new revision or touching the draft" do
83 login_as :quentin
84 node = Node.root.children.create!(:slug => "translations_autosave_test")
85 node.publish_draft!
86 node.lock_for_editing!(users(:quentin))
87 page_count_before = node.pages.count
88
89 put :autosave, params: { :node_id => node.id, :translation_locale => "en", :page => { :title => "in progress" } }
90
91 assert_response :success
92 node.reload
93 assert_not_nil node.autosave
94 assert_equal page_count_before, node.pages.count
95 assert_equal "in progress", Globalize.with_locale(:en) { node.autosave.title }
96 end
97end
diff --git a/test/controllers/related_assets_controller_test.rb b/test/controllers/related_assets_controller_test.rb
new file mode 100644
index 0000000..9d20cbb
--- /dev/null
+++ b/test/controllers/related_assets_controller_test.rb
@@ -0,0 +1,91 @@
1require 'test_helper'
2
3class RelatedAssetsControllerTest < ActionController::TestCase
4 test "search finds assets by name, excluding ones already attached" do
5 login_as :quentin
6 node = Node.root.children.create!(:slug => "related_assets_search_test")
7
8 attached = Asset.create!(:name => "biometrics-scan", :upload_content_type => "image/png")
9 node.draft.assets << attached
10
11 Asset.create!(:name => "biometrics-poster", :upload_content_type => "image/png")
12 Asset.create!(:name => "chaostreff-flyer", :upload_content_type => "image/png")
13
14 get :search, params: { :node_id => node.id, :search_term => "biometrics" }
15
16 json = JSON.parse(response.body)
17 names = json.map { |a| a["name"] }
18 assert_includes names, "biometrics-poster"
19 assert_not_includes names, "biometrics-scan"
20 assert_not_includes names, "chaostreff-flyer"
21 end
22
23 test "search with a blank term returns the most recently created assets" do
24 Asset.delete_all
25 RelatedAsset.delete_all
26 login_as :quentin
27 node = Node.root.children.create!(:slug => "related_assets_recent_test")
28
29 Asset.create!(:name => "older-photo", :upload_content_type => "image/png", :created_at => 2.days.ago)
30 Asset.create!(:name => "newer-photo", :upload_content_type => "image/png", :created_at => 1.hour.ago)
31
32 get :search, params: { :node_id => node.id, :search_term => "" }
33
34 json = JSON.parse(response.body)
35 assert_equal ["newer-photo", "older-photo"], json.map { |a| a["name"] }
36 end
37
38 test "create attaches an asset to the node's editable page" do
39 login_as :quentin
40 node = Node.root.children.create!(:slug => "related_assets_create_test")
41 asset = Asset.create!(:name => "erfa-photo", :upload_content_type => "image/png")
42
43 post :create, params: { :node_id => node.id, :asset_id => asset.id }
44
45 assert_response :success
46 assert_includes node.draft.reload.related_assets.map(&:asset_id), asset.id
47 json = JSON.parse(response.body)
48 assert json["url"].present?
49 end
50
51 test "create does not duplicate an already-attached asset" do
52 login_as :quentin
53 node = Node.root.children.create!(:slug => "related_assets_dup_test")
54 asset = Asset.create!(:name => "erfa-photo-2", :upload_content_type => "image/png")
55 node.draft.assets << asset
56
57 post :create, params: { :node_id => node.id, :asset_id => asset.id }
58
59 assert_response :success
60 assert_equal 1, node.draft.reload.related_assets.count
61 end
62
63 test "destroy removes the attached asset" do
64 login_as :quentin
65 node = Node.root.children.create!(:slug => "related_assets_destroy_test")
66 asset = Asset.create!(:name => "old-photo", :upload_content_type => "image/png")
67 node.draft.assets << asset
68 related = node.draft.related_assets.first
69
70 delete :destroy, params: { :node_id => node.id, :id => related.id }
71
72 assert_response :success
73 assert_equal 0, node.draft.reload.related_assets.count
74 end
75
76 test "update reorders the attached assets" do
77 login_as :quentin
78 node = Node.root.children.create!(:slug => "related_assets_reorder_test")
79 first = Asset.create!(:name => "first-photo", :upload_content_type => "image/png")
80 second = Asset.create!(:name => "second-photo", :upload_content_type => "image/png")
81 node.draft.assets << first
82 node.draft.assets << second
83
84 second_related = node.draft.related_assets.find_by(:asset_id => second.id)
85 patch :update, params: { :node_id => node.id, :id => second_related.id, :position => 1 }
86
87 assert_response :success
88 ordered_asset_ids = node.draft.reload.related_assets.map(&:asset_id)
89 assert_equal [second.id, first.id], ordered_asset_ids
90 end
91end
diff --git a/test/controllers/revisions_controller_test.rb b/test/controllers/revisions_controller_test.rb
index b4dcd8f..e5726f7 100644
--- a/test/controllers/revisions_controller_test.rb
+++ b/test/controllers/revisions_controller_test.rb
@@ -1,12 +1,12 @@
1require 'test_helper' 1require 'test_helper'
2 2
3class RevisionsControllerTest < ActionController::TestCase 3class RevisionsControllerTest < ActionController::TestCase
4 4
5 def setup 5 def setup
6 Node.root.descendants.destroy_all 6 Node.root.descendants.destroy_all
7 @user = User.find_by_login("aaron") 7 @user = User.find_by_login("aaron")
8 @node = Node.root.children.create!( :slug => "version_me" ) 8 @node = Node.root.children.create!( :slug => "version_me" )
9 9
10 draft = @node.draft 10 draft = @node.draft
11 draft.body = "first" 11 draft.body = "first"
12 @node.publish_draft! 12 @node.publish_draft!
@@ -15,48 +15,189 @@ class RevisionsControllerTest < ActionController::TestCase
15 draft.update(:body => "second") 15 draft.update(:body => "second")
16 @node.publish_draft! 16 @node.publish_draft!
17 end 17 end
18 18
19 test "setup" do 19 test "setup" do
20 assert_equal 2, Node.count 20 assert_equal 2, Node.count
21 assert_equal 2, @node.pages.count 21 assert_equal 2, @node.pages.count
22 assert_equal ["first", "second"], @node.pages.map {|p| p.body} 22 assert_equal ["first", "second"], @node.pages.map {|p| p.body}
23 end 23 end
24 24
25 test "get list of revisions for a given node" do 25 test "get list of revisions for a given node" do
26 login_as :quentin 26 login_as :quentin
27 get :index, params: { :node_id => @node.id } 27 get :index, params: { :node_id => @node.id }
28 assert_response :success 28 assert_response :success
29 assert_select ".revision", 2 29 assert_select ".revision", 2
30 end 30 end
31 31
32 test "showing one revision" do 32 test "showing one revision" do
33 login_as :quentin 33 login_as :quentin
34 get :show, params: { :node_id => @node.id, :id => @node.pages.last.id } 34 get :show, params: { :node_id => @node.id, :id => @node.pages.last.id }
35 assert_response :success 35 assert_response :success
36 assert_select "strong", "Body" 36 assert_select ".node_description", "Body"
37 assert_select "td", {:count => 1, :text => "second"} 37 assert_select ".node_content", {:count => 1, :text => "second"}
38 end
39
40 test "showing a revision renders real markup in the body, not escaped entities" do
41 login_as :quentin
42 node = Node.root.children.create!(:slug => "show_markup_test")
43 draft = node.draft
44 draft.body = "<h3>Hello</h3>"
45 node.publish_draft!
46
47 get :show, params: { :node_id => node.id, :id => node.head.id }
48 assert_response :success
49 assert_select ".node_content h3", "Hello"
38 end 50 end
39 51
40 test "diffing two revisions" do 52 test "diffing two revisions" do
41 login_as :quentin 53 login_as :quentin
42 post( 54 post(
43 :diff, params: { 55 :diff, params: {
44 :node_id => @node.id, 56 :node_id => @node.id,
45 :start_revision => @node.pages.first.revision, 57 :start_revision => @node.pages.first.revision,
46 :end_revision => @node.pages.last.revision 58 :end_revision => @node.pages.last.revision
47 } 59 }
48 ) 60 )
49 assert_response :success 61 assert_response :success
50 end 62 end
51 63
52 test "restoring a revision" do 64 test "restoring a revision" do
53 assert_equal "second", @node.head.body 65 assert_equal "second", @node.head.body
54 66
55 login_as :aaron 67 login_as :aaron
56 put( :restore, params: { :node_id => @node.id, :id => @node.pages.first.id } ) 68 put( :restore, params: { :node_id => @node.id, :id => @node.pages.first.id } )
57 69
58 @node.reload 70 @node.reload
59 assert_equal @node.head, @node.pages.first 71 assert_equal @node.head, @node.pages.first
60 assert_equal "first", @node.head.reload.body 72 assert_equal "first", @node.head.reload.body
61 end 73 end
74
75 test "diffing two revisions renders real markup with only the changed words marked" do
76 login_as :quentin
77 post(
78 :diff, params: {
79 :node_id => @node.id,
80 :start_revision => @node.pages.first.revision,
81 :end_revision => @node.pages.last.revision
82 }
83 )
84 assert_response :success
85 assert_select "del", "first"
86 assert_select "ins", "second"
87 assert_no_match /&lt;/, response.body
88 end
89
90 test "diffing two revisions in side by side view renders two columns" do
91 login_as :quentin
92 post(
93 :diff, params: {
94 :node_id => @node.id,
95 :start_revision => @node.pages.first.revision,
96 :end_revision => @node.pages.last.revision,
97 :view => "side_by_side"
98 }
99 )
100 assert_response :success
101 assert_select ".diff_column", 2
102 end
103
104 test "diffing head against draft by name" do
105 login_as :quentin
106 @node.find_or_create_draft(@user)
107 @node.draft.update(:body => "draft body")
108
109 post(:diff, params: { :node_id => @node.id, :start_revision => "head", :end_revision => "draft" })
110 assert_response :success
111 end
112
113 test "diffing a layer pair that no longer exists redirects with a flash" do
114 login_as :quentin
115 post(:diff, params: { :node_id => @node.id, :start_revision => "draft", :end_revision => "autosave" })
116 assert_redirected_to node_path(@node)
117 assert flash[:error].present?
118 end
119
120 test "diffing by name shows a clear comparison label instead of a misleading revision picker" do
121 login_as :quentin
122 @node.find_or_create_draft(@user)
123
124 post(:diff, params: { :node_id => @node.id, :start_revision => "head", :end_revision => "draft" })
125 assert_response :success
126 assert_select "strong", "Head"
127 assert_select "strong", "Draft"
128 assert_select "select[name=?]", "start_revision", :count => 0
129 end
130
131 test "pair-switcher buttons carry their params as real hidden fields, not a query string" do
132 login_as :quentin
133 @node.find_or_create_draft(@user)
134 @node.lock_for_editing!(@user)
135 @node.autosave!({ :body => "unsaved" }, @user)
136
137 post(:diff, params: { :node_id => @node.id, :start_revision => "head", :end_revision => "draft" })
138 assert_response :success
139 assert_select "form.computation input[type=hidden][name=start_revision]"
140 assert_select "form.computation input[type=hidden][name=end_revision]"
141 end
142
143 test "the view toggle is available even when comparing named layers" do
144 login_as :quentin
145 @node.find_or_create_draft(@user)
146
147 post(:diff, params: { :node_id => @node.id, :start_revision => "head", :end_revision => "draft" })
148 assert_response :success
149 assert_select "a", "Side by side"
150 end
151
152 test "diffing two revisions also shows tag, template, and asset changes" do
153 login_as :quentin
154 @node.find_or_create_draft(@user)
155 @node.draft.tag_list = "update"
156 @node.draft.save!
157
158 post(:diff, params: { :node_id => @node.id, :start_revision => @node.pages.first.revision, :end_revision => @node.pages.last.revision })
159 assert_response :success
160 assert_select "h3", "Tags"
161 assert_select "h3", "Template"
162 assert_select "h3", "Assets"
163 end
164
165 test "revisions#index links back to the node" do
166 login_as :quentin
167 get :index, params: { :node_id => @node.id }
168 assert_response :success
169 assert_select "a[href=?]", node_path(@node)
170 end
171
172 test "diff defaults to a locale that actually changed, not the ambient default" do
173 login_as :quentin
174 node = Node.root.children.create!(:slug => "diff_default_locale_test")
175 node.draft.save!
176 node.publish_draft!
177
178 node.find_or_create_draft(@user)
179 Globalize.with_locale(:en) { node.draft.update!(:title => "Changed in English only") }
180 node.draft.save!
181
182 post(:diff, params: { :node_id => node.id, :start_revision => "head", :end_revision => "draft" })
183
184 assert_response :success
185 assert_match(/Changed/, response.body)
186 end
187
188 test "diff respects an explicitly requested locale over the auto-detected one" do
189 login_as :quentin
190 node = Node.root.children.create!(:slug => "diff_explicit_locale_test")
191 node.draft.save!
192 node.publish_draft!
193
194 node.find_or_create_draft(@user)
195 Globalize.with_locale(:en) { node.draft.update!(:title => "English changed") }
196 node.draft.save!
197
198 post(:diff, params: { :node_id => node.id, :start_revision => "head", :end_revision => "draft", :locale => "de" })
199
200 assert_response :success
201 assert_no_match(/English changed/, response.body)
202 end
62end 203end
diff --git a/test/controllers/shared_previews_controller_test.rb b/test/controllers/shared_previews_controller_test.rb
new file mode 100644
index 0000000..5f2d20d
--- /dev/null
+++ b/test/controllers/shared_previews_controller_test.rb
@@ -0,0 +1,35 @@
1require 'test_helper'
2
3class SharedPreviewsControllerTest < ActionController::TestCase
4 test "renders the preview for a draft that is current but not yet head" do
5 node = Node.root.children.create!(:slug => "shared_preview_draft_test")
6 node.draft.ensure_preview_token!
7
8 get :show, params: { :token => node.draft.preview_token }
9
10 assert_response :success
11 end
12
13 test "renders the preview for a brand-new draft on an already-published node" do
14 node = Node.root.children.create!(:slug => "shared_preview_published_node_test")
15 node.publish_draft!
16 node.find_or_create_draft(User.first)
17 node.draft.ensure_preview_token!
18
19 get :show, params: { :token => node.draft.preview_token }
20
21 assert_response :success
22 end
23
24 test "redirects to the live page once the previewed draft has been published and become head" do
25 node = Node.root.children.create!(:slug => "shared_preview_now_head_test")
26 node.draft.ensure_preview_token!
27 token = node.draft.preview_token
28
29 node.publish_draft!
30
31 get :show, params: { :token => token }
32
33 assert_redirected_to node.head.public_link
34 end
35end
diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb
index 5cd5ad4..58f8a86 100644
--- a/test/controllers/users_controller_test.rb
+++ b/test/controllers/users_controller_test.rb
@@ -13,7 +13,7 @@ class UsersControllerTest < ActionController::TestCase
13 login_as :aaron 13 login_as :aaron
14 get :index 14 get :index
15 assert_response :success 15 assert_response :success
16 assert_select "button[type=submit]", "destroy" 16 assert_select "button[type=submit]", "Destroy"
17 assert_select "a", "show" 17 assert_select "a", "show"
18 end 18 end
19 19
diff --git a/test/models/concerns/rrule_humanizer_test.rb b/test/models/concerns/rrule_humanizer_test.rb
new file mode 100644
index 0000000..279ff73
--- /dev/null
+++ b/test/models/concerns/rrule_humanizer_test.rb
@@ -0,0 +1,99 @@
1require 'test_helper'
2
3class RruleHumanizerTest < ActiveSupport::TestCase
4 def humanize(rrule, locale = :de)
5 Event.new(rrule: rrule).humanize_rrule(locale)
6 end
7
8 test "weekly single day" do
9 assert_equal "Jeden Dienstag", humanize("FREQ=WEEKLY;BYDAY=TU")
10 assert_equal "Every Tuesday", humanize("FREQ=WEEKLY;BYDAY=TU", :en)
11 end
12
13 test "weekly two days" do
14 assert_equal "Jeden Mittwoch und Freitag", humanize("FREQ=WEEKLY;BYDAY=WE,FR")
15 assert_equal "Every Wednesday and Friday", humanize("FREQ=WEEKLY;BYDAY=WE,FR", :en)
16 end
17
18 test "weekly no byday" do
19 assert_equal "Wöchentlich", humanize("FREQ=WEEKLY")
20 assert_equal "Weekly", humanize("FREQ=WEEKLY", :en)
21 end
22
23 test "biweekly with day" do
24 assert_equal "Alle zwei Wochen donnerstags", humanize("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH")
25 assert_equal "Every other Thursday", humanize("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH", :en)
26 end
27
28 test "biweekly no day" do
29 assert_equal "Alle zwei Wochen", humanize("FREQ=WEEKLY;INTERVAL=2")
30 assert_equal "Every other week", humanize("FREQ=WEEKLY;INTERVAL=2", :en)
31 end
32
33 test "monthly nth weekday" do
34 assert_equal "Jeden ersten Dienstag im Monat", humanize("FREQ=MONTHLY;BYDAY=1TU")
35 assert_equal "Jeden zweiten Freitag im Monat", humanize("FREQ=MONTHLY;BYDAY=2FR")
36 assert_equal "Jeden dritten Sonntag im Monat", humanize("FREQ=MONTHLY;BYDAY=3SU")
37 assert_equal "Jeden letzten Mittwoch im Monat", humanize("FREQ=MONTHLY;BYDAY=-1WE")
38 end
39
40 test "monthly nth weekday english" do
41 assert_equal "Every first Tuesday of the month", humanize("FREQ=MONTHLY;BYDAY=1TU", :en)
42 assert_equal "Every last Wednesday of the month", humanize("FREQ=MONTHLY;BYDAY=-1WE", :en)
43 end
44
45 test "monthly second-to-last" do
46 assert_equal "Jeden vorletzten Donnerstag im Monat", humanize("FREQ=MONTHLY;BYDAY=-2TH")
47 assert_equal "Every second-to-last Thursday of the month", humanize("FREQ=MONTHLY;BYDAY=-2TH", :en)
48 end
49
50 test "monthly no byday" do
51 assert_equal "Monatlich", humanize("FREQ=MONTHLY")
52 assert_equal "Monthly", humanize("FREQ=MONTHLY", :en)
53 end
54
55 test "monthly with single excluded month" do
56 assert_equal "Jeden letzten Donnerstag im Monat, außer im Dezember",
57 humanize("FREQ=MONTHLY;BYDAY=-1TH;BYMONTH=1,2,3,4,5,6,7,8,9,10,11")
58 assert_equal "Every last Thursday of the month, except in December",
59 humanize("FREQ=MONTHLY;BYDAY=-1TH;BYMONTH=1,2,3,4,5,6,7,8,9,10,11", :en)
60 end
61
62 test "monthly excluding january" do
63 assert_equal "Jeden zweiten Mittwoch im Monat, außer im Januar",
64 humanize("FREQ=MONTHLY;BYMONTH=2,3,4,5,6,7,8,9,10,11,12;BYDAY=2WE")
65 end
66
67 test "blank rrule returns nil" do
68 assert_nil humanize(nil)
69 assert_nil humanize("")
70 end
71
72 test "count and until are not guessed at" do
73 assert_nil humanize("FREQ=MONTHLY;BYDAY=1WE;COUNT=36")
74 assert_nil humanize("FREQ=MONTHLY;BYDAY=1WE;UNTIL=20050105T222222Z")
75 end
76
77 test "unrecognized freq returns nil" do
78 assert_nil humanize("FREQ=YEARLY;BYMONTH=12")
79 end
80
81 test "falls back to english for unknown locale" do
82 assert_equal "Every Tuesday", humanize("FREQ=WEEKLY;BYDAY=TU", :fr)
83 end
84
85 test "wday_abbr returns the correct German abbreviation for each day" do
86 monday = Time.parse("2026-07-06") # confirmed Monday
87 assert_equal "Mo", RruleHumanizer.wday_abbr(monday, :de)
88 assert_equal "Di", RruleHumanizer.wday_abbr(monday + 1.day, :de)
89 assert_equal "Mi", RruleHumanizer.wday_abbr(monday + 2.days, :de)
90 assert_equal "Do", RruleHumanizer.wday_abbr(monday + 3.days, :de)
91 assert_equal "Fr", RruleHumanizer.wday_abbr(monday + 4.days, :de)
92 assert_equal "Sa", RruleHumanizer.wday_abbr(monday + 5.days, :de)
93 assert_equal "So", RruleHumanizer.wday_abbr(monday + 6.days, :de)
94 end
95
96 test "wday_abbr falls back to :de for an unrecognized locale" do
97 assert_equal "Mo", RruleHumanizer.wday_abbr(Time.parse("2026-07-06"), :fr)
98 end
99end
diff --git a/test/models/event_test.rb b/test/models/event_test.rb
index f310af8..e98605e 100644
--- a/test/models/event_test.rb
+++ b/test/models/event_test.rb
@@ -29,7 +29,6 @@ class EventTest < ActiveSupport::TestCase
29 :longitude => 13.378944, 29 :longitude => 13.378944,
30 :rrule => "FOOBAR", 30 :rrule => "FOOBAR",
31 :allday => false, 31 :allday => false,
32 :custom_rrule => false,
33 :node_id => @cal_node.id 32 :node_id => @cal_node.id
34 ) 33 )
35 end 34 end
@@ -44,7 +43,6 @@ class EventTest < ActiveSupport::TestCase
44 :longitude => 13.378944, 43 :longitude => 13.378944,
45 :rrule => nil, 44 :rrule => nil,
46 :allday => false, 45 :allday => false,
47 :custom_rrule => false,
48 :node_id => @cal_node.id 46 :node_id => @cal_node.id
49 ) 47 )
50 48
@@ -62,7 +60,6 @@ class EventTest < ActiveSupport::TestCase
62 :longitude => 13.378944, 60 :longitude => 13.378944,
63 :rrule => "FREQ=WEEKLY;INTERVAL=1", 61 :rrule => "FREQ=WEEKLY;INTERVAL=1",
64 :allday => false, 62 :allday => false,
65 :custom_rrule => false,
66 :node_id => @cal_node.id 63 :node_id => @cal_node.id
67 ) 64 )
68 65
@@ -74,17 +71,17 @@ class EventTest < ActiveSupport::TestCase
74 71
75 assert_equal "2009-12-24T15:23:42".to_time, scoped_occurrences[51].start_time 72 assert_equal "2009-12-24T15:23:42".to_time, scoped_occurrences[51].start_time
76 assert_equal "2009-12-24T20:05:23".to_time, scoped_occurrences[51].end_time 73 assert_equal "2009-12-24T20:05:23".to_time, scoped_occurrences[51].end_time
77 assert_equal @cal_node.event, scoped_occurrences[51].event 74 assert_equal @cal_node.events.first, scoped_occurrences[51].event
78 assert_equal @cal_node, scoped_occurrences[51].node 75 assert_equal @cal_node, scoped_occurrences[51].node
79 76
80 assert_equal "2009-03-19T15:23:42".to_time, scoped_occurrences[11].start_time 77 assert_equal "2009-03-19T15:23:42".to_time, scoped_occurrences[11].start_time
81 assert_equal "2009-03-19T20:05:23".to_time, scoped_occurrences[11].end_time 78 assert_equal "2009-03-19T20:05:23".to_time, scoped_occurrences[11].end_time
82 assert_equal @cal_node.event, scoped_occurrences[11].event 79 assert_equal @cal_node.events.first, scoped_occurrences[11].event
83 assert_equal @cal_node, scoped_occurrences[11].node 80 assert_equal @cal_node, scoped_occurrences[11].node
84 81
85 assert_equal "2009-01-01T15:23:42".to_time, scoped_occurrences[0].start_time 82 assert_equal "2009-01-01T15:23:42".to_time, scoped_occurrences[0].start_time
86 assert_equal "2009-01-01T20:05:23".to_time, scoped_occurrences[0].end_time 83 assert_equal "2009-01-01T20:05:23".to_time, scoped_occurrences[0].end_time
87 assert_equal @cal_node.event, scoped_occurrences[11].event 84 assert_equal @cal_node.events.first, scoped_occurrences[11].event
88 assert_equal @cal_node, scoped_occurrences[11].node 85 assert_equal @cal_node, scoped_occurrences[11].node
89 end 86 end
90 87
@@ -97,7 +94,6 @@ class EventTest < ActiveSupport::TestCase
97 :longitude => 13.378944, 94 :longitude => 13.378944,
98 :rrule => "FREQ=MONTHLY;INTERVAL=1;BYDAY=-1WE", 95 :rrule => "FREQ=MONTHLY;INTERVAL=1;BYDAY=-1WE",
99 :allday => false, 96 :allday => false,
100 :custom_rrule => true,
101 :node_id => @cal_node.id 97 :node_id => @cal_node.id
102 ) 98 )
103 99
@@ -111,4 +107,4 @@ class EventTest < ActiveSupport::TestCase
111 chaosradio_days = scoped_occurrences.map {|x| x.start_time.day} 107 chaosradio_days = scoped_occurrences.map {|x| x.start_time.day}
112 assert_equal expected_days, chaosradio_days 108 assert_equal expected_days, chaosradio_days
113 end 109 end
114end \ No newline at end of file 110end
diff --git a/test/models/helpers/admin_helper_test.rb b/test/models/helpers/admin_helper_test.rb
index 23d9f40..94e9861 100644
--- a/test/models/helpers/admin_helper_test.rb
+++ b/test/models/helpers/admin_helper_test.rb
@@ -1,4 +1,14 @@
1require 'test_helper' 1require 'test_helper'
2 2
3class AdminHelperTest < ActionView::TestCase 3class AdminHelperTest < ActionView::TestCase
4 test "mtime_busted_path appends the file's real mtime as a query param" do
5 path = "/stylesheets/admin.css"
6 expected_mtime = File.mtime(Rails.public_path.join("stylesheets/admin.css")).to_i
7
8 assert_equal "#{path}?v=#{expected_mtime}", mtime_busted_path(path)
9 end
10
11 test "mtime_busted_path raises clearly for a missing file rather than silently omitting the version" do
12 assert_raises(RuntimeError) { mtime_busted_path("/stylesheets/does_not_exist.css") }
13 end
4end 14end
diff --git a/test/models/helpers/content_helper_test.rb b/test/models/helpers/content_helper_test.rb
index 2da82d7..a7ed478 100644
--- a/test/models/helpers/content_helper_test.rb
+++ b/test/models/helpers/content_helper_test.rb
@@ -1,4 +1,8 @@
1require 'test_helper' 1require 'test_helper'
2 2
3class ContentHelperTest < ActionView::TestCase 3class ContentHelperTest < ActionView::TestCase
4 test "weekday_abbr delegates through the current I18n locale" do
5 I18n.locale = :de
6 assert_equal "Mo", weekday_abbr(Time.parse("2026-07-06"))
7 end
4end 8end
diff --git a/test/models/helpers/datetime_helper_test.rb b/test/models/helpers/datetime_helper_test.rb
new file mode 100644
index 0000000..a24e5d3
--- /dev/null
+++ b/test/models/helpers/datetime_helper_test.rb
@@ -0,0 +1,19 @@
1require 'test_helper'
2
3class DatetimeHelperTest < ActionView::TestCase
4 test "relative_time_phrase pluralizes correctly in English and German" do
5 travel_to Time.zone.parse("2026-07-12 12:00:00") do
6 I18n.with_locale(:en) do
7 assert_equal "1 day ago", relative_time_phrase(1.day.ago)
8 assert_equal "3 days ago", relative_time_phrase(3.days.ago)
9 end
10 I18n.with_locale(:de) do
11 assert_equal "vor 1 Tag", relative_time_phrase(1.day.ago)
12 assert_equal "vor 3 Tagen", relative_time_phrase(3.days.ago)
13 assert_equal "vor 1 Monat", relative_time_phrase(30.days.ago)
14 assert_equal "vor 2 Monaten", relative_time_phrase(45.days.ago)
15 assert_equal "vor 2 Jahren", relative_time_phrase(2.years.ago)
16 end
17 end
18 end
19end
diff --git a/test/models/helpers/events_helper_test.rb b/test/models/helpers/events_helper_test.rb
index 2e7567e..0486b3e 100644
--- a/test/models/helpers/events_helper_test.rb
+++ b/test/models/helpers/events_helper_test.rb
@@ -1,4 +1,25 @@
1require 'test_helper' 1require 'test_helper'
2 2
3class EventsHelperTest < ActionView::TestCase 3class EventsHelperTest < ActionView::TestCase
4 test "rrule_with_break_opportunities inserts a break opportunity after each semicolon" do
5 result = rrule_with_break_opportunities("FREQ=MONTHLY;BYMONTH=1,2,3;BYDAY=-1TH")
6 assert_equal "FREQ=MONTHLY;<wbr>BYMONTH=1,2,3;<wbr>BYDAY=-1TH", result
7 assert result.html_safe?
8 end
9
10 test "rrule_with_break_opportunities escapes HTML-significant characters" do
11 result = rrule_with_break_opportunities("FREQ=WEEKLY;BYDAY=<script>")
12 assert_no_match /<script>/, result
13 assert_match "&lt;script&gt;", result
14 end
15
16 test "rrule_with_break_opportunities returns an empty string for blank input" do
17 assert_equal "", rrule_with_break_opportunities(nil)
18 assert_equal "", rrule_with_break_opportunities("")
19 end
20
21 test "rrule_with_break_opportunities preserves a trailing semicolon" do
22 result = rrule_with_break_opportunities("FREQ=WEEKLY;")
23 assert_equal "FREQ=WEEKLY;<wbr>", result
24 end
4end 25end
diff --git a/test/models/helpers/nodes_helper_test.rb b/test/models/helpers/nodes_helper_test.rb
index 13011de..5ab924f 100644
--- a/test/models/helpers/nodes_helper_test.rb
+++ b/test/models/helpers/nodes_helper_test.rb
@@ -1,4 +1,36 @@
1require 'test_helper' 1require 'test_helper'
2 2
3class NodesHelperTest < ActionView::TestCase 3class NodesHelperTest < ActionView::TestCase
4 FakePage = Struct.new(:tag_list)
5
6 test "default_event_tag_mapping matches erfa-detail" do
7 page = FakePage.new(["erfa-detail"])
8 assert_equal ["erfa-detail", "open-day"], default_event_tag_mapping(page)
9 end
10
11 test "default_event_tag_mapping matches chaostreff-detail" do
12 page = FakePage.new(["chaostreff-detail"])
13 assert_equal ["chaostreff-detail", "open-day"], default_event_tag_mapping(page)
14 end
15
16 test "default_event_tag_mapping returns nil for unrelated tags" do
17 page = FakePage.new(["update"])
18 assert_nil default_event_tag_mapping(page)
19 end
20
21 test "default_event_tag_list is nil without a matching tag" do
22 page = FakePage.new([])
23 assert_nil default_event_tag_list(page)
24 end
25
26 test "sitemap_node_open? is false for a configured collapsed path" do
27 club = Node.root.children.create!(:slug => "club")
28 erfas = club.children.create!(:slug => "erfas")
29 assert_equal false, sitemap_node_open?(erfas)
30 end
31
32 test "sitemap_node_open? is true for anything not configured as collapsed" do
33 node = Node.root.children.create!(:slug => "sitemap_open_test")
34 assert_equal true, sitemap_node_open?(node)
35 end
4end 36end
diff --git a/test/models/node_test.rb b/test/models/node_test.rb
index 514ba3f..91a4a6d 100644
--- a/test/models/node_test.rb
+++ b/test/models/node_test.rb
@@ -98,6 +98,7 @@ class NodeTest < ActiveSupport::TestCase
98 assert_not_nil node.draft 98 assert_not_nil node.draft
99 assert_nil node.draft.user 99 assert_nil node.draft.user
100 assert_nil node.head 100 assert_nil node.head
101 assert_nil node.autosave
101 end 102 end
102 103
103 def test_create_new_draft_of_published_page 104 def test_create_new_draft_of_published_page
@@ -110,15 +111,19 @@ class NodeTest < ActiveSupport::TestCase
110 node.publish_draft! 111 node.publish_draft!
111 assert_not_nil node.find_or_create_draft( @user1 ) 112 assert_not_nil node.find_or_create_draft( @user1 )
112 end 113 end
113 114
114 def test_find_or_create_draft_if_draft_exists_and_is_owned_by_user 115 def test_find_or_create_draft_if_draft_exists_and_is_owned_by_user
115 node = Node.root.children.create :slug => "xyz" 116 node = Node.root.children.create :slug => "xyz"
116 node.publish_draft! 117 node.publish_draft!
117 118
118 node.find_or_create_draft @user1 119 first_call = node.find_or_create_draft @user1
119 node.find_or_create_draft @user1 120 second_call = node.find_or_create_draft @user1
121
122 assert_equal first_call, second_call
123 assert_equal 2, node.pages.count
124 assert_equal @user1, node.lock_owner
120 end 125 end
121 126
122 def test_exception_if_draft_exists_but_locked_by_another_user 127 def test_exception_if_draft_exists_but_locked_by_another_user
123 node = Node.root.children.create :slug => "xyz" 128 node = Node.root.children.create :slug => "xyz"
124 node.publish_draft! 129 node.publish_draft!
@@ -280,6 +285,191 @@ class NodeTest < ActiveSupport::TestCase
280 node = Node.root.children.create( :slug => "wow" ) 285 node = Node.root.children.create( :slug => "wow" )
281 assert_nil node.draft.published_at 286 assert_nil node.draft.published_at
282 end 287 end
288
289 test "lock_for_editing! acquires the lock without creating a draft or autosave" do
290 node = create_node_with_published_page
291
292 node.lock_for_editing!(@user1)
293
294 assert_equal @user1, node.lock_owner
295 assert_nil node.draft
296 assert_nil node.autosave
297 end
298
299 test "autosave! creates a buffer that never appears among a node's pages, leaving the draft untouched" do
300 node = create_node_with_draft
301 node.lock_for_editing!(@user1)
302 page_count_before = node.pages.count
303
304 node.autosave!({ :title => "in progress" }, @user1)
305 node.reload
306
307 assert_not_nil node.autosave
308 assert_nil node.autosave.node_id
309 assert_equal page_count_before, node.pages.count
310 assert_not_equal "in progress", node.draft.title
311 end
312
313 test "save_draft! promotes an autosave into an existing draft without creating a new revision" do
314 node = create_node_with_draft
315 node.lock_for_editing!(@user1)
316 node.autosave!({ :title => "in progress" }, @user1)
317 page_count_before = node.pages.count
318
319 node.save_draft!(@user1)
320 node.reload
321
322 assert_nil node.autosave
323 assert_equal "in progress", node.draft.title
324 assert_equal page_count_before, node.pages.count
325 end
326
327 test "save_draft! promotes an autosave into a brand new, correctly-revisioned draft when none exists" do
328 node = create_node_with_published_page
329 head_revision = node.head.revision
330
331 node.lock_for_editing!(@user1)
332 node.autosave!({ :title => "updated version" }, @user1)
333 node.reload
334
335 assert_nil node.draft
336 assert_nil node.autosave.node_id
337
338 node.save_draft!(@user1)
339 node.reload
340
341 assert_not_nil node.draft
342 assert_equal head_revision + 1, node.draft.revision
343 assert_equal head_revision, node.head.revision
344 assert_nil node.autosave
345 assert_equal 2, node.pages.count
346 assert_equal node.head.user, node.draft.user
347 assert_equal @user1, node.draft.editor
348 assert_equal node.head.published_at, node.draft.published_at
349 end
350
351 test "autosave!, save_draft!, and lock_for_editing! raise LockedByAnotherUser for a second user" do
352 node = create_node_with_published_page
353 node.lock_for_editing!(@user1)
354
355 assert_raise(LockedByAnotherUser) { node.autosave!({ :title => "x" }, @user2) }
356 assert_raise(LockedByAnotherUser) { node.save_draft!(@user2) }
357 assert_raise(LockedByAnotherUser) { node.lock_for_editing!(@user2) }
358
359 assert_equal @user1, node.reload.lock_owner
360 end
361
362 test "wipe_draft! leaves a fresh autosave and its lock untouched" do
363 node = create_node_with_published_page
364 node.lock_for_editing!(@user1)
365 node.autosave!({ :title => "still typing" }, @user1)
366
367 node.wipe_draft!
368 node.reload
369
370 assert_not_nil node.autosave
371 assert_equal @user1, node.lock_owner
372 end
373
374 test "wipe_draft! destroys a stale, orphaned autosave and releases its lock" do
375 node = create_node_with_published_page
376 node.lock_for_editing!(@user1)
377 node.autosave!({ :title => "abandoned mid-session" }, @user1)
378 node.autosave.update_column(:updated_at, 2.days.ago)
379
380 node.wipe_draft!
381 node.reload
382
383 assert_nil node.autosave
384 assert_nil node.lock_owner
385 end
386
387 test "revert! is a safe no-op on a fresh node with only a draft" do
388 node = create_node_with_draft
389 node.lock_for_editing!(@user1)
390
391 node.revert!(@user1)
392 node.reload
393
394 assert_not_nil node.draft
395 assert_equal @user1, node.lock_owner
396 end
397
398 test "revert! discards an autosave on a fresh node without touching its only draft" do
399 node = create_node_with_draft
400 node.lock_for_editing!(@user1)
401 node.autosave!({ :title => "typing" }, @user1)
402
403 node.revert!(@user1)
404 node.reload
405
406 assert_nil node.autosave
407 assert_not_nil node.draft
408 assert_equal @user1, node.lock_owner
409 end
410
411 test "revert! does nothing when a published node has no draft or autosave" do
412 node = create_node_with_published_page
413 node.lock_for_editing!(@user1)
414
415 node.revert!(@user1)
416 node.reload
417
418 assert_not_nil node.head
419 assert_nil node.draft
420 end
421
422 test "revert! discards a fresh autosave and releases the lock when no draft exists" do
423 node = create_node_with_published_page
424 node.lock_for_editing!(@user1)
425 node.autosave!({ :title => "in progress" }, @user1)
426
427 node.revert!(@user1)
428 node.reload
429
430 assert_nil node.autosave
431 assert_nil node.draft
432 assert_nil node.lock_owner
433 end
434
435 test "revert! destroys an existing draft and releases the lock" do
436 node = create_node_with_published_page
437 head_title = node.head.title
438 node.lock_for_editing!(@user1)
439 node.autosave!({ :title => "second version" }, @user1)
440 node.save_draft!(@user1)
441
442 node.revert!(@user1)
443 node.reload
444
445 assert_nil node.draft
446 assert_equal head_title, node.head.title
447 assert_nil node.lock_owner
448 end
449
450 test "revert! discards only the autosave when a draft survives beneath it" do
451 node = create_node_with_published_page
452 node.lock_for_editing!(@user1)
453 node.autosave!({ :title => "second version" }, @user1)
454 node.save_draft!(@user1)
455 node.autosave!({ :title => "third version, still typing" }, @user1)
456
457 node.revert!(@user1)
458 node.reload
459
460 assert_nil node.autosave
461 assert_not_nil node.draft
462 assert_equal "second version", node.draft.title
463 assert_equal @user1, node.lock_owner
464 end
465
466 test "revert! raises LockedByAnotherUser for a non-owner" do
467 node = create_node_with_published_page
468 node.lock_for_editing!(@user1)
469
470 assert_raise(LockedByAnotherUser) { node.revert!(@user2) }
471 assert_equal @user1, node.reload.lock_owner
472 end
283 473
284 def create_revisions node, count 474 def create_revisions node, count
285 count.times do 475 count.times do
@@ -287,4 +477,173 @@ class NodeTest < ActiveSupport::TestCase
287 node.publish_draft! 477 node.publish_draft!
288 end 478 end
289 end 479 end
480
481 test "available_layer_pairs matches the six-state table" do
482 node = Node.root.children.create!(:slug => "layer_pairs_test")
483 user = @user1 || User.find_by_login("aaron")
484
485 assert_equal [[:draft, :autosave]], (node.lock_for_editing!(user); node.autosave!({title: "v1"}, user); node.available_layer_pairs) # state F
486
487 node.save_draft!(user)
488 node.publish_draft!
489 assert_equal [], node.available_layer_pairs # state A
490
491 node.lock_for_editing!(user)
492 node.autosave!({title: "v2"}, user)
493 assert_equal [[:head, :autosave]], node.available_layer_pairs # state B
494
495 node.save_draft!(user)
496 assert_equal [[:head, :draft]], node.available_layer_pairs # state C
497
498 node.lock_for_editing!(user)
499 node.autosave!({title: "v3"}, user)
500 assert_equal [[:head, :draft], [:draft, :autosave]], node.available_layer_pairs # state D
501 end
502
503 test "publishing a staged move under one's own descendant is rejected, not allowed to crash" do
504 a = Node.root.children.create!(:slug => "cycle_guard_a")
505 b = a.children.create!(:slug => "cycle_guard_b")
506
507 a.staged_parent_id = b.id
508
509 assert_raises(ActiveRecord::RecordInvalid) { a.publish_draft! }
510
511 a.reload
512 assert_equal Node.root.id, a.parent_id
513 end
514
515 test "editor_search matches a partial substring, not just a whole word" do
516 node = Node.root.children.create!(:slug => "editor_search_substring_test")
517 node.find_or_create_draft(@user1)
518 node.draft.update(:title => "Biometrics Conference")
519 node.publish_draft!
520
521 assert_includes Node.editor_search("bio"), node
522 assert_includes Node.editor_search("Conf"), node
523 end
524
525 test "editor_search returns an empty relation for a blank term, not every node" do
526 assert_equal 0, Node.editor_search("").count
527 assert_equal 0, Node.editor_search(nil).count
528 assert_equal 0, Node.editor_search(" ").count
529 end
530
531 test "editor_search requires every word to match, but each word can match a different field" do
532 node = Node.root.children.create!(:slug => "editor_search_multiword_test")
533 node.find_or_create_draft(@user1)
534 node.draft.update(:title => "Backspace e.V. Bamberg", :abstract => "Spiegelgraben 41, 96052 Bamberg")
535 node.publish_draft!
536
537 assert_includes Node.editor_search("Backspace Spiegelgraben"), node
538 assert_equal 0, Node.editor_search("Backspace Nonexistentstreet").count
539 end
540
541 test "drafts_and_autosaves without a user sorts by recency only" do
542 older = Node.root.children.create!(:slug => "drafts_order_older")
543 older.find_or_create_draft(@user1)
544 newer = Node.root.children.create!(:slug => "drafts_order_newer")
545 newer.find_or_create_draft(@user1)
546
547 result = Node.drafts_and_autosaves.to_a
548 assert result.index(newer) < result.index(older)
549 end
550
551 test "drafts_and_autosaves with a user puts their own locked nodes first, regardless of recency" do
552 mine = Node.root.children.create!(:slug => "drafts_order_mine")
553 mine.lock_for_editing!(@user1)
554 mine.autosave!({:title => "mine"}, @user1)
555
556 someone_elses_newer = Node.root.children.create!(:slug => "drafts_order_theirs")
557 other_user = User.find_by_login("quentin")
558 someone_elses_newer.lock_for_editing!(other_user)
559 someone_elses_newer.autosave!({:title => "theirs"}, other_user)
560
561 result = Node.drafts_and_autosaves(current_user_id: @user1.id).to_a
562 assert result.index(mine) < result.index(someone_elses_newer)
563 end
564
565 test "recently_changed includes a node whose head was recently published" do
566 node = Node.root.children.create!(:slug => "recent_changed_published")
567 node.find_or_create_draft(@user1)
568 node.autosave!({:title => "v1"}, @user1)
569 node.save_draft!(@user1)
570 node.publish_draft!
571
572 assert_includes Node.recently_changed, node
573 end
574
575 test "recently_changed excludes a node only touched by locking or unlocking after an old publish" do
576 node = Node.root.children.create!(:slug => "recent_changed_lock_only")
577 node.find_or_create_draft(@user1)
578 node.autosave!({:title => "v1"}, @user1)
579 node.save_draft!(@user1)
580 node.publish_draft!
581 node.head.update_column(:updated_at, 20.days.ago)
582
583 node.lock_for_editing!(@user1)
584 node.unlock!
585
586 assert_not_includes Node.recently_changed, node
587 end
588
589 test "autosave! carries over the current related assets to the newly created autosave row" do
590 node = Node.root.children.create!(:slug => "autosave_asset_carryover_test")
591 user = User.find_by_login("quentin")
592 asset = Asset.create!(:name => "carryover-photo", :upload_content_type => "image/png")
593 node.draft.assets << asset
594
595 node.lock_for_editing!(user)
596 node.autosave!({:title => "v1"}, user)
597
598 assert_includes node.autosave.reload.assets, asset
599 end
600
601 test "autosave! does not reset assets already attached directly to an existing autosave" do
602 node = Node.root.children.create!(:slug => "autosave_asset_no_reset_test")
603 user = User.find_by_login("quentin")
604 original = Asset.create!(:name => "original-photo", :upload_content_type => "image/png")
605 extra = Asset.create!(:name => "extra-photo", :upload_content_type => "image/png")
606 node.draft.assets << original
607
608 node.lock_for_editing!(user)
609 node.autosave!({:title => "v1"}, user)
610 node.autosave.assets << extra
611
612 node.autosave!({:title => "v2"}, user)
613
614 assert_includes node.autosave.reload.assets, original
615 assert_includes node.autosave.reload.assets, extra
616 end
617
618 test "autosave! carries over other-locale translations to the newly created autosave row" do
619 node = Node.root.children.create!(:slug => "autosave_translation_carryover_test")
620 user = User.find_by_login("quentin")
621
622 Globalize.with_locale(:en) { node.draft.update!(:title => "English title") }
623
624 node.lock_for_editing!(user)
625 Globalize.with_locale(:de) { node.autosave!({:title => "German edit"}, user) }
626
627 autosave = node.autosave.reload
628 assert_includes autosave.translated_locales, :en
629 assert_equal "English title", Globalize.with_locale(:en) { autosave.title }
630 assert_equal "German edit", Globalize.with_locale(:de) { autosave.title }
631 end
632
633 test "autosave! does not reset other-locale translations already attached directly to an existing autosave" do
634 node = Node.root.children.create!(:slug => "autosave_translation_no_reset_test")
635 user = User.find_by_login("quentin")
636
637 Globalize.with_locale(:en) { node.draft.update!(:title => "Original English title") }
638
639 node.lock_for_editing!(user)
640 Globalize.with_locale(:de) { node.autosave!({:title => "v1"}, user) }
641 Globalize.with_locale(:en) { node.autosave.update!(:title => "Edited directly on autosave") }
642
643 Globalize.with_locale(:de) { node.autosave!({:title => "v2"}, user) }
644
645 autosave = node.autosave.reload
646 assert_equal "Edited directly on autosave", Globalize.with_locale(:en) { autosave.title }
647 assert_equal "v2", Globalize.with_locale(:de) { autosave.title }
648 end
290end 649end
diff --git a/test/models/page_test.rb b/test/models/page_test.rb
index afba8b5..24bc7c5 100644
--- a/test/models/page_test.rb
+++ b/test/models/page_test.rb
@@ -142,5 +142,232 @@ class PageTest < ActiveSupport::TestCase
142 update = updates2009.children.create!( :slug => "my-first-update" ) 142 update = updates2009.children.create!( :slug => "my-first-update" )
143 assert_equal "update", update.draft.template_name 143 assert_equal "update", update.draft.template_name
144 end 144 end
145 145
146 test "a page scheduled for future publication is not yet public even after being published" do
147 node = Node.root.children.create!(slug: "preview-scheduled-test")
148 draft = node.find_or_create_draft(@user1)
149 draft.title = "Scheduled test"
150 draft.published_at = 1.day.from_now
151 draft.save!
152 token = draft.ensure_preview_token!
153
154 node.publish_draft!
155 page = Page.find_by(preview_token: token)
156
157 assert_equal page.id, page.node.head_id
158 assert_not page.public?
159 end
160
161 test "a superseded page is no longer the head, even though it was once published" do
162 node = Node.root.children.create!(slug: "preview-superseded-test")
163 first_draft = node.find_or_create_draft(@user1)
164 first_draft.title = "First version"
165 first_draft.save!
166 first_token = first_draft.ensure_preview_token!
167 node.publish_draft!
168
169 second_draft = node.find_or_create_draft(@user1)
170 second_draft.title = "Second version"
171 second_draft.save!
172 node.publish_draft!
173
174 first_page = Page.find_by(preview_token: first_token)
175
176 assert_not_equal first_page.id, first_page.node.head_id
177 assert first_page.published_at.present?
178 end
179
180 test "clone_attributes_from preserves an unchanged locale's original timestamp" do
181 n = Node.root.children.create!(:slug => "clone_preserve_timestamp_test")
182 source = n.draft
183 Globalize.with_locale(:de) { source.update!(:title => "Deutscher Titel") }
184 Globalize.with_locale(:en) { source.update!(:title => "English Title") }
185
186 target = Page.create!
187 target.clone_attributes_from(source)
188 original_en_updated_at = target.translations.find_by(:locale => :en).updated_at
189
190 Globalize.with_locale(:de) { source.update!(:title => "Deutscher Titel (bearbeitet)") }
191 target.clone_attributes_from(source)
192
193 en_translation = target.translations.find_by(:locale => :en)
194 assert_equal "English Title", en_translation.title
195 assert_equal original_en_updated_at, en_translation.updated_at
196 end
197
198 test "clone_attributes_from gives a genuinely changed locale a fresh timestamp" do
199 n = Node.root.children.create!(:slug => "clone_fresh_timestamp_test")
200 source = n.draft
201 Globalize.with_locale(:de) { source.update!(:title => "Erste Version") }
202
203 target = Page.create!
204 target.clone_attributes_from(source)
205 original_de_updated_at = target.translations.find_by(:locale => :de).updated_at
206
207 Globalize.with_locale(:de) { source.update!(:title => "Zweite Version") }
208 target.clone_attributes_from(source)
209
210 de_translation = target.translations.find_by(:locale => :de)
211 assert_equal "Zweite Version", de_translation.title
212 assert_operator de_translation.updated_at, :>, original_de_updated_at
213 end
214
215 test "clone_attributes_from removes a locale no longer present in the source" do
216 n = Node.root.children.create!(:slug => "clone_removed_locale_test")
217 source = n.draft
218 Globalize.with_locale(:en) { source.update!(:title => "English Title") }
219
220 target = Page.create!
221 target.clone_attributes_from(source)
222 assert_includes target.translations.map(&:locale), :en
223
224 source.translations.where(:locale => :en).delete_all
225 target.clone_attributes_from(source)
226
227 assert_not_includes target.reload.translations.map(&:locale), :en
228 end
229
230 def test_diff_against_inline_keeps_tags_and_marks_only_the_changed_word
231 n = Node.root.children.create! :slug => "diff_against_test"
232 d = n.find_or_create_draft @user1
233 d.title = "Old heading"
234 d.save!
235 n.publish_draft!
236
237 d2 = n.find_or_create_draft @user1
238 d2.title = "New heading"
239 d2.save!
240
241 diff = d2.diff_against(n.head)
242
243 assert_match "<del>Old</del>", diff[:title]
244 assert_match "<ins>New</ins>", diff[:title]
245 end
246
247 def test_diff_against_side_by_side_returns_two_annotated_strings
248 n = Node.root.children.create! :slug => "diff_against_sbs_test"
249 d = n.find_or_create_draft @user1
250 d.title = "Old heading"
251 d.save!
252 n.publish_draft!
253
254 d2 = n.find_or_create_draft @user1
255 d2.title = "New heading"
256 d2.save!
257
258 old_html, new_html = d2.diff_against(n.head, view: :side_by_side)[:title]
259
260 assert_match "<del>Old</del>", old_html
261 assert_match "<ins>New</ins>", new_html
262 end
263
264 test "diff_against handles an inserted paragraph split without corrupting the document" do
265 n = Node.root.children.create! :slug => "paragraph_split_test"
266 d = n.find_or_create_draft @user1
267 d.body = "<p>Der Vortragsraum ist ab 19 Uhr geöffnet, der Zugang erfolgt über den Hinterhof.</p>"
268 d.save!
269 n.publish_draft!
270
271 d2 = n.find_or_create_draft @user1
272 d2.body = "<p>Der Vortragsraum ist ab 19 Uhr geöffnet,</p>\n<p>der Zugang erfolgt über den Hinterhof.</p>"
273 d2.save!
274
275 diff = d2.diff_against(n.head)
276 fragment = Nokogiri::HTML::DocumentFragment.parse(diff[:body])
277
278 assert_equal 2, fragment.css('ins.diff_structural').length
279 assert_match "der Zugang erfolgt über den Hinterhof.", fragment.text
280 end
281
282 test "diff_against reports tag and template changes" do
283 n = Node.root.children.create! :slug => "field_diff_test"
284 d = n.find_or_create_draft @user1
285 d.tag_list = "update"
286 d.template_name = "standard_template"
287 d.save!
288 n.publish_draft!
289
290 d2 = n.find_or_create_draft @user1
291 d2.tag_list = "update, pressemitteilung"
292 d2.template_name = "title_only"
293 d2.save!
294
295 diff = d2.diff_against(n.head)
296
297 assert_equal ["pressemitteilung"], diff[:tags][:added]
298 assert_equal [], diff[:tags][:removed]
299 assert diff[:template_name][:changed]
300 assert_equal "standard_template", diff[:template_name][:from]
301 assert_equal "title_only", diff[:template_name][:to]
302 end
303
304 test "diff_against reports added and removed assets by filename" do
305 n = Node.root.children.create! :slug => "asset_diff_test"
306 d = n.find_or_create_draft @user1
307 d.save!
308 n.publish_draft!
309
310 kept_asset = Asset.create!(:upload_file_name => "kept.png", :upload_content_type => "image/png", :upload_file_size => 1)
311 removed_asset = Asset.create!(:upload_file_name => "removed.pdf", :upload_content_type => "application/pdf", :upload_file_size => 1)
312 n.head.update_assets([kept_asset.id, removed_asset.id])
313
314 d2 = n.find_or_create_draft @user1
315 added_asset = Asset.create!(:upload_file_name => "added.png", :upload_content_type => "image/png", :upload_file_size => 1)
316 d2.update_assets([kept_asset.id, added_asset.id])
317 d2.save!
318
319 diff = d2.diff_against(n.head)
320
321 assert_equal [added_asset], diff[:assets][:added]
322 assert_equal [removed_asset], diff[:assets][:removed]
323 end
324
325 test "diff_against with an explicit locale compares that locale's own translation on each side" do
326 n = Node.root.children.create!(:slug => "diff_locale_test")
327 d = n.find_or_create_draft(@user1)
328 Globalize.with_locale(:en) { d.update!(:title => "Old English") }
329 d.save!
330 n.publish_draft!
331
332 d2 = n.find_or_create_draft(@user1)
333 Globalize.with_locale(:en) { d2.update!(:title => "New English") }
334 d2.save!
335
336 diff = d2.diff_against(n.head, :locale => :en)
337
338 assert_match "<del>Old</del>", diff[:title]
339 assert_match "<ins>New</ins>", diff[:title]
340 end
341
342 test "diff_against with an explicit locale ignores content in other locales entirely" do
343 n = Node.root.children.create!(:slug => "diff_locale_isolation_test")
344 d = n.find_or_create_draft(@user1)
345 d.save!
346 n.publish_draft!
347
348 d2 = n.find_or_create_draft(@user1)
349 Globalize.with_locale(:de) { d2.update!(:title => "Nur Deutsch geändert") }
350 d2.save!
351
352 diff = d2.diff_against(n.head, :locale => :en)
353
354 assert_no_match(/Deutsch/, diff[:title])
355 end
356
357 test "locale_diff_summary flags a locale that only exists on one side as changed" do
358 n = Node.root.children.create!(:slug => "diff_locale_summary_test")
359 d = n.find_or_create_draft(@user1)
360 d.save!
361 n.publish_draft!
362
363 d2 = n.find_or_create_draft(@user1)
364 Globalize.with_locale(:en) { d2.update!(:title => "New English translation") }
365 d2.save!
366
367 summary = d2.locale_diff_summary(n.head)
368 en_entry = summary.find { |s| s[:locale] == :en }
369
370 assert en_entry[:changed]
371 refute en_entry[:exists_there]
372 end
146end 373end