1# frozen_string_literal: true 2 3require 'spec_helper' 4 5RSpec.describe Todo do 6 let(:issue) { create(:issue) } 7 8 describe 'relationships' do 9 it { is_expected.to belong_to(:author).class_name("User") } 10 it { is_expected.to belong_to(:note) } 11 it { is_expected.to belong_to(:project) } 12 it { is_expected.to belong_to(:group) } 13 it { is_expected.to belong_to(:target).touch(true) } 14 it { is_expected.to belong_to(:user) } 15 end 16 17 describe 'respond to' do 18 it { is_expected.to respond_to(:author_name) } 19 it { is_expected.to respond_to(:author_email) } 20 end 21 22 describe 'validations' do 23 it { is_expected.to validate_presence_of(:action) } 24 it { is_expected.to validate_presence_of(:target_type) } 25 it { is_expected.to validate_presence_of(:user) } 26 it { is_expected.to validate_presence_of(:author) } 27 28 context 'for commits' do 29 subject { described_class.new(target_type: 'Commit') } 30 31 it { is_expected.to validate_presence_of(:commit_id) } 32 it { is_expected.not_to validate_presence_of(:target_id) } 33 end 34 35 context 'for issuables' do 36 subject { described_class.new(target: issue) } 37 38 it { is_expected.to validate_presence_of(:target_id) } 39 it { is_expected.not_to validate_presence_of(:commit_id) } 40 end 41 end 42 43 describe '#body' do 44 before do 45 subject.target = build(:issue, title: 'Bugfix') 46 end 47 48 it 'returns target title when note is blank' do 49 subject.note = nil 50 51 expect(subject.body).to eq 'Bugfix' 52 end 53 54 it 'returns note when note is present' do 55 subject.note = build(:note, note: 'quick fix') 56 57 expect(subject.body).to eq 'quick fix' 58 end 59 end 60 61 describe '#done' do 62 it 'changes state to done' do 63 todo = create(:todo, state: :pending) 64 65 expect { todo.done }.to change(todo, :state).from('pending').to('done') 66 end 67 68 it 'does not raise error when is already done' do 69 todo = create(:todo, state: :done) 70 71 expect { todo.done }.not_to raise_error 72 end 73 end 74 75 describe '#for_commit?' do 76 it 'returns true when target is a commit' do 77 subject.target_type = 'Commit' 78 79 expect(subject.for_commit?).to eq true 80 end 81 82 it 'returns false when target is an issuable' do 83 subject.target_type = 'Issue' 84 85 expect(subject.for_commit?).to eq false 86 end 87 end 88 89 describe '#for_design?' do 90 it 'returns true when target is a Design' do 91 subject.target_type = 'DesignManagement::Design' 92 93 expect(subject.for_design?).to eq(true) 94 end 95 96 it 'returns false when target is not a Design' do 97 subject.target_type = 'Issue' 98 99 expect(subject.for_design?).to eq(false) 100 end 101 end 102 103 describe '#for_alert?' do 104 it 'returns true when target is a Alert' do 105 subject.target_type = 'AlertManagement::Alert' 106 107 expect(subject.for_alert?).to eq(true) 108 end 109 110 it 'returns false when target is not a Alert' do 111 subject.target_type = 'Issue' 112 113 expect(subject.for_alert?).to eq(false) 114 end 115 end 116 117 describe '#target' do 118 context 'for commits' do 119 let(:project) { create(:project, :repository) } 120 let(:commit) { project.commit } 121 122 it 'returns an instance of Commit when exists' do 123 subject.project = project 124 subject.target_type = 'Commit' 125 subject.commit_id = commit.id 126 127 expect(subject.target).to be_a(Commit) 128 expect(subject.target).to eq commit 129 end 130 131 it 'returns nil when does not exists' do 132 subject.project = project 133 subject.target_type = 'Commit' 134 subject.commit_id = 'xxxx' 135 136 expect(subject.target).to be_nil 137 end 138 end 139 140 it 'returns the issuable for issuables' do 141 subject.target_id = issue.id 142 subject.target_type = issue.class.name 143 144 expect(subject.target).to eq issue 145 end 146 end 147 148 describe '#target_reference' do 149 it 'returns commit full reference with short id' do 150 project = create(:project, :repository) 151 commit = project.commit 152 153 subject.project = project 154 subject.target_type = 'Commit' 155 subject.commit_id = commit.id 156 157 expect(subject.target_reference).to eq commit.reference_link_text(full: false) 158 end 159 160 it 'returns full reference for issuables' do 161 subject.target = issue 162 163 expect(subject.target_reference).to eq issue.to_reference(full: false) 164 end 165 end 166 167 describe '#self_added?' do 168 let(:user_1) { build(:user) } 169 170 before do 171 subject.user = user_1 172 end 173 174 it 'is true when the user is the author' do 175 subject.author = user_1 176 177 expect(subject).to be_self_added 178 end 179 180 it 'is false when the user is not the author' do 181 subject.author = build(:user) 182 183 expect(subject).not_to be_self_added 184 end 185 end 186 187 describe '#done?' do 188 let_it_be(:todo1) { create(:todo, state: :pending) } 189 let_it_be(:todo2) { create(:todo, state: :done) } 190 191 it 'returns true for todos with done state' do 192 expect(todo2.done?).to be_truthy 193 end 194 195 it 'returns false for todos with state pending' do 196 expect(todo1.done?).to be_falsey 197 end 198 end 199 200 describe '#self_assigned?' do 201 let(:user_1) { build(:user) } 202 203 context 'when self_added' do 204 before do 205 subject.user = user_1 206 subject.author = user_1 207 end 208 209 it 'returns true for ASSIGNED' do 210 subject.action = Todo::ASSIGNED 211 212 expect(subject).to be_self_assigned 213 end 214 215 it 'returns true for REVIEW_REQUESTED' do 216 subject.action = Todo::REVIEW_REQUESTED 217 218 expect(subject).to be_self_assigned 219 end 220 221 it 'returns false for other action' do 222 subject.action = Todo::MENTIONED 223 224 expect(subject).not_to be_self_assigned 225 end 226 end 227 228 context 'when todo is not self_added' do 229 before do 230 subject.user = user_1 231 subject.author = build(:user) 232 end 233 234 it 'returns false' do 235 subject.action = Todo::ASSIGNED 236 237 expect(subject).not_to be_self_assigned 238 end 239 end 240 end 241 242 describe '.for_action' do 243 it 'returns the todos for a given action' do 244 create(:todo, action: Todo::MENTIONED) 245 246 todo = create(:todo, action: Todo::ASSIGNED) 247 248 expect(described_class.for_action(Todo::ASSIGNED)).to eq([todo]) 249 end 250 end 251 252 describe '.for_author' do 253 it 'returns the todos for a given author' do 254 user1 = create(:user) 255 user2 = create(:user) 256 todo = create(:todo, author: user1) 257 258 create(:todo, author: user2) 259 260 expect(described_class.for_author(user1)).to eq([todo]) 261 end 262 end 263 264 describe '.for_project' do 265 it 'returns the todos for a given project' do 266 project1 = create(:project) 267 project2 = create(:project) 268 todo = create(:todo, project: project1) 269 270 create(:todo, project: project2) 271 272 expect(described_class.for_project(project1)).to eq([todo]) 273 end 274 275 it 'returns the todos for many projects' do 276 project1 = create(:project) 277 project2 = create(:project) 278 project3 = create(:project) 279 280 todo1 = create(:todo, project: project1) 281 todo2 = create(:todo, project: project2) 282 create(:todo, project: project3) 283 284 expect(described_class.for_project([project2, project1])).to contain_exactly(todo2, todo1) 285 end 286 end 287 288 describe '.for_undeleted_projects' do 289 let(:project1) { create(:project) } 290 let(:project2) { create(:project) } 291 let(:project3) { create(:project) } 292 293 let!(:todo1) { create(:todo, project: project1) } 294 let!(:todo2) { create(:todo, project: project2) } 295 let!(:todo3) { create(:todo, project: project3) } 296 297 it 'returns the todos for a given project' do 298 expect(described_class.for_undeleted_projects).to contain_exactly(todo1, todo2, todo3) 299 end 300 301 context 'when todo belongs to deleted project' do 302 let(:project2) { create(:project, pending_delete: true) } 303 304 it 'excludes todos of deleted projects' do 305 expect(described_class.for_undeleted_projects).to contain_exactly(todo1, todo3) 306 end 307 end 308 end 309 310 describe '.for_group' do 311 it 'returns the todos for a given group' do 312 group1 = create(:group) 313 group2 = create(:group) 314 todo = create(:todo, group: group1) 315 316 create(:todo, group: group2) 317 318 expect(described_class.for_group(group1)).to eq([todo]) 319 end 320 end 321 322 describe '.for_type' do 323 it 'returns the todos for a given target type' do 324 todo = create(:todo, target: create(:issue)) 325 326 create(:todo, target: create(:merge_request)) 327 328 expect(described_class.for_type(Issue.name)).to eq([todo]) 329 end 330 end 331 332 describe '.for_target' do 333 it 'returns the todos for a given target' do 334 todo = create(:todo, target: create(:issue)) 335 336 create(:todo, target: create(:merge_request)) 337 338 expect(described_class.for_type(Issue.name).for_target(todo.target)) 339 .to contain_exactly(todo) 340 end 341 end 342 343 describe '.for_commit' do 344 it 'returns the todos for a commit ID' do 345 todo = create(:todo, commit_id: '123') 346 347 create(:todo, commit_id: '456') 348 349 expect(described_class.for_commit('123')).to eq([todo]) 350 end 351 end 352 353 describe '.for_group_ids_and_descendants' do 354 it 'returns the todos for a group and its descendants' do 355 parent_group = create(:group) 356 child_group = create(:group, parent: parent_group) 357 358 todo1 = create(:todo, group: parent_group) 359 todo2 = create(:todo, group: child_group) 360 todos = described_class.for_group_ids_and_descendants([parent_group.id]) 361 362 expect(todos).to contain_exactly(todo1, todo2) 363 end 364 end 365 366 describe '.for_user' do 367 it 'returns the expected todos' do 368 user1 = create(:user) 369 user2 = create(:user) 370 371 todo1 = create(:todo, user: user1) 372 todo2 = create(:todo, user: user1) 373 create(:todo, user: user2) 374 375 expect(described_class.for_user(user1)).to contain_exactly(todo1, todo2) 376 end 377 end 378 379 describe '.for_note' do 380 it 'returns todos that belongs to notes' do 381 note_1 = create(:note, noteable: issue, project: issue.project) 382 note_2 = create(:note, noteable: issue, project: issue.project) 383 todo_1 = create(:todo, note: note_1) 384 todo_2 = create(:todo, note: note_2) 385 create(:todo, note: create(:note)) 386 387 expect(described_class.for_note([note_1, note_2])).to contain_exactly(todo_1, todo_2) 388 end 389 end 390 391 describe '.group_by_user_id_and_state' do 392 let_it_be(:user1) { create(:user) } 393 let_it_be(:user2) { create(:user) } 394 395 before do 396 create(:todo, user: user1, state: :pending) 397 create(:todo, user: user1, state: :pending) 398 create(:todo, user: user1, state: :done) 399 create(:todo, user: user2, state: :pending) 400 end 401 402 specify do 403 expect(Todo.count_grouped_by_user_id_and_state).to eq({ [user1.id, "done"] => 1, [user1.id, "pending"] => 2, [user2.id, "pending"] => 1 }) 404 end 405 end 406 407 describe '.any_for_target?' do 408 it 'returns true if there are todos for a given target' do 409 todo = create(:todo) 410 411 expect(described_class.any_for_target?(todo.target)).to eq(true) 412 end 413 414 it 'returns true if there is at least one todo for a given target with state pending' do 415 issue = create(:issue) 416 create(:todo, state: :done, target: issue) 417 create(:todo, state: :pending, target: issue) 418 419 expect(described_class.any_for_target?(issue)).to eq(true) 420 end 421 422 it 'returns false if there are only todos for a given target with state done while searching for pending' do 423 issue = create(:issue) 424 create(:todo, state: :done, target: issue) 425 create(:todo, state: :done, target: issue) 426 427 expect(described_class.any_for_target?(issue, :pending)).to eq(false) 428 end 429 430 it 'returns false if there are no todos for a given target' do 431 issue = create(:issue) 432 433 expect(described_class.any_for_target?(issue)).to eq(false) 434 end 435 end 436 437 describe '.batch_update' do 438 it 'updates the state of todos' do 439 todo = create(:todo, :pending) 440 ids = described_class.batch_update(state: :done) 441 442 todo.reload 443 444 expect(ids).to eq([todo.id]) 445 expect(todo.state).to eq('done') 446 end 447 448 it 'does not update todos that already have the given state' do 449 create(:todo, :pending) 450 451 expect(described_class.batch_update(state: :pending)).to be_empty 452 end 453 454 it 'updates updated_at' do 455 create(:todo, :pending) 456 457 travel_to(1.day.from_now) do 458 expected_update_date = Time.current.utc 459 460 ids = described_class.batch_update(state: :done) 461 462 expect(Todo.where(id: ids).map(&:updated_at)).to all(be_like_time(expected_update_date)) 463 end 464 end 465 end 466 467 describe '.distinct_user_ids' do 468 subject { described_class.distinct_user_ids } 469 470 let_it_be(:user1) { create(:user) } 471 let_it_be(:user2) { create(:user) } 472 let_it_be(:todo) { create(:todo, user: user1) } 473 let_it_be(:todo) { create(:todo, user: user1) } 474 let_it_be(:todo) { create(:todo, user: user2) } 475 476 it { is_expected.to contain_exactly(user1.id, user2.id) } 477 end 478end 479