milestone_spec.rb 7.8 KB
Newer Older
D
Dmitriy Zaporozhets 已提交
1 2
require 'spec_helper'

D
Douwe Maan 已提交
3
describe Milestone, models: true do
4
  describe "Validation" do
5 6 7 8
    before do
      allow(subject).to receive(:set_iid).and_return(false)
    end

9 10
    it { is_expected.to validate_presence_of(:title) }
    it { is_expected.to validate_presence_of(:project) }
V
Valery Sizov 已提交
11 12 13 14 15 16 17 18 19 20 21 22 23 24

    describe 'start_date' do
      it 'adds an error when start_date is greated then due_date' do
        milestone = build(:milestone, start_date: Date.tomorrow, due_date: Date.yesterday)

        expect(milestone).not_to be_valid
        expect(milestone.errors[:start_date]).to include("Can't be greater than due date")
      end
    end
  end

  describe "Associations" do
    it { is_expected.to belong_to(:project) }
    it { is_expected.to have_many(:issues) }
25 26
  end

27
  let(:project) { create(:empty_project, :public) }
28 29
  let(:milestone) { create(:milestone, project: project) }
  let(:issue) { create(:issue, project: project) }
30
  let(:user) { create(:user) }
31

32
  describe "#title" do
33
    let(:milestone) { create(:milestone, title: "<b>foo & bar -> 2.2</b>") }
34 35

    it "sanitizes title" do
36
      expect(milestone.title).to eq("foo & bar -> 2.2")
37 38 39
    end
  end

40
  describe "unique milestone title per project" do
41
    it "does not accept the same title in a project twice" do
42 43 44 45
      new_milestone = Milestone.new(project: milestone.project, title: milestone.title)
      expect(new_milestone).not_to be_valid
    end

46
    it "accepts the same title in another project" do
47
      project = build(:empty_project)
48 49 50 51 52 53
      new_milestone = Milestone.new(project: project, title: milestone.title)

      expect(new_milestone).to be_valid
    end
  end

54
  describe "#percent_complete" do
55
    it "does not count open issues" do
56
      milestone.issues << issue
57
      expect(milestone.percent_complete(user)).to eq(0)
58 59
    end

60
    it "counts closed issues" do
A
Andrew8xx8 已提交
61
      issue.close
62
      milestone.issues << issue
63
      expect(milestone.percent_complete(user)).to eq(100)
64
    end
65

66
    it "recovers from dividing by zero" do
67
      expect(milestone.percent_complete(user)).to eq(0)
68 69 70
    end
  end

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

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

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

85
      it { expect(milestone.expired?).to be_falsey }
86 87 88
    end
  end

V
Valery Sizov 已提交
89 90 91 92 93 94 95 96 97 98 99 100
  describe '#upcoming?' do
    it 'returns true' do
      milestone = build(:milestone, start_date: Time.now + 1.month)
      expect(milestone.upcoming?).to be_truthy
    end

    it 'returns false' do
      milestone = build(:milestone, start_date: Date.today.prev_year)
      expect(milestone.upcoming?).to be_falsey
    end
  end

101
  describe '#percent_complete' do
102
    before do
103
      allow(milestone).to receive_messages(
104 105 106 107 108
        closed_items_count: 3,
        total_items_count: 4
      )
    end

109
    it { expect(milestone.percent_complete(user)).to eq(75) }
110 111
  end

112
  describe '#can_be_closed?' do
113
    it { expect(milestone.can_be_closed?).to be_truthy }
114
  end
A
Andrew8xx8 已提交
115

116
  describe '#total_items_count' do
117
    before do
118
      create :closed_issue, milestone: milestone, project: project
119
      create :merge_request, milestone: milestone
120
    end
A
Andrew8xx8 已提交
121

122
    it 'returns total count of issues and merge requests assigned to milestone' do
123
      expect(milestone.total_items_count(user)).to eq 2
A
Andrew8xx8 已提交
124 125 126
    end
  end

127
  describe '#can_be_closed?' do
128
    before do
A
Andrew8xx8 已提交
129
      milestone = create :milestone
130 131
      create :closed_issue, milestone: milestone

132
      create :issue
133
    end
A
Andrew8xx8 已提交
134

135
    it 'returns true if milestone active and all nested issues closed' do
136
      expect(milestone.can_be_closed?).to be_truthy
A
Andrew8xx8 已提交
137 138
    end

139
    it 'returns false if milestone active and not all nested issues closed' do
140
      issue.milestone = milestone
A
Andrey Kumanyaev 已提交
141
      issue.save
A
Andrew8xx8 已提交
142

143
      expect(milestone.can_be_closed?).to be_falsey
A
Andrew8xx8 已提交
144 145 146
    end
  end

147 148 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
  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
175 176 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

  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
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221

  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) }

S
Sean McGivern 已提交
222 223 224
    # 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 } }
225 226 227 228 229 230 231 232 233 234 235 236 237

    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
238 239 240 241 242 243 244 245 246 247

  describe '#to_reference' do
    let(:project) { build(:empty_project, name: 'sample-project') }
    let(:milestone) { build(:milestone, iid: 1, project: project) }

    it 'returns a String reference to the object' do
      expect(milestone.to_reference).to eq "%1"
    end

    it 'supports a cross-project reference' do
248
      another_project = build(:empty_project, name: 'another-project', namespace: project.namespace)
249 250 251
      expect(milestone.to_reference(another_project)).to eq "sample-project%1"
    end
  end
D
Dmitriy Zaporozhets 已提交
252
end