diff --git a/app/assets/stylesheets/main.css b/app/assets/stylesheets/main.css
index 84ea2e1..4f679f5 100644
--- a/app/assets/stylesheets/main.css
+++ b/app/assets/stylesheets/main.css
@@ -112,6 +112,10 @@ li {
margin-top: 1rem;
}
+.pr-9 {
+ padding-right: 9rem;
+}
+
.text-center {
text-align: center;
}
@@ -248,7 +252,8 @@ nav div.user {
.buttons .collections,
.buttons .equations,
.buttons .groupings,
-.buttons .participants {
+.buttons .participants,
+.buttons .reports {
width: 100%;
gap: 2.5vmin;
}
@@ -326,7 +331,8 @@ nav div.user {
.collections,
.equations,
.groupings,
-.participants {
+.participants,
+.reports {
width: 90%;
display: flex;
flex-direction: column;
@@ -363,7 +369,8 @@ nav div.user {
#answers,
#collections,
-#groupings {
+#groupings,
+#reports {
width: 100%;
display: flex;
flex-direction: column;
@@ -427,7 +434,8 @@ nav div.user {
.collections a,
.equations a,
.groupings a,
-.participants a {
+.participants a,
+.reports a {
text-align: center;
font-size: 1.5rem;
margin: 2vmin auto;
@@ -525,6 +533,7 @@ a.forgot-password:hover {
}
.btn {
+ /* font-size: 1.2rem; */
cursor: pointer;
width: 100%;
text-align: center;
diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb
new file mode 100644
index 0000000..0343403
--- /dev/null
+++ b/app/controllers/reports_controller.rb
@@ -0,0 +1,38 @@
+class ReportsController < ApplicationController
+ before_action :set_report, only: %i[ show destroy ]
+
+ # GET /reports or /reports.json
+ def index
+ @reports = current_admin.reports
+ end
+
+ # GET /reports/1 or /reports/1.json
+ def show
+ if @report.nil?
+ @reports = current_admin.reports
+ flash.now[:alert] = I18n.t("reports.errors.not_found")
+ render :index
+ end
+ end
+
+ # DELETE /reports/1 or /reports/1.json
+ def destroy
+ @report.destroy!
+
+ respond_to do |format|
+ format.html { redirect_to reports_path, status: :see_other, notice: I18n.t("reports.destroyed") }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_report
+ @report = Report.find_by_id(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def report_params
+ params.require(:report).permit(:user_admin_id, :collection_id, :grouping_id)
+ end
+end
diff --git a/app/models/collection.rb b/app/models/collection.rb
index be2029d..3a15c71 100644
--- a/app/models/collection.rb
+++ b/app/models/collection.rb
@@ -2,6 +2,7 @@ class Collection < ApplicationRecord
belongs_to :user_admin, class_name: "User::Admin", dependent: :destroy
has_many :collection_equations, dependent: :destroy
has_many :equations, through: :collection_equations
+ has_many :reports
validates :name, :equations_quantity, presence: true
end
diff --git a/app/models/report.rb b/app/models/report.rb
new file mode 100644
index 0000000..cde4bcd
--- /dev/null
+++ b/app/models/report.rb
@@ -0,0 +1,9 @@
+class Report < ApplicationRecord
+ belongs_to :user_admin, class_name: "User::Admin", foreign_key: "user_admin_id"
+ belongs_to :collection
+ belongs_to :grouping
+
+ has_many :participants, class_name: "User::Participant",
+ foreign_key: :user_participant_id, through: :grouping
+ has_many :rounds
+end
diff --git a/app/models/round.rb b/app/models/round.rb
index e6fac6a..0f7bd68 100644
--- a/app/models/round.rb
+++ b/app/models/round.rb
@@ -1,5 +1,33 @@
class Round < ApplicationRecord
belongs_to :collection
belongs_to :participant, class_name: "User::Participant", foreign_key: "user_participant_id"
+ belongs_to :report, optional: true
+
has_many :answers
+
+ validates :user_participant_id, uniqueness: { scope: :collection_id }
+ validate :rounds_length
+
+ private
+
+ def rounds_length
+ report = Report.find_by(collection: collection)
+ grouping = report&.grouping
+
+ if report && grouping
+ rounds = report&.rounds
+ participants = grouping&.participants
+
+ if rounds && participants
+ return true if rounds.length <= participants.length
+
+ errors.add(
+ :base,
+ :too_many_rounds,
+ grouping: grouping.name,
+ count: grouping.participants.length
+ )
+ end
+ end
+ end
end
diff --git a/app/services/report/create_service.rb b/app/services/report/create_service.rb
new file mode 100644
index 0000000..e0f31ad
--- /dev/null
+++ b/app/services/report/create_service.rb
@@ -0,0 +1,23 @@
+class Report::CreateService
+ def initialize(user_admin, collection, grouping)
+ @user_admin = user_admin
+ @collection = collection
+ @grouping = grouping
+ end
+
+ def self.call(user_admin:, collection:, grouping:)
+ new(user_admin, collection, grouping).call
+ end
+
+ def call
+ create_report
+ end
+
+ def create_report
+ @report = Report.find_or_create_by(
+ user_admin: @user_admin,
+ collection: @collection,
+ grouping: @grouping
+ )
+ end
+end
diff --git a/app/services/round/finish_service.rb b/app/services/round/finish_service.rb
index e69977f..1bef723 100644
--- a/app/services/round/finish_service.rb
+++ b/app/services/round/finish_service.rb
@@ -29,9 +29,17 @@ def collection_completed?
def finalize_round
@completed_at = Time.current
+ report = Report::CreateService.call(
+ user_admin: @user_admin,
+ collection: @collection,
+ grouping: @participant.grouping,
+ )
+
@current_round.update!(
completed_at: @completed_at,
- round_time: calculate_round_time
+ round_time: calculate_round_time,
+ report: report
+
)
end
diff --git a/app/views/reports/_report.html.erb b/app/views/reports/_report.html.erb
new file mode 100644
index 0000000..03947bc
--- /dev/null
+++ b/app/views/reports/_report.html.erb
@@ -0,0 +1,54 @@
+
+
+
+
+
+ <%= report.grouping.name %>
+ |
+
+
+ <%= t("activerecord.models.collection") %> |
+ <%= report.collection.name %> |
+
+
+ <% report.rounds.each do |round| %>
+
+
+ <%= t("activerecord.models.user/participant") %> |
+ <%= round.participant.full_name %> |
+
+
+
+
+
+ <%= t("activerecord.models.equation").pluralize %>
+ |
+ <%= t("activerecord.attributes.equation.position_a") %> |
+ <%= t("activerecord.attributes.equation.operator") %> |
+ <%= t("activerecord.attributes.equation.position_b") %> |
+ <%= t("activerecord.attributes.equation.position_c") %> |
+ <%= t("activerecord.attributes.equation.unknown_position") %> |
+
+ <%= t("activerecord.models.answer").pluralize %>
+ |
+ <%= t("activerecord.attributes.answer.answer_value") %> |
+ <%= t("activerecord.attributes.answer.correct_answer") %> |
+ <%= t("activerecord.attributes.answer.time") %> |
+
+ <% round.answers.each do |answer| %>
+
+ <%= answer.equation.position_a %> |
+ <%= answer.equation.operator %> |
+ <%= answer.equation.position_b %> |
+ <%= answer.equation.position_c %> |
+ <%= answer.equation.unknown_position %> |
+ <%= answer.answer_value %> |
+ <%= answer.correct_answer ? "✔️" : "❌" %> |
+ <%= answer.formatted_time %> |
+
+ <% end %>
+ <% end %>
+
+
+
+
diff --git a/app/views/reports/_report.json.jbuilder b/app/views/reports/_report.json.jbuilder
new file mode 100644
index 0000000..4e5c131
--- /dev/null
+++ b/app/views/reports/_report.json.jbuilder
@@ -0,0 +1,2 @@
+json.extract! report, :id, :user_admin_id, :user_participant_id, :collection_id, :grouping_id, :created_at, :updated_at
+json.url report_url(report, format: :json)
diff --git a/app/views/reports/index.html.erb b/app/views/reports/index.html.erb
new file mode 100644
index 0000000..6096e1c
--- /dev/null
+++ b/app/views/reports/index.html.erb
@@ -0,0 +1,18 @@
+<% content_for :title, t("activerecord.models.report").pluralize %>
+
+
+ <%= render "shared/header" %>
+ <%= t("activerecord.models.report").pluralize %>
+
+
+ <% @reports.each do |report| %>
+
+ <%= render report %>
+ <%= link_to t("reports.show"), report %>
+
+ <% end %>
+
+ <%= link_to t("home.back"), root_path, class: "btn mustard btn-text-black" %>
+
+
+
diff --git a/app/views/reports/index.json.jbuilder b/app/views/reports/index.json.jbuilder
new file mode 100644
index 0000000..31811f6
--- /dev/null
+++ b/app/views/reports/index.json.jbuilder
@@ -0,0 +1 @@
+json.array! @reports, partial: "reports/report", as: :report
diff --git a/app/views/reports/show.html.erb b/app/views/reports/show.html.erb
new file mode 100644
index 0000000..0975380
--- /dev/null
+++ b/app/views/reports/show.html.erb
@@ -0,0 +1,12 @@
+
+
+ <%= render "shared/header" %>
+
+ <%= render @report %>
+
+ <%= link_to t("reports.back"), reports_path %>
+ <%= button_to t("reports.delete"), @report, class: "btn crimson", method: :delete, data: { turbo_method: :delete } %>
+
+
+
+
diff --git a/app/views/reports/show.json.jbuilder b/app/views/reports/show.json.jbuilder
new file mode 100644
index 0000000..b5a5508
--- /dev/null
+++ b/app/views/reports/show.json.jbuilder
@@ -0,0 +1 @@
+json.partial! "reports/report", report: @report
diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb
index 784f74a..9643956 100644
--- a/app/views/shared/_header.html.erb
+++ b/app/views/shared/_header.html.erb
@@ -7,7 +7,7 @@
<%= link_to t("activerecord.models.grouping").pluralize, groupings_path, class: "nav-item" %>
<%= link_to t("activerecord.models.collection").pluralize, collections_path, class: "nav-item" %>
<%= link_to t("activerecord.models.equation").pluralize, equations_path, class: "nav-item" %>
- <%#= link_to t("activerecord.models.answer").pluralize, answers_path, class: "nav-item" %>
+ <%= link_to t("activerecord.models.report").pluralize, reports_path, class: "nav-item" %>
<% end %>
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 37e4a73..942c717 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -14,9 +14,9 @@ pt-BR:
register: "Cadastrar nova resposta"
show: "Ver resposta"
show_all: "Ver todas as respostas"
- created: "A resposta foi criada com sucesso."
- updated: "A resposta foi atualizada com sucesso."
- destroyed: "A resposta foi excluída com sucesso."
+ created: "A resposta foi criada com sucesso!"
+ updated: "A resposta foi atualizada com sucesso!"
+ destroyed: "A resposta foi excluída com sucesso!"
collections:
back: "Voltar para etapas"
delete: "Excluir etapa"
@@ -26,9 +26,9 @@ pt-BR:
register: "Cadastrar nova etapa"
show: "Ver etapa"
show_all: "Ver todas as etapas"
- created: "A etapa foi criada com sucesso."
- updated: "A etapa foi atualizada com sucesso."
- destroyed: "A etapa foi excluída com sucesso."
+ created: "A etapa foi criada com sucesso!"
+ updated: "A etapa foi atualizada com sucesso!"
+ destroyed: "A etapa foi excluída com sucesso!"
errors:
equations_limit:
one: "não pode ter mais do que %{count} equação"
@@ -44,9 +44,9 @@ pt-BR:
register: "Cadastrar equação"
show: "Ver equação"
show_all: "Ver todas as equações"
- created: "A equação foi criada com sucesso."
- updated: "A equação foi atualizada com sucesso."
- destroyed: "A equação foi excluída com sucesso."
+ created: "A equação foi criada com sucesso!"
+ updated: "A equação foi atualizada com sucesso!"
+ destroyed: "A equação foi excluída com sucesso!"
groupings:
back: "Voltar para grupos"
delete: "Excluir grupo"
@@ -56,9 +56,9 @@ pt-BR:
select: "Selecionar grupo"
show: "Ver grupo"
show_all: "Ver todos os grupos"
- created: "O grupo foi criado com sucesso."
- updated: "O grupo foi atualizado com sucesso."
- destroyed: "O grupo foi excluído com sucesso."
+ created: "O grupo foi criado com sucesso!"
+ updated: "O grupo foi atualizado com sucesso!"
+ destroyed: "O grupo foi excluído com sucesso!"
participants:
back: "Voltar para participantes"
delete: "Excluir participante"
@@ -70,9 +70,17 @@ pt-BR:
register: "Cadastrar participante"
show: "Ver participante"
show_all: "Ver todos os participantes"
- created: "O participante foi criado com sucesso."
- updated: "O participante foi atualizado com sucesso."
- destroyed: "O participante foi excluído com sucesso."
+ created: "O participante foi criado com sucesso!"
+ updated: "O participante foi atualizado com sucesso!"
+ destroyed: "O participante foi excluído com sucesso!"
+ reports:
+ back: "Voltar para relatórios"
+ delete: "Excluir relatório"
+ show: "Ver relatório"
+ show_all: "Ver todos os relatórios"
+ destroyed: "O relatório foi excluído com sucesso!"
+ errors:
+ not_found: "O relatório não foi encontrado!"
rounds:
new: "Nova sessão"
congrats: "Parabéns!"
@@ -112,6 +120,7 @@ pt-BR:
collection: "Etapa"
equation: "Equação"
grouping: "Grupo"
+ report: "Relatório"
round: "Sessão"
user/admin: "Admin"
user/participant: "Participante"
@@ -256,6 +265,9 @@ pt-BR:
wrong_length:
one: não possui o tamanho esperado (%{count} caracter)
other: não possui o tamanho esperado (%{count} caracteres)
+ too_many_rounds:
+ one: "%{model} não pode ter mais que %{count} sessão, pois o Grupo %{grouping} possui apenas %{count} participante"
+ other: "%{model} não pode ter mais que %{count} sessões, pois o Grupo %{grouping} possui apenas %{count} participantes"
template:
body: "Por favor, verifique o(s) seguinte(s) campo(s):"
header:
diff --git a/config/routes.rb b/config/routes.rb
index b0fa0b4..c6ed3b3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,6 +1,6 @@
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
Rails.application.routes.draw do
- scope "(:locale)", locale: /pt-BR|en/ do
+ scope "(:locale)", locale: /pt-BR|en/, defaults: { locale: "pt-BR" } do
# Defines the root path route ("/")
root "home#index"
@@ -11,7 +11,7 @@
resources :equations
resources :groupings
resources :participants, module: :user
- resources :reports
+ resources :reports, only: [ :index, :show, :destroy ]
resources :equations, only: [] do
resources :collections, only: [] do
diff --git a/db/migrate/20241213171716_create_reports.rb b/db/migrate/20241213171716_create_reports.rb
new file mode 100644
index 0000000..335caef
--- /dev/null
+++ b/db/migrate/20241213171716_create_reports.rb
@@ -0,0 +1,11 @@
+class CreateReports < ActiveRecord::Migration[7.2]
+ def change
+ create_table :reports do |t|
+ t.references :user_admin, null: false, foreign_key: true
+ t.references :collection, null: false, foreign_key: true
+ t.references :group, null: false, foreign_key: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20241216155927_add_report_ref_to_rounds.rb b/db/migrate/20241216155927_add_report_ref_to_rounds.rb
new file mode 100644
index 0000000..ab31190
--- /dev/null
+++ b/db/migrate/20241216155927_add_report_ref_to_rounds.rb
@@ -0,0 +1,5 @@
+class AddReportRefToRounds < ActiveRecord::Migration[7.2]
+ def change
+ add_reference :rounds, :report, null: false, foreign_key: true
+ end
+end
diff --git a/db/migrate/20241221023018_change_round_column_report_to_allow_null.rb b/db/migrate/20241221023018_change_round_column_report_to_allow_null.rb
new file mode 100644
index 0000000..37e3193
--- /dev/null
+++ b/db/migrate/20241221023018_change_round_column_report_to_allow_null.rb
@@ -0,0 +1,5 @@
+class ChangeRoundColumnReportToAllowNull < ActiveRecord::Migration[7.2]
+ def change
+ change_column_null :rounds, :report_id, true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index deabf6a..0f6ab62 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 2024_12_18_195846) do
+ActiveRecord::Schema[7.2].define(version: 2024_12_21_023018) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -87,7 +87,7 @@
t.integer "round_time"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.bigint "report_id", null: false
+ t.bigint "report_id"
t.index ["collection_id"], name: "index_rounds_on_collection_id"
t.index ["current_equation_id"], name: "index_rounds_on_current_equation_id"
t.index ["report_id"], name: "index_rounds_on_report_id"
diff --git a/spec/factories/reports.rb b/spec/factories/reports.rb
new file mode 100644
index 0000000..a3f9166
--- /dev/null
+++ b/spec/factories/reports.rb
@@ -0,0 +1,7 @@
+FactoryBot.define do
+ factory :report do
+ user_admin
+ collection
+ grouping
+ end
+end
diff --git a/spec/factories/rounds.rb b/spec/factories/rounds.rb
index c8059d2..786645c 100644
--- a/spec/factories/rounds.rb
+++ b/spec/factories/rounds.rb
@@ -4,6 +4,7 @@
association :participant, factory: :user_participant
started_at { "2024-12-06 14:00:00" }
completed_at { "2024-12-06 14:02:50" }
+ report
trait :unfinished do
started_at { Time.current }
diff --git a/spec/factories/user/participants.rb b/spec/factories/user/participants.rb
index 4a98c2d..f7248c7 100644
--- a/spec/factories/user/participants.rb
+++ b/spec/factories/user/participants.rb
@@ -3,7 +3,7 @@
first_name { FFaker::Name.first_name }
last_name { FFaker::Name.last_name }
birth_date { FFaker::Date.birthday(min_age: 5) }
- grouping { build(:grouping) }
+ grouping
user_admin
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
new file mode 100644
index 0000000..9093d6e
--- /dev/null
+++ b/spec/models/report_spec.rb
@@ -0,0 +1,21 @@
+require 'rails_helper'
+
+RSpec.describe Report, type: :model do
+ describe 'database columns' do
+ it { should have_db_column(:user_admin_id).of_type(:integer) }
+ it { should have_db_column(:collection_id).of_type(:integer) }
+ it { should have_db_column(:grouping_id).of_type(:integer) }
+ end
+
+ describe 'model associations' do
+ it { should belong_to(:user_admin).class_name('User::Admin') }
+ it { should belong_to(:collection) }
+ it { should belong_to(:grouping) }
+ it { should have_many(:rounds) }
+ it do
+ should have_many(:participants)
+ .class_name('User::Participant')
+ .through(:grouping)
+ end
+ end
+end
diff --git a/spec/models/round_spec.rb b/spec/models/round_spec.rb
index bbf9cc2..8b1b6f8 100644
--- a/spec/models/round_spec.rb
+++ b/spec/models/round_spec.rb
@@ -14,6 +14,86 @@
describe 'model associations' do
it { should belong_to(:collection) }
it { should belong_to(:participant).class_name('User::Participant') }
+ it { should belong_to(:report).optional }
it { should have_many(:answers) }
end
+
+
+ describe 'model validations' do
+ let(:user_admin) { create(:user_admin) }
+ let(:collection) { create(:collection, user_admin: user_admin) }
+ let(:grouping) { create(:grouping, user_admin: user_admin) }
+ let(:participant) {
+ create(
+ :user_participant,
+ grouping: grouping,
+ user_admin: user_admin
+ )
+ }
+ let(:report) {
+ create(
+ :report,
+ user_admin: user_admin,
+ collection: collection,
+ grouping: grouping
+ )
+ }
+
+ context "when report is nil" do
+ it "should be valid" do
+ round = create(:round, report: nil)
+
+ expect(round).to be_valid
+ expect(round.errors).to be_empty
+ end
+ end
+
+ context 'when rounds count is less or equal to the number of participants in a grouping' do
+ it 'should be valid with no errors' do
+ round = create(
+ :round,
+ collection: collection,
+ participant: participant,
+ report: report
+ )
+
+ expect(round).to be_valid
+ expect(round.errors).to be_empty
+ end
+ end
+
+ context 'when rounds count is greater than the number of participants in a grouping' do
+ it 'should not be valid' do
+ grouping.reload
+
+ report.rounds << [
+ create(
+ :round,
+ collection: collection,
+ participant: participant,
+ report: report
+ )
+ ]
+
+ round = create(
+ :round,
+ collection: collection,
+ participant: create(:user_participant, user_admin: user_admin),
+ report: report
+ )
+
+
+ expect(round).not_to be_valid
+ expect(round.errors).not_to be_empty
+ expect(round.errors.full_messages).to include(
+ I18n.t(
+ 'errors.messages.too_many_rounds',
+ model: round.model_name.human,
+ grouping: grouping.name,
+ count: grouping.participants.length
+ )
+ )
+ end
+ end
+ end
end
diff --git a/spec/requests/reports_spec.rb b/spec/requests/reports_spec.rb
new file mode 100644
index 0000000..7ac3ce3
--- /dev/null
+++ b/spec/requests/reports_spec.rb
@@ -0,0 +1,78 @@
+require 'rails_helper'
+
+# This spec was generated by rspec-rails when you ran the scaffold generator.
+# It demonstrates how one might use RSpec to test the controller code that
+# was generated by Rails when you ran the scaffold generator.
+#
+# It assumes that the implementation code is generated by the rails scaffold
+# generator. If you are using any extension libraries to generate different
+# controller code, this generated spec may or may not pass.
+#
+# It only uses APIs available in rails and/or rspec-rails. There are a number
+# of tools you can use to make these specs even more expressive, but we're
+# sticking to rails and rspec-rails APIs to keep things simple and stable.
+
+RSpec.describe "/reports", type: :request do
+ # This should return the minimal set of attributes required to create a valid
+ # Report. As you add validations to Report, be sure to
+ # adjust the attributes here as well.
+ let(:user_admin) { create(:user_admin) }
+ let(:valid_attributes) {
+ {
+ user_admin: user_admin,
+ collection: create(:collection),
+ grouping: create(:grouping)
+ }
+ }
+
+ let(:invalid_attributes) {
+ {
+ user_admin: nil,
+ collection: nil,
+ grouping: nil
+ }
+ }
+
+ describe "GET /index" do
+ it "renders a successful response" do
+ sign_in user_admin
+ Report.create! valid_attributes
+ get reports_url
+ expect(response).to be_successful
+ end
+ end
+
+ describe "GET /show" do
+ context "with valid parameters" do
+ it "renders a successful response" do
+ report = Report.create! valid_attributes
+ get report_url(report, locale: I18n.locale)
+ expect(response).to be_successful
+ end
+ end
+
+ context "with invalid parameters" do
+ it "redirects to index page" do
+ sign_in user_admin
+ invalid_report_id = -1
+ get report_url(invalid_report_id, locale: I18n.locale)
+ expect(response.body).to match(I18n.t("reports.errors.not_found"))
+ end
+ end
+ end
+
+ describe "DELETE /destroy" do
+ it "destroys the requested report" do
+ report = Report.create! valid_attributes
+ expect {
+ delete report_url(report, locale: I18n.locale)
+ }.to change(Report, :count).by(-1)
+ end
+
+ it "redirects to the reports list" do
+ report = Report.create! valid_attributes
+ delete report_url(report, locale: I18n.locale)
+ expect(response).to redirect_to(reports_url)
+ end
+ end
+end
diff --git a/spec/routing/reports_routing_spec.rb b/spec/routing/reports_routing_spec.rb
new file mode 100644
index 0000000..760dbc6
--- /dev/null
+++ b/spec/routing/reports_routing_spec.rb
@@ -0,0 +1,17 @@
+require "rails_helper"
+
+RSpec.describe ReportsController, type: :routing do
+ describe "routing" do
+ it "routes to #index" do
+ expect(get: "/reports").to route_to("reports#index", locale: I18n.locale.to_s)
+ end
+
+ it "routes to #show" do
+ expect(get: "/reports/1").to route_to("reports#show", id: "1", locale: I18n.locale.to_s)
+ end
+
+ it "routes to #destroy" do
+ expect(delete: "/reports/1").to route_to("reports#destroy", id: "1", locale: I18n.locale.to_s)
+ end
+ end
+end
diff --git a/spec/services/report/create_service_spec.rb b/spec/services/report/create_service_spec.rb
new file mode 100644
index 0000000..c1c8e9b
--- /dev/null
+++ b/spec/services/report/create_service_spec.rb
@@ -0,0 +1,54 @@
+require "rails_helper"
+
+RSpec.describe Report::CreateService, type: :service do
+ describe ".call" do
+ let(:user_admin) { create(:user_admin) }
+ let(:collection) { create(:collection) }
+ let(:grouping) { create(:grouping) }
+
+ it "calls the instance method #call" do
+ service_instance = instance_double(Report::CreateService)
+ allow(Report::CreateService).to receive(:new).and_return(service_instance)
+ allow(service_instance).to receive(:call)
+
+ Report::CreateService.call(user_admin: user_admin, collection: collection, grouping: grouping)
+
+ expect(Report::CreateService).to have_received(:new).with(user_admin, collection, grouping)
+ expect(service_instance).to have_received(:call)
+ end
+ end
+
+ describe "#initialize" do
+ subject(:service) { described_class.new(user_admin, collection, grouping) }
+ let(:user_admin) { create(:user_admin) }
+ let(:collection) { create(:collection) }
+ let(:grouping) { create(:grouping) }
+
+ it "initializes with user_admin, collection and grouping" do
+ expect(service.instance_variable_get(:@user_admin)).to eq(user_admin)
+ expect(service.instance_variable_get(:@collection)).to eq(collection)
+ expect(service.instance_variable_get(:@grouping)).to eq(grouping)
+ end
+ end
+
+ describe "#call" do
+ subject(:service) { described_class.new(user_admin, collection, grouping) }
+ let(:user_admin) { create(:user_admin) }
+ let(:collection) { create(:collection) }
+ let(:grouping) { create(:grouping) }
+
+ context "when report doesn't exist" do
+ it "creates a report" do
+ expect { service.call }.to change(Report, :count).by(1)
+ end
+ end
+
+ context "when report already exists" do
+ let!(:report) { create(:report, user_admin: user_admin, collection: collection, grouping: grouping) }
+
+ it "doesn't create a report" do
+ expect { service.call }.not_to change(Report, :count)
+ end
+ end
+ end
+end
diff --git a/spec/views/reports/_report.html.erb_spec.rb b/spec/views/reports/_report.html.erb_spec.rb
new file mode 100644
index 0000000..b57eed4
--- /dev/null
+++ b/spec/views/reports/_report.html.erb_spec.rb
@@ -0,0 +1,40 @@
+require 'rails_helper'
+
+RSpec.describe "reports/_report", type: :view do
+ let(:current_admin) { create(:user_admin) }
+ it "renders a report table" do
+ report = create(:report)
+ assign(:report, report)
+
+ render(locals: { report: report })
+
+ expect(rendered).to have_selector("table")
+ expect(rendered).to have_selector("th > strong", text: report.grouping.name)
+ expect(rendered).to have_selector("th", text: report.collection.name)
+
+ report.rounds.each do |round|
+ expect(rendered).to have_selector("th", text: round.participant.full_name)
+ expect(rendered).to have_selector("th > strong", text: I18n.t("activerecord.models.equation").pluralize)
+ expect(rendered).to have_selector("th", text: I18n.t("activerecord.attributes.equation.position_a"))
+ expect(rendered).to have_selector("th", text: I18n.t("activerecord.attributes.equation.operator"))
+ expect(rendered).to have_selector("th", text: I18n.t("activerecord.attributes.equation.position_b"))
+ expect(rendered).to have_selector("th", text: I18n.t("activerecord.attributes.equation.position_c"))
+ expect(rendered).to have_selector("th", text: I18n.t("activerecord.attributes.equation.unknown_position"))
+ expect(rendered).to have_selector("th > strong", text: I18n.t("activerecord.models.answer").pluralize)
+ expect(rendered).to have_selector("th", text: I18n.t("activerecord.attributes.answer.answer_value"))
+ expect(rendered).to have_selector("th", text: I18n.t("activerecord.attributes.answer.correct_answer"))
+ expect(rendered).to have_selector("th", text: I18n.t("activerecord.attributes.answer.time"))
+
+ round.answers.each do |answer|
+ expect(rendered).to have_selector("td", text: answer.equation.position_a)
+ expect(rendered).to have_selector("td", text: answer.equation.operator)
+ expect(rendered).to have_selector("td", text: answer.equation.position_b)
+ expect(rendered).to have_selector("td", text: answer.equation.position_c)
+ expect(rendered).to have_selector("td", text: answer.equation.unknown_position)
+ expect(rendered).to have_selector("td", text: answer.answer_value)
+ expect(rendered).to have_selector("td", text: answer.correct_answer ? "✔️" : "❌")
+ expect(rendered).to have_selector("td", text: answer.formatted_time)
+ end
+ end
+ end
+end
diff --git a/spec/views/reports/index.html.erb_spec.rb b/spec/views/reports/index.html.erb_spec.rb
new file mode 100644
index 0000000..cae0c5f
--- /dev/null
+++ b/spec/views/reports/index.html.erb_spec.rb
@@ -0,0 +1,34 @@
+require 'rails_helper'
+
+RSpec.describe "reports/index", type: :view do
+ let(:current_admin) { create(:user_admin) }
+
+ before(:each) do
+ assign(:reports, [
+ Report.create!(
+ user_admin: current_admin,
+ collection: create(:collection),
+ grouping: create(:grouping)
+ ),
+ Report.create!(
+ user_admin: current_admin,
+ collection: create(:collection),
+ grouping: create(:grouping)
+ )
+ ])
+ end
+
+ it "renders a list of reports" do
+ sign_in current_admin
+ stub_template("shared/_header.html.erb" => "This content")
+
+ render
+
+ expect(rendered).to have_selector("main", class: "background")
+ expect(rendered).to have_selector("section", class: "blur")
+ expect(rendered).to have_rendered("shared/_header")
+ expect(rendered).to have_selector("h2", text: I18n.t("activerecord.models.report").pluralize)
+ expect(rendered).to have_rendered("reports/_report")
+ expect(rendered).to have_link(I18n.t("reports.show"), href: report_path(Report.last))
+ end
+end
diff --git a/spec/views/reports/show.html.erb_spec.rb b/spec/views/reports/show.html.erb_spec.rb
new file mode 100644
index 0000000..fe4eb95
--- /dev/null
+++ b/spec/views/reports/show.html.erb_spec.rb
@@ -0,0 +1,27 @@
+require 'rails_helper'
+
+RSpec.describe "reports/show", type: :view do
+ let(:current_admin) { create(:user_admin) }
+
+ before(:each) do
+ assign(:report, Report.create!(
+ user_admin: current_admin,
+ collection: create(:collection),
+ grouping: create(:grouping)
+ ))
+ end
+
+ it "renders partial reports/report" do
+ sign_in current_admin
+ stub_template("shared/_header.html.erb" => "This content")
+
+ render
+
+ expect(rendered).to have_selector("main", class: "background")
+ expect(rendered).to have_selector("section", class: "blur")
+ expect(rendered).to have_rendered("shared/_header")
+ expect(rendered).to have_rendered("reports/_report")
+ expect(rendered).to have_link(I18n.t("reports.back"), href: reports_path)
+ expect(rendered).to have_selector("button", text: I18n.t("reports.delete"), class: "btn crimson")
+ end
+end