BigW Consortium Gitlab

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

Douwe Maan committed
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) }
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 28 29
  let(:project) { create(:project, :public) }
  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 48 49 50 51 52 53
      project = build(:project)
      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
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

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 112 113
  end

  describe :items_count do
    before do
114 115
      milestone.issues << create(:issue, project: project)
      milestone.issues << create(:closed_issue, project: project)
116 117 118
      milestone.merge_requests << create(:merge_request)
    end

119 120 121
    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 }
122 123
  end

124
  describe '#can_be_closed?' do
125
    it { expect(milestone.can_be_closed?).to be_truthy }
126
  end
127

128
  describe '#total_items_count' do
129
    before do
130
      create :closed_issue, milestone: milestone, project: project
131
      create :merge_request, milestone: milestone
132
    end
133

134
    it 'returns total count of issues and merge requests assigned to milestone' do
135
      expect(milestone.total_items_count(user)).to eq 2
136 137 138
    end
  end

139
  describe '#can_be_closed?' do
140
    before do
141
      milestone = create :milestone
142 143
      create :closed_issue, milestone: milestone

144
      create :issue
145
    end
146

147
    it 'returns true if milestone active and all nested issues closed' do
148
      expect(milestone.can_be_closed?).to be_truthy
149 150
    end

151
    it 'returns false if milestone active and not all nested issues closed' do
152
      issue.milestone = milestone
153
      issue.save
154

155
      expect(milestone.can_be_closed?).to be_falsey
156 157 158
    end
  end

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
  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
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216

  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
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233

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

234 235 236
    # 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 } }
237 238 239 240 241 242 243 244 245 246 247 248 249

    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
250 251 252 253 254 255 256 257 258 259 260 261 262 263

  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
      another_project = build(:project, name: 'another-project', namespace: project.namespace)
      expect(milestone.to_reference(another_project)).to eq "sample-project%1"
    end
  end
264
end