summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorBrett Walker <bwalker@gitlab.com>2018-10-26 12:49:16 +0000
committerNick Thomas <nick@gitlab.com>2018-10-26 12:49:16 +0000
commit7aeab58f4861144fcc1d334907cb1b465c645001 (patch)
treecc37daf4788c2f7c3d951e3fdc3164e1b7ae0fd9 /spec
parent9791f82b4b4f873c153e6698fbb2148b8fa5babe (diff)
downloadgitlab-ce-7aeab58f4861144fcc1d334907cb1b465c645001.tar.gz
Automatically navigate to last board visited
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/groups/boards_controller_spec.rb58
-rw-r--r--spec/controllers/projects/boards_controller_spec.rb56
-rw-r--r--spec/factories/board_group_recent_visit.rb9
-rw-r--r--spec/factories/board_project_recent_visit.rb9
-rw-r--r--spec/models/board_group_recent_visit_spec.rb64
-rw-r--r--spec/models/board_project_recent_visit_spec.rb64
-rw-r--r--spec/services/boards/visits/create_service_spec.rb53
-rw-r--r--spec/services/boards/visits/latest_service_spec.rb47
8 files changed, 357 insertions, 3 deletions
diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb
index bf41aa0706f..d1d08391164 100644
--- a/spec/controllers/groups/boards_controller_spec.rb
+++ b/spec/controllers/groups/boards_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Groups::BoardsController do
let(:group) { create(:group) }
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
before do
group.add_maintainer(user)
@@ -22,6 +22,27 @@ describe Groups::BoardsController do
expect(response.content_type).to eq 'text/html'
end
+ it 'redirects to latest visited board' do
+ board = create(:board, group: group)
+ create(:board_group_recent_visit, group: board.group, board: board, user: user)
+
+ list_boards
+
+ expect(response).to redirect_to(group_board_path(id: board.id))
+ end
+
+ it 'renders template if visited board is not found' do
+ visited = double
+
+ allow(visited).to receive(:board_id).and_return(12)
+ allow_any_instance_of(Boards::Visits::LatestService).to receive(:execute).and_return(visited)
+
+ list_boards
+
+ expect(response).to render_template :index
+ expect(response.content_type).to eq 'text/html'
+ end
+
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true)
@@ -35,12 +56,30 @@ describe Groups::BoardsController do
expect(response.content_type).to eq 'text/html'
end
end
+
+ context 'when user is signed out' do
+ let(:group) { create(:group, :public) }
+
+ it 'renders template' do
+ sign_out(user)
+
+ board = create(:board, group: group)
+ create(:board_group_recent_visit, group: board.group, board: board, user: user)
+
+ list_boards
+
+ expect(response).to render_template :index
+ expect(response.content_type).to eq 'text/html'
+ end
+ end
end
context 'when format is JSON' do
it 'return an array with one group board' do
create(:board, group: group)
+ expect(Boards::Visits::LatestService).not_to receive(:new)
+
list_boards format: :json
parsed_response = JSON.parse(response.body)
@@ -74,7 +113,7 @@ describe Groups::BoardsController do
context 'when format is HTML' do
it 'renders template' do
- read_board board: board
+ expect { read_board board: board }.to change(BoardGroupRecentVisit, :count).by(1)
expect(response).to render_template :show
expect(response.content_type).to eq 'text/html'
@@ -93,10 +132,25 @@ describe Groups::BoardsController do
expect(response.content_type).to eq 'text/html'
end
end
+
+ context 'when user is signed out' do
+ let(:group) { create(:group, :public) }
+
+ it 'does not save visit' do
+ sign_out(user)
+
+ expect { read_board board: board }.to change(BoardGroupRecentVisit, :count).by(0)
+
+ expect(response).to render_template :show
+ expect(response.content_type).to eq 'text/html'
+ end
+ end
end
context 'when format is JSON' do
it 'returns project board' do
+ expect(Boards::Visits::CreateService).not_to receive(:new)
+
read_board board: board, format: :json
expect(response).to match_response_schema('board')
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index 096efc1c7b2..667eaa5e34f 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -28,6 +28,27 @@ describe Projects::BoardsController do
expect(response.content_type).to eq 'text/html'
end
+ it 'redirects to latest visited board' do
+ board = create(:board, project: project)
+ create(:board_project_recent_visit, project: board.project, board: board, user: user)
+
+ list_boards
+
+ expect(response).to redirect_to(namespace_project_board_path(id: board.id))
+ end
+
+ it 'renders template if visited board is not found' do
+ visited = double
+
+ allow(visited).to receive(:board_id).and_return(12)
+ allow_any_instance_of(Boards::Visits::LatestService).to receive(:execute).and_return(visited)
+
+ list_boards
+
+ expect(response).to render_template :index
+ expect(response.content_type).to eq 'text/html'
+ end
+
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
@@ -41,12 +62,30 @@ describe Projects::BoardsController do
expect(response.content_type).to eq 'text/html'
end
end
+
+ context 'when user is signed out' do
+ let(:project) { create(:project, :public) }
+
+ it 'renders template' do
+ sign_out(user)
+
+ board = create(:board, project: project)
+ create(:board_project_recent_visit, project: board.project, board: board, user: user)
+
+ list_boards
+
+ expect(response).to render_template :index
+ expect(response.content_type).to eq 'text/html'
+ end
+ end
end
context 'when format is JSON' do
it 'returns a list of project boards' do
create_list(:board, 2, project: project)
+ expect(Boards::Visits::LatestService).not_to receive(:new)
+
list_boards format: :json
parsed_response = JSON.parse(response.body)
@@ -98,7 +137,7 @@ describe Projects::BoardsController do
context 'when format is HTML' do
it 'renders template' do
- read_board board: board
+ expect { read_board board: board }.to change(BoardProjectRecentVisit, :count).by(1)
expect(response).to render_template :show
expect(response.content_type).to eq 'text/html'
@@ -117,10 +156,25 @@ describe Projects::BoardsController do
expect(response.content_type).to eq 'text/html'
end
end
+
+ context 'when user is signed out' do
+ let(:project) { create(:project, :public) }
+
+ it 'does not save visit' do
+ sign_out(user)
+
+ expect { read_board board: board }.to change(BoardProjectRecentVisit, :count).by(0)
+
+ expect(response).to render_template :show
+ expect(response.content_type).to eq 'text/html'
+ end
+ end
end
context 'when format is JSON' do
it 'returns project board' do
+ expect(Boards::Visits::CreateService).not_to receive(:new)
+
read_board board: board, format: :json
expect(response).to match_response_schema('board')
diff --git a/spec/factories/board_group_recent_visit.rb b/spec/factories/board_group_recent_visit.rb
new file mode 100644
index 00000000000..97ad5d6d068
--- /dev/null
+++ b/spec/factories/board_group_recent_visit.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :board_group_recent_visit do
+ user
+ group
+ board
+ end
+end
diff --git a/spec/factories/board_project_recent_visit.rb b/spec/factories/board_project_recent_visit.rb
new file mode 100644
index 00000000000..49ae4d7b391
--- /dev/null
+++ b/spec/factories/board_project_recent_visit.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :board_project_recent_visit do
+ user
+ project
+ board
+ end
+end
diff --git a/spec/models/board_group_recent_visit_spec.rb b/spec/models/board_group_recent_visit_spec.rb
new file mode 100644
index 00000000000..59ad4e5417e
--- /dev/null
+++ b/spec/models/board_group_recent_visit_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe BoardGroupRecentVisit do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:board) { create(:board, group: group) }
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to belong_to(:group) }
+ it { is_expected.to belong_to(:board) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:user) }
+ it { is_expected.to validate_presence_of(:group) }
+ it { is_expected.to validate_presence_of(:board) }
+ end
+
+ describe '#visited' do
+ it 'creates a visit if one does not exists' do
+ expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1)
+ end
+
+ shared_examples 'was visited previously' do
+ let!(:visit) { create :board_group_recent_visit, group: board.group, board: board, user: user, updated_at: 7.days.ago }
+
+ it 'updates the timestamp' do
+ Timecop.freeze do
+ described_class.visited!(user, board)
+
+ expect(described_class.count).to eq 1
+ expect(described_class.first.updated_at).to be_like_time(Time.zone.now)
+ end
+ end
+ end
+
+ it_behaves_like 'was visited previously'
+
+ context 'when we try to create a visit that is not unique' do
+ before do
+ expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique, 'record not unique')
+ expect(described_class).to receive(:find_or_create_by).and_return(visit)
+ end
+
+ it_behaves_like 'was visited previously'
+ end
+ end
+
+ describe '#latest' do
+ it 'returns the most recent visited' do
+ board2 = create(:board, group: group)
+ board3 = create(:board, group: group)
+
+ create :board_group_recent_visit, group: board.group, board: board, user: user, updated_at: 7.days.ago
+ create :board_group_recent_visit, group: board2.group, board: board2, user: user, updated_at: 5.days.ago
+ recent = create :board_group_recent_visit, group: board3.group, board: board3, user: user, updated_at: 1.day.ago
+
+ expect(described_class.latest(user, group)).to eq recent
+ end
+ end
+end
diff --git a/spec/models/board_project_recent_visit_spec.rb b/spec/models/board_project_recent_visit_spec.rb
new file mode 100644
index 00000000000..275581945fa
--- /dev/null
+++ b/spec/models/board_project_recent_visit_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe BoardProjectRecentVisit do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:board) { create(:board, project: project) }
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:board) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:user) }
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:board) }
+ end
+
+ describe '#visited' do
+ it 'creates a visit if one does not exists' do
+ expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1)
+ end
+
+ shared_examples 'was visited previously' do
+ let!(:visit) { create :board_project_recent_visit, project: board.project, board: board, user: user, updated_at: 7.days.ago }
+
+ it 'updates the timestamp' do
+ Timecop.freeze do
+ described_class.visited!(user, board)
+
+ expect(described_class.count).to eq 1
+ expect(described_class.first.updated_at).to be_like_time(Time.zone.now)
+ end
+ end
+ end
+
+ it_behaves_like 'was visited previously'
+
+ context 'when we try to create a visit that is not unique' do
+ before do
+ expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique, 'record not unique')
+ expect(described_class).to receive(:find_or_create_by).and_return(visit)
+ end
+
+ it_behaves_like 'was visited previously'
+ end
+ end
+
+ describe '#latest' do
+ it 'returns the most recent visited' do
+ board2 = create(:board, project: project)
+ board3 = create(:board, project: project)
+
+ create :board_project_recent_visit, project: board.project, board: board, user: user, updated_at: 7.days.ago
+ create :board_project_recent_visit, project: board2.project, board: board2, user: user, updated_at: 5.days.ago
+ recent = create :board_project_recent_visit, project: board3.project, board: board3, user: user, updated_at: 1.day.ago
+
+ expect(described_class.latest(user, project)).to eq recent
+ end
+ end
+end
diff --git a/spec/services/boards/visits/create_service_spec.rb b/spec/services/boards/visits/create_service_spec.rb
new file mode 100644
index 00000000000..6baf7ac9deb
--- /dev/null
+++ b/spec/services/boards/visits/create_service_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Boards::Visits::CreateService do
+ describe '#execute' do
+ let(:user) { create(:user) }
+
+ context 'when a project board' do
+ let(:project) { create(:project) }
+ let(:project_board) { create(:board, project: project) }
+
+ subject(:service) { described_class.new(project_board.parent, user) }
+
+ it 'returns nil when there is no user' do
+ service.current_user = nil
+
+ expect(service.execute(project_board)).to eq nil
+ end
+
+ it 'returns nil when database is read only' do
+ allow(Gitlab::Database).to receive(:read_only?) { true }
+
+ expect(service.execute(project_board)).to eq nil
+ end
+
+ it 'records the visit' do
+ expect(BoardProjectRecentVisit).to receive(:visited!).once
+
+ service.execute(project_board)
+ end
+ end
+
+ context 'when a group board' do
+ let(:group) { create(:group) }
+ let(:group_board) { create(:board, group: group) }
+
+ subject(:service) { described_class.new(group_board.parent, user) }
+
+ it 'returns nil when there is no user' do
+ service.current_user = nil
+
+ expect(service.execute(group_board)).to eq nil
+ end
+
+ it 'records the visit' do
+ expect(BoardGroupRecentVisit).to receive(:visited!).once
+
+ service.execute(group_board)
+ end
+ end
+ end
+end
diff --git a/spec/services/boards/visits/latest_service_spec.rb b/spec/services/boards/visits/latest_service_spec.rb
new file mode 100644
index 00000000000..e55d599e2cc
--- /dev/null
+++ b/spec/services/boards/visits/latest_service_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Boards::Visits::LatestService do
+ describe '#execute' do
+ let(:user) { create(:user) }
+
+ context 'when a project board' do
+ let(:project) { create(:project) }
+ let(:project_board) { create(:board, project: project) }
+
+ subject(:service) { described_class.new(project_board.parent, user) }
+
+ it 'returns nil when there is no user' do
+ service.current_user = nil
+
+ expect(service.execute).to eq nil
+ end
+
+ it 'queries for most recent visit' do
+ expect(BoardProjectRecentVisit).to receive(:latest).once
+
+ service.execute
+ end
+ end
+
+ context 'when a group board' do
+ let(:group) { create(:group) }
+ let(:group_board) { create(:board, group: group) }
+
+ subject(:service) { described_class.new(group_board.parent, user) }
+
+ it 'returns nil when there is no user' do
+ service.current_user = nil
+
+ expect(service.execute).to eq nil
+ end
+
+ it 'queries for most recent visit' do
+ expect(BoardGroupRecentVisit).to receive(:latest).once
+
+ service.execute
+ end
+ end
+ end
+end