Read about our upcoming Code of Conduct on this issue

issuable_spec.rb 11.9 KB
Newer Older
1
2
require 'spec_helper'

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
3
describe Issue, "Issuable" do
4
  let(:issue) { create(:issue) }
5
  let(:user) { create(:user) }
6
7

  describe "Associations" do
8
9
10
11
    it { is_expected.to belong_to(:project) }
    it { is_expected.to belong_to(:author) }
    it { is_expected.to belong_to(:assignee) }
    it { is_expected.to have_many(:notes).dependent(:destroy) }
12
    it { is_expected.to have_many(:todos).dependent(:destroy) }
13
14
15
16
17
18
19
20
21
22

    context 'Notes' do
      let!(:note) { create(:note, noteable: issue, project: issue.project) }
      let(:scoped_issue) { Issue.includes(notes: :author).find(issue.id) }

      it 'indicates if the notes have their authors loaded' do
        expect(issue.notes).not_to be_authors_loaded
        expect(scoped_issue.notes).to be_authors_loaded
      end
    end
23
24
  end

25
26
27
28
  describe 'Included modules' do
    it { is_expected.to include_module(Awardable) }
  end

29
  describe "Validation" do
30
31
32
33
    before do
      allow(subject).to receive(:set_iid).and_return(false)
    end

34
35
36
37
    it { is_expected.to validate_presence_of(:project) }
    it { is_expected.to validate_presence_of(:iid) }
    it { is_expected.to validate_presence_of(:author) }
    it { is_expected.to validate_presence_of(:title) }
38
    it { is_expected.to validate_length_of(:title).is_at_most(255) }
39
40
41
  end

  describe "Scope" do
42
43
44
    it { expect(described_class).to respond_to(:opened) }
    it { expect(described_class).to respond_to(:closed) }
    it { expect(described_class).to respond_to(:assigned) }
45
46
47
48
49
  end

  describe ".search" do
    let!(:searchable_issue) { create(:issue, title: "Searchable issue") }

Yorick Peterse's avatar
Yorick Peterse committed
50
    it 'returns notes with a matching title' do
51
52
53
54
55
      expect(described_class.search(searchable_issue.title)).
        to eq([searchable_issue])
    end

    it 'returns notes with a partially matching title' do
56
      expect(described_class.search('able')).to eq([searchable_issue])
57
    end
58
59
60
61
62
63
64
65
66
67
68
69

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

  describe ".full_search" do
    let!(:searchable_issue) do
      create(:issue, title: "Searchable issue", description: 'kittens')
    end

Yorick Peterse's avatar
Yorick Peterse committed
70
    it 'returns notes with a matching title' do
71
72
73
74
75
76
77
78
79
80
81
82
83
      expect(described_class.full_search(searchable_issue.title)).
        to eq([searchable_issue])
    end

    it 'returns notes with a partially matching title' do
      expect(described_class.full_search('able')).to eq([searchable_issue])
    end

    it 'returns notes with a matching title regardless of the casing' do
      expect(described_class.full_search(searchable_issue.title.upcase)).
        to eq([searchable_issue])
    end

Yorick Peterse's avatar
Yorick Peterse committed
84
    it 'returns notes with a matching description' do
85
86
87
88
89
90
91
92
93
94
95
96
97
      expect(described_class.full_search(searchable_issue.description)).
        to eq([searchable_issue])
    end

    it 'returns notes with a partially matching description' do
      expect(described_class.full_search(searchable_issue.description)).
        to eq([searchable_issue])
    end

    it 'returns notes with a matching description regardless of the casing' do
      expect(described_class.full_search(searchable_issue.description.upcase)).
        to eq([searchable_issue])
    end
98
99
  end

100
101
102
103
104
  describe '.to_ability_name' do
    it { expect(Issue.to_ability_name).to eq("issue") }
    it { expect(MergeRequest.to_ability_name).to eq("merge_request") }
  end

105
106
107
  describe "#today?" do
    it "returns true when created today" do
      # Avoid timezone differences and just return exactly what we want
108
109
      allow(Date).to receive(:today).and_return(issue.created_at.to_date)
      expect(issue.today?).to be_truthy
110
111
112
    end

    it "returns false when not created today" do
113
114
      allow(Date).to receive(:today).and_return(Date.yesterday)
      expect(issue.today?).to be_falsey
115
116
117
118
119
    end
  end

  describe "#new?" do
    it "returns true when created today and record hasn't been updated" do
120
121
      allow(issue).to receive(:today?).and_return(true)
      expect(issue.new?).to be_truthy
122
123
124
    end

    it "returns false when not created today" do
125
126
      allow(issue).to receive(:today?).and_return(false)
      expect(issue.new?).to be_falsey
127
128
129
    end

    it "returns false when record has been updated" do
130
      allow(issue).to receive(:today?).and_return(true)
131
      issue.touch
132
      expect(issue.new?).to be_falsey
133
134
    end
  end
135

136
  describe "#sort" do
137
138
139
    let(:project) { build_stubbed(:empty_project) }

    context "by milestone due date" do
Felipe Artur's avatar
Felipe Artur committed
140
141
142
143
      # Correct order is:
      # Issues/MRs with milestones ordered by date
      # Issues/MRs with milestones without dates
      # Issues/MRs without milestones
144

145
146
147
148
149
150
151
152
153
154
155
      let!(:issue) { create(:issue, project: project) }
      let!(:early_milestone) { create(:milestone, project: project, due_date: 10.days.from_now) }
      let!(:late_milestone) { create(:milestone, project: project, due_date: 30.days.from_now) }
      let!(:issue1) { create(:issue, project: project, milestone: early_milestone) }
      let!(:issue2) { create(:issue, project: project, milestone: late_milestone) }
      let!(:issue3) { create(:issue, project: project) }

      it "sorts desc" do
        issues = project.issues.sort('milestone_due_desc')
        expect(issues).to match_array([issue2, issue1, issue, issue3])
      end
156

157
158
159
      it "sorts asc" do
        issues = project.issues.sort('milestone_due_asc')
        expect(issues).to match_array([issue1, issue2, issue, issue3])
160
161
      end
    end
162
163
164
165
166
167
168
169
170
171
172
173
174
175

    context 'when all of the results are level on the sort key' do
      let!(:issues) do
        10.times { create(:issue, project: project) }
      end

      it 'has no duplicates across pages' do
        sorted_issue_ids = 1.upto(10).map do |i|
          project.issues.sort('milestone_due_desc').page(i).per(1).first.id
        end

        expect(sorted_issue_ids).to eq(sorted_issue_ids.uniq)
      end
    end
176
177
  end

178
  describe '#subscribed?' do
179
180
    let(:project) { issue.project }

181
182
183
184
    context 'user is not a participant in the issue' do
      before { allow(issue).to receive(:participants).with(user).and_return([]) }

      it 'returns false when no subcription exists' do
185
        expect(issue.subscribed?(user, project)).to be_falsey
186
187
188
      end

      it 'returns true when a subcription exists and subscribed is true' do
189
        issue.subscriptions.create(user: user, project: project, subscribed: true)
190

191
        expect(issue.subscribed?(user, project)).to be_truthy
192
193
194
      end

      it 'returns false when a subcription exists and subscribed is false' do
195
        issue.subscriptions.create(user: user, project: project, subscribed: false)
196

197
        expect(issue.subscribed?(user, project)).to be_falsey
198
199
200
201
202
203
204
      end
    end

    context 'user is a participant in the issue' do
      before { allow(issue).to receive(:participants).with(user).and_return([user]) }

      it 'returns false when no subcription exists' do
205
        expect(issue.subscribed?(user, project)).to be_truthy
206
207
208
      end

      it 'returns true when a subcription exists and subscribed is true' do
209
        issue.subscriptions.create(user: user, project: project, subscribed: true)
210

211
        expect(issue.subscribed?(user, project)).to be_truthy
212
213
214
      end

      it 'returns false when a subcription exists and subscribed is false' do
215
        issue.subscriptions.create(user: user, project: project, subscribed: false)
216

217
        expect(issue.subscribed?(user, project)).to be_falsey
218
219
220
221
      end
    end
  end

222
  describe "#to_hook_data" do
223
224
225
    let(:data) { issue.to_hook_data(user) }
    let(:project) { issue.project }

226
    it "returns correct hook data" do
227
228
229
      expect(data[:object_kind]).to eq("issue")
      expect(data[:user]).to eq(user.hook_attrs)
      expect(data[:object_attributes]).to eq(issue.hook_attrs)
230
      expect(data).not_to have_key(:assignee)
231
232
233
234
235
236
    end

    context "issue is assigned" do
      before { issue.update_attribute(:assignee, user) }

      it "returns correct hook data" do
237
238
        expect(data[:object_attributes]['assignee_id']).to eq(user.id)
        expect(data[:assignee]).to eq(user.hook_attrs)
239
      end
240
    end
241
242
243

    include_examples 'project hook data'
    include_examples 'deprecated repository hook data'
244
  end
245
246
247
248
249
250
251

  describe '#card_attributes' do
    it 'includes the author name' do
      allow(issue).to receive(:author).and_return(double(name: 'Robert'))
      allow(issue).to receive(:assignee).and_return(nil)

      expect(issue.card_attributes).
Douwe Maan's avatar
Douwe Maan committed
252
        to eq({ 'Author' => 'Robert', 'Assignee' => nil })
253
254
255
256
257
258
259
    end

    it 'includes the assignee name' do
      allow(issue).to receive(:author).and_return(double(name: 'Robert'))
      allow(issue).to receive(:assignee).and_return(double(name: 'Douwe'))

      expect(issue.card_attributes).
Douwe Maan's avatar
Douwe Maan committed
260
        to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' })
261
262
    end
  end
263

264
265
266
267
268
269
270
271
272
273
274
275
276
277
  describe '#labels_array' do
    let(:project) { create(:project) }
    let(:bug) { create(:label, project: project, title: 'bug') }
    let(:issue) { create(:issue, project: project) }

    before(:each) do
      issue.labels << bug
    end

    it 'loads the association and returns it as an array' do
      expect(issue.reload.labels_array).to eq([bug])
    end
  end

278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
  describe '#user_notes_count' do
    let(:project) { create(:project) }
    let(:issue1) { create(:issue, project: project) }
    let(:issue2) { create(:issue, project: project) }

    before do
      create_list(:note, 3, noteable: issue1, project: project)
      create_list(:note, 6, noteable: issue2, project: project)
    end

    it 'counts the user notes' do
      expect(issue1.user_notes_count).to be(3)
      expect(issue2.user_notes_count).to be(6)
    end
  end

294
  describe "votes" do
295
296
    let(:project) { issue.project }

297
    before do
Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
298
299
      create(:award_emoji, :upvote, awardable: issue)
      create(:award_emoji, :downvote, awardable: issue)
300
301
302
303
304
305
306
    end

    it "returns correct values" do
      expect(issue.upvotes).to eq(1)
      expect(issue.downvotes).to eq(1)
    end
  end
307

308
309
310
311
312
313
314
315
316
317
318
319
320
321
  describe '.order_labels_priority' do
    let(:label_1) { create(:label, title: 'label_1', project: issue.project, priority: 1) }
    let(:label_2) { create(:label, title: 'label_2', project: issue.project, priority: 2) }

    subject { Issue.order_labels_priority(excluded_labels: ['label_1']).first.highest_priority }

    before do
      issue.labels << label_1
      issue.labels << label_2
    end

    it { is_expected.to eq(2) }
  end

322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
  describe ".with_label" do
    let(:project) { create(:project, :public) }
    let(:bug) { create(:label, project: project, title: 'bug') }
    let(:feature) { create(:label, project: project, title: 'feature') }
    let(:enhancement) { create(:label, project: project, title: 'enhancement') }
    let(:issue1) { create(:issue, title: "Bugfix1", project: project) }
    let(:issue2) { create(:issue, title: "Bugfix2", project: project) }
    let(:issue3) { create(:issue, title: "Feature1", project: project) }

    before(:each) do
      issue1.labels << bug
      issue1.labels << feature
      issue2.labels << bug
      issue2.labels << enhancement
      issue3.labels << feature
    end

    it 'finds the correct issue containing just enhancement label' do
      expect(Issue.with_label(enhancement.title)).to match_array([issue2])
    end

    it 'finds the correct issues containing the same label' do
      expect(Issue.with_label(bug.title)).to match_array([issue1, issue2])
    end

    it 'finds the correct issues containing only both labels' do
      expect(Issue.with_label([bug.title, enhancement.title])).to match_array([issue2])
    end
  end
Yorick Peterse's avatar
Yorick Peterse committed
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371

  describe '#assignee_or_author?' do
    let(:user) { build(:user, id: 1) }
    let(:issue) { build(:issue) }

    it 'returns true for a user that is assigned to an issue' do
      issue.assignee = user

      expect(issue.assignee_or_author?(user)).to eq(true)
    end

    it 'returns true for a user that is the author of an issue' do
      issue.author = user

      expect(issue.assignee_or_author?(user)).to eq(true)
    end

    it 'returns false for a user that is not the assignee or author' do
      expect(issue.assignee_or_author?(user)).to eq(false)
    end
  end
372
end