milestone_spec.rb 7.28 KB
Newer Older
1 2
require 'spec_helper'

Douwe Maan's avatar
Douwe Maan committed
3
describe Milestone, models: true do
4
  describe "Associations" do
5 6
    it { is_expected.to belong_to(:project) }
    it { is_expected.to have_many(:issues) }
7 8 9
  end

  describe "Validation" do
10 11 12 13
    before do
      allow(subject).to receive(:set_iid).and_return(false)
    end

14 15
    it { is_expected.to validate_presence_of(:title) }
    it { is_expected.to validate_presence_of(:project) }
16 17
  end

18 19
  let(:milestone) { create(:milestone) }
  let(:issue) { create(:issue) }
20
  let(:user) { create(:user) }
21

22
  describe "#title" do
23
    let(:milestone) { create(:milestone, title: "<b>foo & bar -> 2.2</b>") }
24 25

    it "sanitizes title" do
26
      expect(milestone.title).to eq("foo & bar -> 2.2")
27 28 29
    end
  end

30
  describe "unique milestone title per project" do
31
    it "does not accept the same title in a project twice" do
32 33 34 35
      new_milestone = Milestone.new(project: milestone.project, title: milestone.title)
      expect(new_milestone).not_to be_valid
    end

36
    it "accepts the same title in another project" do
37 38 39 40 41 42 43
      project = build(:project)
      new_milestone = Milestone.new(project: project, title: milestone.title)

      expect(new_milestone).to be_valid
    end
  end

44
  describe "#percent_complete" do
45
    it "does not count open issues" do
46
      milestone.issues << issue
47
      expect(milestone.percent_complete(user)).to eq(0)
48 49
    end

50
    it "counts closed issues" do
Andrew8xx8's avatar
Andrew8xx8 committed
51
      issue.close
52
      milestone.issues << issue
53
      expect(milestone.percent_complete(user)).to eq(100)
54
    end
55

56
    it "recovers from dividing by zero" do
57
      expect(milestone.percent_complete(user)).to eq(0)
58 59 60
    end
  end

61
  describe "#expires_at" do
62
    it "is nil when due_date is unset" do
63
      milestone.update_attributes(due_date: nil)
64
      expect(milestone.expires_at).to be_nil
65 66
    end

67
    it "is not nil when due_date is set" do
68
      milestone.update_attributes(due_date: Date.tomorrow)
69
      expect(milestone.expires_at).to be_present
70
    end
71
  end
72

73
  describe '#expired?' do
74 75
    context "expired" do
      before do
76
        allow(milestone).to receive(:due_date).and_return(Date.today.prev_year)
77 78
      end

79
      it { expect(milestone.expired?).to be_truthy }
80 81 82 83
    end

    context "not expired" do
      before do
84
        allow(milestone).to receive(:due_date).and_return(Date.today.next_year)
85 86
      end

87
      it { expect(milestone.expired?).to be_falsey }
88 89 90
    end
  end

91
  describe '#percent_complete' do
92
    before do
93
      allow(milestone).to receive_messages(
94 95 96 97 98
        closed_items_count: 3,
        total_items_count: 4
      )
    end

99
    it { expect(milestone.percent_complete(user)).to eq(75) }
100 101 102 103 104
  end

  describe :items_count do
    before do
      milestone.issues << create(:issue)
Andrew8xx8's avatar
Andrew8xx8 committed
105
      milestone.issues << create(:closed_issue)
106 107 108
      milestone.merge_requests << create(:merge_request)
    end

109 110 111
    it { expect(milestone.closed_items_count(user)).to eq(1) }
    it { expect(milestone.total_items_count(user)).to eq(3) }
    it { expect(milestone.is_empty?(user)).to be_falsey }
112 113
  end

114
  describe '#can_be_closed?' do
115
    it { expect(milestone.can_be_closed?).to be_truthy }
116
  end
117

118
  describe '#total_items_count' do
119
    before do
120 121
      create :closed_issue, milestone: milestone
      create :merge_request, milestone: milestone
122
    end
123

124
    it 'returns total count of issues and merge requests assigned to milestone' do
125
      expect(milestone.total_items_count(user)).to eq 2
126 127 128
    end
  end

129
  describe '#can_be_closed?' do
130
    before do
131
      milestone = create :milestone
132 133
      create :closed_issue, milestone: milestone

134
      create :issue
135
    end
136

137
    it 'returns true if milestone active and all nested issues closed' do
138
      expect(milestone.can_be_closed?).to be_truthy
139 140
    end

141
    it 'returns false if milestone active and not all nested issues closed' do
142
      issue.milestone = milestone
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
143
      issue.save
144

145
      expect(milestone.can_be_closed?).to be_falsey
146 147 148
    end
  end

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
  describe '#sort_issues' do
    let(:milestone) { create(:milestone) }

    let(:issue1) { create(:issue, milestone: milestone, position: 1) }
    let(:issue2) { create(:issue, milestone: milestone, position: 2) }
    let(:issue3) { create(:issue, milestone: milestone, position: 3) }
    let(:issue4) { create(:issue, position: 42) }

    it 'sorts the given issues' do
      milestone.sort_issues([issue3.id, issue2.id, issue1.id])

      issue1.reload
      issue2.reload
      issue3.reload

      expect(issue1.position).to eq(3)
      expect(issue2.position).to eq(2)
      expect(issue3.position).to eq(1)
    end

    it 'ignores issues not part of the milestone' do
      milestone.sort_issues([issue3.id, issue2.id, issue1.id, issue4.id])

      issue4.reload

      expect(issue4.position).to eq(42)
    end
  end
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206

  describe '.search' do
    let(:milestone) { create(:milestone, title: 'foo', description: 'bar') }

    it 'returns milestones with a matching title' do
      expect(described_class.search(milestone.title)).to eq([milestone])
    end

    it 'returns milestones with a partially matching title' do
      expect(described_class.search(milestone.title[0..2])).to eq([milestone])
    end

    it 'returns milestones with a matching title regardless of the casing' do
      expect(described_class.search(milestone.title.upcase)).to eq([milestone])
    end

    it 'returns milestones with a matching description' do
      expect(described_class.search(milestone.description)).to eq([milestone])
    end

    it 'returns milestones with a partially matching description' do
      expect(described_class.search(milestone.description[0..2])).
        to eq([milestone])
    end

    it 'returns milestones with a matching description regardless of the casing' do
      expect(described_class.search(milestone.description.upcase)).
        to eq([milestone])
    end
  end
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223

  describe '.upcoming_ids_by_projects' do
    let(:project_1) { create(:empty_project) }
    let(:project_2) { create(:empty_project) }
    let(:project_3) { create(:empty_project) }
    let(:projects) { [project_1, project_2, project_3] }

    let!(:past_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now - 1.day) }
    let!(:current_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now + 1.day) }
    let!(:future_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now + 2.days) }

    let!(:past_milestone_project_2) { create(:milestone, project: project_2, due_date: Time.now - 1.day) }
    let!(:closed_milestone_project_2) { create(:milestone, :closed, project: project_2, due_date: Time.now + 1.day) }
    let!(:current_milestone_project_2) { create(:milestone, project: project_2, due_date: Time.now + 2.days) }

    let!(:past_milestone_project_3) { create(:milestone, project: project_3, due_date: Time.now - 1.day) }

224 225 226
    # The call to `#try` is because this returns a relation with a Postgres DB,
    # and an array of IDs with a MySQL DB.
    let(:milestone_ids) { Milestone.upcoming_ids_by_projects(projects).map { |id| id.try(:id) || id } }
227 228 229 230 231 232 233 234 235 236 237 238 239

    it 'returns the next upcoming open milestone ID for each project' do
      expect(milestone_ids).to contain_exactly(current_milestone_project_1.id, current_milestone_project_2.id)
    end

    context 'when the projects have no open upcoming milestones' do
      let(:projects) { [project_3] }

      it 'returns no results' do
        expect(milestone_ids).to be_empty
      end
    end
  end
240
end