diff --git a/app/commands/track/trophy/reseed_variable.rb b/app/commands/track/trophy/reseed_variable.rb index 5e408c014b..1a2e2cf847 100644 --- a/app/commands/track/trophy/reseed_variable.rb +++ b/app/commands/track/trophy/reseed_variable.rb @@ -9,6 +9,7 @@ def call VARIABLE_TROPHIES = [ Track::Trophies::CompletedFiveHardExercisesTrophy, - Track::Trophies::CompletedLearningModeTrophy + Track::Trophies::CompletedLearningModeTrophy, + Track::Trophies::ReadFiveApproachesTrophy ].freeze end diff --git a/app/commands/user_track/viewed_exercise_approach/create.rb b/app/commands/user_track/viewed_exercise_approach/create.rb index 2a88d3f3f0..b4f374d705 100644 --- a/app/commands/user_track/viewed_exercise_approach/create.rb +++ b/app/commands/user_track/viewed_exercise_approach/create.rb @@ -4,6 +4,8 @@ class UserTrack::ViewedExerciseApproach::Create initialize_with :user, :track, :approach def call - ::UserTrack::ViewedExerciseApproach.create_or_find_by!(user:, track:, approach:) + ::UserTrack::ViewedExerciseApproach.create_or_find_by!(user:, track:, approach:).tap do + AwardTrophyJob.perform_later(user, track, :read_five_approaches) + end end end diff --git a/app/images/graphics/trophy-read-five-approaches.svg b/app/images/graphics/trophy-read-five-approaches.svg new file mode 100644 index 0000000000..58ad7af259 --- /dev/null +++ b/app/images/graphics/trophy-read-five-approaches.svg @@ -0,0 +1 @@ + diff --git a/app/models/track/trophies/read_five_approaches_trophy.rb b/app/models/track/trophies/read_five_approaches_trophy.rb new file mode 100644 index 0000000000..93325086ae --- /dev/null +++ b/app/models/track/trophies/read_five_approaches_trophy.rb @@ -0,0 +1,38 @@ +class Track::Trophies::ReadFiveApproachesTrophy < Track::Trophy + def self.valid_track_slugs + Track.active.where(id: + Exercise::Approach.joins(:exercise). + where('exercises.track_id = tracks.id'). + having("count(*) >= ?", NUM_APPROACHES). + select(:track_id)).pluck(:slug) + end + + def name(_) = "Digging Deeper" + def icon = 'trophy-read-five-approaches' + def order = 7 + + # rubocop:disable Layout/LineLength + def criteria(track) + "Awarded for reading %i %s approaches" % { + num_approaches: NUM_APPROACHES, + track_title: track.title + } + end + + def success_message(track) + "Congratulations on reading %i approaches in %s. Learning how to approach an exercise in different ways is a fantastic way to master a language!" % { + num_approaches: NUM_APPROACHES, + track_title: track.title + } + end + # rubocop:enable Layout/LineLength + + def award?(user, track) + UserTrack::ViewedExerciseApproach. + where(user:, track:). + count >= NUM_APPROACHES + end + + NUM_APPROACHES = 5 + private_constant :NUM_APPROACHES +end diff --git a/test/commands/track/trophy/reseed_variable_test.rb b/test/commands/track/trophy/reseed_variable_test.rb index 57cf4c6fb3..9dcff8eb38 100644 --- a/test/commands/track/trophy/reseed_variable_test.rb +++ b/test/commands/track/trophy/reseed_variable_test.rb @@ -6,13 +6,14 @@ class Track::Trophy::ReseedVariableTest < ActiveSupport::TestCase create :iterated_twenty_exercises_trophy create :completed_five_hard_exercises_trophy create :completed_learning_mode_trophy + create :read_five_approaches_trophy Track::Trophies::IteratedTwentyExercisesTrophy.any_instance.expects(:reseed!).never Track::Trophies::MentoredTrophy.any_instance.expects(:reseed!).never - Track::Trophy::ReseedVariable::VARIABLE_TROPHIES.each do |trophy| - trophy.any_instance.expects(:reseed!).once - end + Track::Trophies::CompletedFiveHardExercisesTrophy.any_instance.expects(:reseed!).once + Track::Trophies::CompletedLearningModeTrophy.any_instance.expects(:reseed!).once + Track::Trophies::ReadFiveApproachesTrophy.any_instance.expects(:reseed!).once Track::Trophy::ReseedVariable.() end diff --git a/test/commands/user_track/viewed_exercise_approach/create_test.rb b/test/commands/user_track/viewed_exercise_approach/create_test.rb index 3d350add9e..af65415c3e 100644 --- a/test/commands/user_track/viewed_exercise_approach/create_test.rb +++ b/test/commands/user_track/viewed_exercise_approach/create_test.rb @@ -20,4 +20,26 @@ class UserTrack::ViewedExerciseApproach::CreateTest < ActiveSupport::TestCase assert_equal track, actual.track assert_equal approach, actual.approach end + + test "awards read five approaches trophy when now having read five" do + user = create :user + track = create :track + exercise = create(:practice_exercise, track:) + + perform_enqueued_jobs do + create_list(:exercise_approach, 4, exercise:) do |approach| + create(:user_track_viewed_exercise_approach, user:, track:, approach:) + end + end + + refute_includes user.reload.trophies.map(&:class), Track::Trophies::ReadFiveApproachesTrophy + + approach = create(:exercise_approach, exercise:) + + perform_enqueued_jobs do + UserTrack::ViewedExerciseApproach::Create.(user, track, approach) + end + + assert_includes user.reload.trophies.map(&:class), Track::Trophies::ReadFiveApproachesTrophy + end end diff --git a/test/factories/track/trophies.rb b/test/factories/track/trophies.rb index 3af024da97..1338c4d9ee 100644 --- a/test/factories/track/trophies.rb +++ b/test/factories/track/trophies.rb @@ -6,7 +6,7 @@ completed_all_exercises completed_fifty_percent_of_exercises completed_twenty_exercises mentored iterated_twenty_exercises read_fifty_community_solutions completed_five_hard_exercises - completed_learning_mode functional + completed_learning_mode functional read_five_approaches ].each do |type| factory "#{type}_trophy", class: "Track::Trophies::#{type.to_s.camelize}Trophy" do end diff --git a/test/models/track/trophies/read_fifty_community_solutions_test.rb b/test/models/track/trophies/read_fifty_community_solutions_trophy_test.rb similarity index 100% rename from test/models/track/trophies/read_fifty_community_solutions_test.rb rename to test/models/track/trophies/read_fifty_community_solutions_trophy_test.rb diff --git a/test/models/track/trophies/read_five_approaches_trophy_test.rb b/test/models/track/trophies/read_five_approaches_trophy_test.rb new file mode 100644 index 0000000000..91084db75e --- /dev/null +++ b/test/models/track/trophies/read_five_approaches_trophy_test.rb @@ -0,0 +1,66 @@ +require "test_helper" + +class Track::Trophies::ReadFiveApproachesTrophyTest < ActiveSupport::TestCase + test "award?" do + user = create :user + other_user = create :user + track = create :track + other_track = create :track, slug: 'kotlin' + trophy = create :read_five_approaches_trophy + + exercise = create(:practice_exercise, :random_slug, track:) + other_exercise = create(:practice_exercise, :random_slug, track: other_track) + + # Don't award trophy if no approaches have been read + refute trophy.award?(user, track) + + # Don't award trophy if less than five approaches + create_list(:exercise_approach, 4, :random, exercise:) do |approach| + create(:user_track_viewed_exercise_approach, user:, track:, approach:) + end + refute trophy.award?(user, track) + + # Don't award trophy if other user has read five approaches in the track + create_list(:exercise_approach, 6, :random, exercise:) do |approach| + create(:user_track_viewed_exercise_approach, user: other_user, track:, approach:) + end + refute trophy.award?(user, track) + + # Don't count viewed approaches for other track + create_list(:exercise_approach, 7, :random, exercise: other_exercise) do |approach| + create(:user_track_viewed_exercise_approach, user:, track: other_track, approach:) + end + refute trophy.award?(user, track) + + # Award trophy when five approaches solutions in the track have been read + create(:exercise_approach, exercise:) do |approach| + create(:user_track_viewed_exercise_approach, user:, track:, approach:) + end + assert trophy.award?(user, track) + end + + test "reseed! sets valid_track_slugs to tracks with at least five approaches" do + trophy = create :read_five_approaches_trophy + + # Track is not valid if it doesn't have any approaches + track = create :track, :random_slug + trophy.reseed! + assert_empty trophy.valid_track_slugs + + # Four approaches is not enough + create_list(:exercise_approach, 4, exercise: create(:practice_exercise, :random_slug, track:)) + trophy.reseed! + assert_empty trophy.valid_track_slugs + + # Five approaches is enough + create(:exercise_approach, exercise: create(:practice_exercise, :random_slug, track:)) + trophy.reseed! + assert_equal [track.slug], trophy.valid_track_slugs + + # Include all tracks with five approaches + other_track = create :track, :random_slug + create_list(:exercise_approach, 6, exercise: create(:practice_exercise, :random_slug, track: other_track)) + trophy.reseed! + assert_equal [track.slug, other_track.slug].sort, trophy.valid_track_slugs.sort + end +end