Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@
/config/master.key

/config/database.yml

/vendor/bundle
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--require spec_helper
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,17 @@ group :development do

# Speed up commands on slow machines / big apps [https://github.com/rails/spring]
# gem "spring"
gem 'rspec-rails', '~> 4.1.0'
end

group :test do
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
gem 'capybara'
gem 'selenium-webdriver'
gem 'webdrivers'
gem 'rspec-rails', '~> 4.1.0'
gem 'shoulda-matchers', '~> 5.0'
gem 'shoulda-callback-matchers', '~> 1.1.1'
gem 'rails-controller-testing'
gem 'factory_bot_rails'
end
36 changes: 36 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,14 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
diff-lcs (1.5.0)
erubi (1.12.0)
execjs (2.8.1)
factory_bot (6.2.1)
activesupport (>= 5.0.0)
factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0)
railties (>= 5.0.0)
ffi (1.15.5-x64-mingw-ucrt)
globalid (1.1.0)
activesupport (>= 5.0)
Expand Down Expand Up @@ -203,6 +209,10 @@ GEM
activesupport (= 7.0.4.3)
bundler (>= 1.15.0)
railties (= 7.0.4.3)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
Expand All @@ -223,6 +233,23 @@ GEM
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.2.5)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-mocks (3.12.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (4.1.2)
actionpack (>= 4.2)
activesupport (>= 4.2)
railties (>= 4.2)
rspec-core (~> 3.10)
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
rspec-support (3.12.1)
ruby-vips (2.1.4)
ffi (~> 1.12)
rubyzip (2.3.2)
Expand All @@ -238,6 +265,10 @@ GEM
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
shoulda-callback-matchers (1.1.4)
activesupport (>= 3)
shoulda-matchers (5.3.0)
activesupport (>= 5.2.0)
sprockets (4.2.0)
concurrent-ruby (~> 1.0)
rack (>= 2.2.4, < 4)
Expand Down Expand Up @@ -288,6 +319,7 @@ DEPENDENCIES
capybara
debug
devise (~> 4.9.2)
factory_bot_rails
image_processing (~> 1.2)
importmap-rails (~> 1.1.6)
jbuilder (~> 2.11.5)
Expand All @@ -296,9 +328,13 @@ DEPENDENCIES
pg (~> 1.1)
puma (~> 5.0)
rails (~> 7.0.4, >= 7.0.4.3)
rails-controller-testing
ransack!
rspec-rails (~> 4.1.0)
sassc-rails (~> 2.1.2)
selenium-webdriver
shoulda-callback-matchers (~> 1.1.1)
shoulda-matchers (~> 5.0)
sprockets-rails (~> 3.4.2)
stimulus-rails (~> 1.2.1)
stripe (~> 8.5.0)
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/admin/quizzes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def feature
private

def quiz_params
params.require(:quiz).permit(:title, :description, :level, :time_limit_in_minutes, :featured_at, :quiz_banner, genre_ids: [], quiz_questions_attributes: [:id, :question_id, :active])
params.require(:quiz).permit(:title, :description, :level, :time_limit_in_minutes, :featured_at, :quiz_banner, :amount, genre_ids: [], quiz_questions_attributes: [:id, :question_id, :active])
end

def set_quiz
Expand Down
2 changes: 1 addition & 1 deletion app/models/question.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def self.ransackable_associations(auth_object = nil)
private

def only_one_correct_option
unless question_options.correct.count == 1
unless question_options.map(&:correct).count(true) == 1
errors.add :base, 'Choose exactly one correct option.'
end
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/question_option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class QuestionOption < ApplicationRecord
private

def presence_of_either_data_or_image
unless option_image.present? && data.present?
if option_image.blank? && data.blank?
errors.add :base, 'Both data and image can\'t be blank'
end
end
Expand Down
5 changes: 3 additions & 2 deletions app/models/quiz.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ class Quiz < ApplicationRecord
validates_length_of :title_word_count, minimum: 5, message: 'should be at least 5', if: :title?
validates_length_of :description_word_count, minimum: 15, if: :description?
validates :description, allow_blank: true, format: {
without: Quizathon::URL_REGEXP
without: Quizathon::URL_REGEXP,
message: 'invalid format for description'
}
validates :amount, numericality: { greater_than: 0 }

before_validation :set_time_limit_in_seconds, if: -> { time_limit_in_minutes.present? }
before_validation ActivableCallbacks, on: :update
after_save_commit :schedule_mail_if_featured, if: :featured?
after_save_commit :schedule_mail_if_featured, if: :featured_at?

scope :active, -> { where(active: true) }

Expand Down
4 changes: 4 additions & 0 deletions app/views/admin/quizzes/_edit_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
<br />
<%= form.file_field :quiz_banner %>
</div>
<div class="field">
<%= form.label :amount %>
<%= form.number_field :amount %>
</div>
<%= form.fields_for :quiz_questions do |quiz_questions_form| %>
<%= render 'quiz_question_selector', f: quiz_questions_form, object: quiz_questions_form.object %>
<% end %>
Expand Down
4 changes: 4 additions & 0 deletions app/views/admin/quizzes/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
<br />
<%= form.file_field :quiz_banner %>
</div>
<div class="field">
<%= form.label :amount %>
<%= form.number_field :amount %>
</div>
<div class="field action">
<%= form.submit class: 'btn btn-dark' %>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/views/quizzes/_child_comment.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
<%= form.text_field :data %>
<%= form.hidden_field :parent_comment_id, value: parent_comment_id %>
<%= form.submit 'Reply', class: 'btn btn-info' %>
<%= link_to 'Remove Comment field', '#', class: 'remove_fields', data: {id: form.object.object_id}, class: 'btn btn-secondary' %>
<%= link_to 'Remove Comment field', '#', class: 'remove_fields btn btn-secondary', data: {id: form.object.object_id} %>
</fieldset>
4 changes: 3 additions & 1 deletion config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
config.action_controller.allow_forgery_protection = false

# Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test
config.active_storage.service = :local

config.action_mailer.perform_caching = false

Expand All @@ -43,6 +43,8 @@
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr

Expand Down
13 changes: 13 additions & 0 deletions spec/factories/question.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FactoryBot.define do
factory :question do
title { 'a new css question' }
score { 3 }

before(:create) do |question|
3.times do
question.question_options << FactoryBot.build(:question_option, question: question)
end
question.question_options << FactoryBot.build(:question_option, question: question, correct: true)
end
end
end
7 changes: 7 additions & 0 deletions spec/factories/question_option.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FactoryBot.define do
factory :question_option do
data { 'abc' }
correct { false }
association :question
end
end
9 changes: 9 additions & 0 deletions spec/factories/quiz.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FactoryBot.define do
factory :quiz do
title { 'A random new quiz for testing' }
time_limit_in_minutes { 2 }
amount { 500 }
currency_code { 'INR' }
active { false }
end
end
6 changes: 6 additions & 0 deletions spec/factories/quiz_question.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FactoryBot.define do
factory :quiz_question do
association :quiz
association :question
end
end
16 changes: 16 additions & 0 deletions spec/factories/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FactoryBot.define do
factory :user do
email { "[email protected]" }
password { "abcdef" }
password_confirmation { "abcdef" }
blocked { false }

trait :admin do
email { "[email protected]" }
password { 'Adminn' }
password_confirmation { 'Adminn' }
role { 'admin' }
blocked { false }
end
end
end
136 changes: 136 additions & 0 deletions spec/models/quiz_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
require 'rails_helper'

RSpec.describe Quiz, type: :model do
subject {
described_class.new(title: 'A random new quiz for testing',
description: 'This random new quiz is for testing purpose only to check is associations and validations are properly checked.',
time_limit_in_seconds: 120,
amount: 500,
currency_code: 'INR')
}

describe "Validations" do
it 'is valid with valid attributes' do
expect(subject).to be_valid
end

it { should validate_presence_of(:title) }

it { should validate_numericality_of(:time_limit_in_seconds) }

it 'is not valid without 5 length title' do
subject.title = 'a b c d'
expect(subject).to_not be_valid
end

it 'is not valid without 15 length description' do
subject.description = 'a b c d e f g h i j k l m n'
expect(subject).to_not be_valid
end

it { should_not allow_value('https://foo.com').for(:description).with_message('invalid format for description') }

it { should validate_numericality_of(:amount) }
end

describe "Associations" do
it { should have_and_belong_to_many(:genres) }
it { should have_many(:quiz_questions).dependent(:destroy) }
it { should have_many(:questions).through(:quiz_questions) }
it { should have_many(:active_quiz_questions).conditions(active: true).class_name('QuizQuestion') }
it { should have_many(:active_questions).through(:active_quiz_questions).conditions(active: true).source(:question) }
it { should accept_nested_attributes_for(:quiz_questions) }
it { should have_one_attached(:quiz_banner) }
it { should have_many(:comments).dependent(:destroy) }
it { should have_many(:ratings).dependent(:destroy) }
it { should have_many(:quiz_runners) }
it { should have_many(:started_quiz_runners).conditions(status: 'started').class_name('QuizRunner') }
it { should have_many(:users).through(:quiz_runners) }
it { should have_many(:payments).dependent(:destroy) }
end

describe 'callbacks' do
it { is_expected.to callback(:set_time_limit_in_seconds).before(:validation) }
it { is_expected.to callback(ActivableCallbacks).before(:validation).on(:update) }
it { is_expected.to callback(:schedule_mail_if_featured).after(:commit) }
end

describe 'Enums' do
it {
should define_enum_for(:level).with_values(
beginner: 1, easy: 2, moderate: 3, difficult: 4, advance: 5).backed_by_column_of_type(:integer)
}
end

describe 'scopes' do
describe 'active' do
context 'when subject is not active' do
it 'doesnot return subject' do
expect(described_class.active).not_to include(subject)
end
end
context 'when a quiz is active' do
let(:quiz) { create(:quiz, active: true) }

it 'it returns the quiz' do
expect(described_class.active).to include(quiz)
end
end
end

describe 'featured' do
context 'when featured_at is null' do
it 'should not return subject' do
expect(described_class.featured).not_to include(subject)
end
end
context 'when quiz is featured' do
let!(:quiz) { create(:quiz, active: true, featured_at: Time.current) }
it 'should return quiz' do
expect(described_class.featured).to include(quiz)
end
end
context 'when quiz was featured a day before' do
let(:quiz) { create(:quiz, active: true, featured_at: 1.day.ago) }
it 'should return quiz' do
expect(described_class.featured).to include(quiz)
end
end
context 'when quiz was featured a day minus 1 second before' do
let(:quiz) { create(:quiz, active: true, featured_at: 1.day.ago - 1.second) }
it 'should not return quiz' do
expect(described_class.featured).not_to include(quiz)
end
end
context 'when quiz is featured for tomorrow' do
let(:quiz) { create(:quiz, active: true, featured_at: 1.day.after) }
it 'should not return quiz' do
expect(described_class.featured).not_to include(quiz)
end
end
end

describe 'instance methods' do
let(:quiz) { create(:quiz) }

describe 'to param' do
it 'should return slug' do
expect(quiz.to_param).to eq(quiz.slug)
end
end

describe 'average rating' do
it 'should return average rating' do
expect(quiz.average_rating).to eq(quiz.ratings.average(:value))
end
end

describe 'feature now' do
it 'should change value of featured_at column' do
quiz.feature_now
expect(quiz.featured_at).to be_between(Time.current - 1.minute, Time.current)
end
end
end
end
end
Loading