diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..5f277000 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_size = 2 +indent_style = space +trim_trailing_whitespace = true diff --git a/Gemfile b/Gemfile index c1d9ca29..15ef0232 100644 --- a/Gemfile +++ b/Gemfile @@ -54,6 +54,7 @@ gem 'activerecord-import' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platform: :mri + gem 'factory_bot_rails' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 2aeb3474..a5f0bf87 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -78,6 +78,11 @@ GEM diff-lcs (1.4.4) erubi (1.10.0) execjs (2.8.1) + factory_bot (6.2.0) + activesupport (>= 5.0.0) + factory_bot_rails (6.2.0) + factory_bot (~> 6.2.0) + railties (>= 5.0.0) ffi (1.15.4) friendly_id (5.1.0) activerecord (>= 4.0.0) @@ -258,6 +263,7 @@ DEPENDENCIES capybara database_cleaner devise + factory_bot_rails friendly_id (~> 5.1.0) haml jbuilder (~> 2.5) diff --git a/app/assets/stylesheets/colors.scss b/app/assets/stylesheets/colors.scss index f9a6a72a..cfabfc5e 100644 --- a/app/assets/stylesheets/colors.scss +++ b/app/assets/stylesheets/colors.scss @@ -17,10 +17,14 @@ $growth: #E0BA9A; $approval: #D0DD86; $ideal: #C0FF73; -.red { +.color-red { color: $red; } +.color-black { + color: $black; +} + .bg-beige { background-color: $beige; } @@ -29,6 +33,10 @@ $ideal: #C0FF73; background-color: $white; } +.fill-black { + fill: $black; +} + .fill-warning { fill: $warning; } @@ -48,3 +56,15 @@ $ideal: #C0FF73; .fill-ideal { fill: $ideal; } + +.stroke-black { + stroke: $black; +} + +.stroke-gray-1 { + stroke: $gray-1; +} + +.stroke-gray-2 { + stroke: $gray-2; +} diff --git a/app/controllers/browse_controller.rb b/app/controllers/browse_controller.rb new file mode 100644 index 00000000..0dc69cdd --- /dev/null +++ b/app/controllers/browse_controller.rb @@ -0,0 +1,23 @@ +class BrowseController < ApplicationController + def show + @category = CategoryPresenter.new( + category: SqmCategory.find_by_name('Teachers & Leadership'), + academic_year: academic_year, + school: school, + ) + end + + private + + def school + @school ||= School.find_by_slug school_slug + end + + def school_slug + params[:school_id] + end + + def academic_year + @academic_year ||= AcademicYear.find_by_range params[:year] + end +end diff --git a/app/models/measure.rb b/app/models/measure.rb index a7fc4c6b..50c724f5 100644 --- a/app/models/measure.rb +++ b/app/models/measure.rb @@ -1,44 +1,6 @@ class Measure < ActiveRecord::Base belongs_to :subcategory + has_many :survey_items - Zone = Struct.new(:low_benchmark, :high_benchmark, :type) do - def includes_score?(score) - score > low_benchmark and score < high_benchmark - end - end - - def warning_zone - Zone.new(1, watch_low_benchmark, :warning) - end - - def watch_zone - Zone.new(watch_low_benchmark, growth_low_benchmark, :watch) - end - - def growth_zone - Zone.new(growth_low_benchmark, approval_low_benchmark, :growth) - end - - def approval_zone - Zone.new(approval_low_benchmark, ideal_low_benchmark, :approval) - end - - def ideal_zone - Zone.new(ideal_low_benchmark, 5.0, :ideal) - end - - def zone_for_score(score) - case score - when ideal_zone.low_benchmark..ideal_zone.high_benchmark - ideal_zone - when approval_zone.low_benchmark..approval_zone.high_benchmark - approval_zone - when growth_zone.low_benchmark..growth_zone.high_benchmark - growth_zone - when watch_zone.low_benchmark..watch_zone.high_benchmark - watch_zone - else - warning_zone - end - end + has_many :survey_item_responses, through: :survey_items end diff --git a/app/models/sqm_category.rb b/app/models/sqm_category.rb index 3b2841c4..f07b455a 100644 --- a/app/models/sqm_category.rb +++ b/app/models/sqm_category.rb @@ -1,3 +1,3 @@ class SqmCategory < ActiveRecord::Base - + has_many :subcategories end diff --git a/app/models/subcategory.rb b/app/models/subcategory.rb index 1e1afcc9..5bc2a454 100644 --- a/app/models/subcategory.rb +++ b/app/models/subcategory.rb @@ -1,3 +1,5 @@ class Subcategory < ActiveRecord::Base belongs_to :sqm_category + + has_many :measures end diff --git a/app/models/survey_item.rb b/app/models/survey_item.rb index 1a5f5c52..bada2753 100644 --- a/app/models/survey_item.rb +++ b/app/models/survey_item.rb @@ -1,3 +1,4 @@ class SurveyItem < ActiveRecord::Base belongs_to :measure + has_many :survey_item_responses end diff --git a/app/models/survey_item_response.rb b/app/models/survey_item_response.rb index 9776a2d1..92a4428f 100644 --- a/app/models/survey_item_response.rb +++ b/app/models/survey_item_response.rb @@ -2,4 +2,7 @@ class SurveyItemResponse < ActiveRecord::Base belongs_to :academic_year belongs_to :school belongs_to :survey_item + + scope :for_measures, ->(measures) { joins(:survey_item).where('survey_items.measure_id': measures.map(&:id)) } + scope :for_measure, ->(measure) { joins(:survey_item).where('survey_items.measure_id': measure.id) } end diff --git a/app/presenters/category_presenter.rb b/app/presenters/category_presenter.rb new file mode 100644 index 00000000..6a8ff999 --- /dev/null +++ b/app/presenters/category_presenter.rb @@ -0,0 +1,21 @@ +class CategoryPresenter + def initialize(category:, academic_year:, school:) + @category = category + @academic_year = academic_year + @school = school + end + + def name + @category.name + end + + def subcategories + @category.subcategories.map do |subcategory| + SubcategoryPresenter.new( + subcategory: subcategory, + academic_year: @academic_year, + school: @school, + ) + end + end +end diff --git a/app/presenters/gauge_presenter.rb b/app/presenters/gauge_presenter.rb new file mode 100644 index 00000000..96c78135 --- /dev/null +++ b/app/presenters/gauge_presenter.rb @@ -0,0 +1,34 @@ +class GaugePresenter + def initialize(scale:, score:) + @scale = scale + @score = score + end + + def title + zone.type.to_s.capitalize + end + + def color_class + "fill-#{zone.type}" + end + + def score_percentage + percentage_for @score + end + + def key_benchmark_percentage + percentage_for @scale.approval_zone.low_benchmark + end + + private + + def zone + @scale.zone_for_score(@score) + end + + def percentage_for(number) + scale_minimum = @scale.warning_zone.low_benchmark + scale_maximum = @scale.ideal_zone.high_benchmark + (number - scale_minimum) / (scale_maximum - scale_minimum) + end +end diff --git a/app/presenters/measure_graph_row_presenter.rb b/app/presenters/measure_graph_row_presenter.rb index 7bb57a8d..ba984aee 100644 --- a/app/presenters/measure_graph_row_presenter.rb +++ b/app/presenters/measure_graph_row_presenter.rb @@ -26,7 +26,7 @@ class MeasureGraphRowPresenter when :ideal, :approval "50%" else - "#{((0.5 - bar_width_percentage) * 100).round(2)}%" + "#{((0.5 - bar_width_percentage) * 100).abs.round(2)}%" end end @@ -66,6 +66,12 @@ class MeasureGraphRowPresenter end def zone - @measure.zone_for_score(@score) + scale = Scale.new( + watch_low_benchmark: @measure.watch_low_benchmark, + growth_low_benchmark: @measure.growth_low_benchmark, + approval_low_benchmark: @measure.approval_low_benchmark, + ideal_low_benchmark: @measure.ideal_low_benchmark, + ) + scale.zone_for_score(@score) end end diff --git a/app/presenters/scale.rb b/app/presenters/scale.rb new file mode 100644 index 00000000..c520e7ef --- /dev/null +++ b/app/presenters/scale.rb @@ -0,0 +1,45 @@ +class Scale + def initialize(watch_low_benchmark:, growth_low_benchmark:, approval_low_benchmark:, ideal_low_benchmark:) + @watch_low_benchmark = watch_low_benchmark + @growth_low_benchmark = growth_low_benchmark + @approval_low_benchmark = approval_low_benchmark + @ideal_low_benchmark = ideal_low_benchmark + end + + Zone = Struct.new(:low_benchmark, :high_benchmark, :type) + + def warning_zone + Zone.new(1, @watch_low_benchmark, :warning) + end + + def watch_zone + Zone.new(@watch_low_benchmark, @growth_low_benchmark, :watch) + end + + def growth_zone + Zone.new(@growth_low_benchmark, @approval_low_benchmark, :growth) + end + + def approval_zone + Zone.new(@approval_low_benchmark, @ideal_low_benchmark, :approval) + end + + def ideal_zone + Zone.new(@ideal_low_benchmark, 5.0, :ideal) + end + + def zone_for_score(score) + case score + when ideal_zone.low_benchmark..ideal_zone.high_benchmark + ideal_zone + when approval_zone.low_benchmark..approval_zone.high_benchmark + approval_zone + when growth_zone.low_benchmark..growth_zone.high_benchmark + growth_zone + when watch_zone.low_benchmark..watch_zone.high_benchmark + watch_zone + else + warning_zone + end + end +end diff --git a/app/presenters/subcategory_presenter.rb b/app/presenters/subcategory_presenter.rb new file mode 100644 index 00000000..202629d7 --- /dev/null +++ b/app/presenters/subcategory_presenter.rb @@ -0,0 +1,34 @@ +class SubcategoryPresenter + def initialize(subcategory:, academic_year:, school:) + @subcategory = subcategory + @academic_year = academic_year + @school = school + end + + def name + @subcategory.name + end + + def gauge_presenter + average_score = SurveyItemResponse.for_measures(measures) + .where(academic_year: @academic_year, school: @school) + .average(:likert_score) + + GaugePresenter.new(scale: scale, score: average_score) + end + + private + + def scale + Scale.new( + watch_low_benchmark: measures.map(&:watch_low_benchmark).average, + growth_low_benchmark: measures.map(&:growth_low_benchmark).average, + approval_low_benchmark: measures.map(&:approval_low_benchmark).average, + ideal_low_benchmark: measures.map(&:ideal_low_benchmark).average, + ) + end + + def measures + @measures ||= @subcategory.measures + end +end diff --git a/app/services/survey_response_aggregator.rb b/app/services/survey_response_aggregator.rb index 66e2064b..99ff53a8 100644 --- a/app/services/survey_response_aggregator.rb +++ b/app/services/survey_response_aggregator.rb @@ -1,11 +1,10 @@ +# TODO inline me pls class SurveyResponseAggregator # Returns an average score for all SurveyItemResponses for the given AcademicYear, School, and Measure def self.score(academic_year:, school:, measure:) - SurveyItemResponse + SurveyItemResponse.for_measure(measure) .where(academic_year: academic_year, school: school) - .joins(:survey_item).where('survey_items.measure_id': measure.id) - .map { |survey_item_response| survey_item_response.likert_score } - .average + .average(:likert_score) end # Returns an array of SurveyItemResponses for the given AcademicYear, School, and Measure diff --git a/app/views/browse/_gauge_graph.html.erb b/app/views/browse/_gauge_graph.html.erb new file mode 100644 index 00000000..06f54c6c --- /dev/null +++ b/app/views/browse/_gauge_graph.html.erb @@ -0,0 +1,60 @@ +<% outer_radius = 100 %> +<% inner_radius = 50 %> +<% stroke_width = 1 %> +<% key_benchmark_indicator_height = 10 %> +<% scale = -Math::PI %> + + + + + + <%= gauge.title %> + + + + + + + + diff --git a/app/views/browse/show.html.erb b/app/views/browse/show.html.erb new file mode 100644 index 00000000..d894d1fd --- /dev/null +++ b/app/views/browse/show.html.erb @@ -0,0 +1,7 @@ +

<%= @category.name %>

+ +<% @category.subcategories.each do |subcategory| %> +

<%= subcategory.name %>

+ <%= render partial: "gauge_graph", locals: { gauge: subcategory.gauge_presenter } %> +<% end %> + diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb index 31c59a99..22f85075 100644 --- a/app/views/dashboard/index.html.erb +++ b/app/views/dashboard/index.html.erb @@ -1,3 +1,5 @@ +Browse +

<%= @school.name %>

@@ -28,7 +30,7 @@
-

Distance from benchmark

+

Distance from benchmark

This graph shows how much a score is above or below the benchmark of any given scale.

diff --git a/config/routes.rb b/config/routes.rb index 6f875801..939b60d2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,6 +5,7 @@ Rails.application.routes.draw do resources :districts do resources :schools, only: [:index, :show] do resources :dashboard, only: [:index] + resources :browse, only: [:show] end end diff --git a/spec/factories.rb b/spec/factories.rb new file mode 100644 index 00000000..d99f7df7 --- /dev/null +++ b/spec/factories.rb @@ -0,0 +1,50 @@ +FactoryBot.define do + factory :sqm_category do + name { "A category" } + end + + factory :subcategory do + name { "A subcategory" } + sqm_category + + factory :subcategory_with_measures do + transient do + measures_count { 2 } + end + after(:create) do |subcategory, evaluator| + create_list(:measure, evaluator.measures_count, subcategory: subcategory) + end + end + end + + factory :measure do + measure_id { rand.to_s } + name { 'A Measure' } + watch_low_benchmark { 2.0 } + growth_low_benchmark { 3.0 } + approval_low_benchmark { 4.0 } + ideal_low_benchmark { 4.5 } + subcategory + end + + factory :survey_item do + survey_item_id { rand.to_s } + measure + end + + factory :academic_year do + range { '2050-51' } + end + + factory :school do + name { "#{rand} School" } + end + + factory :survey_item_response do + likert_score { 3 } + response_id { rand.to_s } + academic_year + school + survey_item + end +end diff --git a/spec/features/school_dashboard_feature_spec.rb b/spec/features/school_dashboard_feature_spec.rb index 2c0c9f3b..937a59c1 100644 --- a/spec/features/school_dashboard_feature_spec.rb +++ b/spec/features/school_dashboard_feature_spec.rb @@ -5,16 +5,21 @@ feature 'School dashboard', type: feature do let(:school) { School.find_by_slug 'winchester-high-school' } let(:school_in_same_district) { School.find_by_slug 'muraco-elementary-school' } + let(:category) { SqmCategory.find_by_name('Teachers & Leadership') } + let(:subcategory) { Subcategory.find_by_name('Teachers & The Teaching Environment') } + let(:measures_for_subcategory) { Measure.where(subcategory: subcategory) } + let(:survey_items_for_subcategory) { SurveyItem.where(measure: measures_for_subcategory) } + let(:measure_1A_i) { Measure.find_by_measure_id('1A-i') } let(:measure_2A_i) { Measure.find_by_measure_id('2A-i') } let(:measure_4C_i) { Measure.find_by_measure_id('4C-i') } let(:survey_item_1_for_measure_1A_i) { SurveyItem.create measure: measure_1A_i, survey_item_id: rand.to_s } let(:survey_item_2_for_measure_1A_i) { SurveyItem.create measure: measure_1A_i, survey_item_id: rand.to_s } - let(:survey_item_1_for_measure_2A_i) { SurveyItem.create measure: measure_2A_i, survey_item_id: rand.to_s } - let(:survey_item_2_for_measure_2A_i) { SurveyItem.create measure: measure_2A_i, survey_item_id: rand.to_s } - let(:survey_item_1_for_measure_4C_i) { SurveyItem.create measure: measure_4C_i, survey_item_id: rand.to_s } - let(:survey_item_2_for_measure_4C_i) { SurveyItem.create measure: measure_4C_i, survey_item_id: rand.to_s } + + let(:survey_items_for_measure_1A_i) { SurveyItem.where(measure: measure_1A_i) } + let(:survey_items_for_measure_2A_i) { SurveyItem.where(measure: measure_2A_i) } + let(:survey_items_for_measure_4C_i) { SurveyItem.where(measure: measure_4C_i) } let(:measure_row_bars) { page.all('rect.measure-row-bar') } @@ -24,24 +29,27 @@ feature 'School dashboard', type: feature do let(:password) { 'winchester!' } before :each do - SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, - survey_item: survey_item_1_for_measure_1A_i, likert_score: 4 - SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, - survey_item: survey_item_2_for_measure_1A_i, likert_score: 5 - - SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, - survey_item: survey_item_1_for_measure_2A_i, likert_score: 5 - SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, - survey_item: survey_item_2_for_measure_2A_i, likert_score: 5 - - SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, - survey_item: survey_item_1_for_measure_4C_i, likert_score: 1 + survey_items_for_measure_1A_i.each do |survey_item| + SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, survey_item: survey_item, likert_score: 4 + end + + survey_items_for_measure_2A_i.each do |survey_item| + SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, survey_item: survey_item, likert_score: 5 + end + + survey_items_for_measure_4C_i.each do |survey_item| + SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, survey_item: survey_item, likert_score: 1 + end + + survey_items_for_subcategory.each do |survey_item| + SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, survey_item: survey_item, likert_score: 4 + end end scenario 'User authentication fails' do page.driver.browser.basic_authorize('wrong username', 'wrong password') - visit "/districts/winchester/schools/#{school.slug}/dashboard?year=2020-21" + visit "/districts/#{district.slug}/schools/#{school.slug}/dashboard?year=2020-21" expect(page).not_to have_text(school.name) end @@ -59,7 +67,7 @@ feature 'School dashboard', type: feature do expect(page).to have_text('Professional Qualifications') professional_qualifications_row = measure_row_bars.find { |item| item['data-for-measure-id'] == '1A-i' } - expect(professional_qualifications_row['width']).to eq '20.66%' + expect(professional_qualifications_row['width']).to eq '10.33%' expect(professional_qualifications_row['x']).to eq '50%' expect(page).to have_text('Student Physical Safety') @@ -78,6 +86,11 @@ feature 'School dashboard', type: feature do problem_solving_emphasis_row_index = measure_row_bars.find_index { |item| item['data-for-measure-id'] == '4C-i' } expect(student_physical_safety_row_index).to be < professional_qualifications_row_index expect(professional_qualifications_row_index).to be < problem_solving_emphasis_row_index + + click_on 'Browse' + + expect(page).to have_text('Teachers & Leadership') + expect(page).to have_text('Approval') end # visit photos_path @@ -97,7 +110,7 @@ feature 'School dashboard', type: feature do expect(page.all('.school-options[selected]')[0].text).to eq 'Winchester High School' school_options = page.all('.school-options') - school_options.each_with_index do |school , index| + school_options.each_with_index do |school , index| break if index == school_options.length-1 expect(school.text).to be < school_options[index+1].text end @@ -113,7 +126,7 @@ feature 'School dashboard', type: feature do expect(page.all('.district-options[selected]')[0].text).to eq 'Winchester' district_options = page.all('.district-options') - district_options.each_with_index do |district , index| + district_options.each_with_index do |district , index| break if index == district_options.length-1 expect(district.text).to be < district_options[index+1].text end diff --git a/spec/models/measure_spec.rb b/spec/models/measure_spec.rb deleted file mode 100644 index bee568de..00000000 --- a/spec/models/measure_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'rails_helper' - -describe Measure, type: :model do - it('has all the measures') do - expect(Measure.count).to eq 30 - end - - it 'returns the measure for the given measure id' do - measure = Measure.find_by_measure_id('1A-i') - - expect(measure.name).to eq 'Professional Qualifications' - expect(measure.warning_zone).to eq Measure::Zone.new(1.0, 2.49, :warning) - expect(measure.watch_zone).to eq Measure::Zone.new(2.49, 3.0, :watch) - expect(measure.growth_zone).to eq Measure::Zone.new(3.0, 3.5, :growth) - expect(measure.approval_zone).to eq Measure::Zone.new(3.5, 4.71, :approval) - expect(measure.ideal_zone).to eq Measure::Zone.new(4.71, 5.0, :ideal) - end - - describe '#zone_for_score' do - let(:measure) { - Measure.new watch_low_benchmark: 1.5, growth_low_benchmark: 2.5, approval_low_benchmark: 3.5, ideal_low_benchmark: 4.5 - } - - context 'when the score is 1.0' do - it 'returns the warning zone' do - expect(measure.zone_for_score(1.0)).to eq measure.warning_zone - end - end - - context 'when the score is 5.0' do - it 'returns the ideal zone' do - expect(measure.zone_for_score(5.0)).to eq measure.ideal_zone - end - end - - context 'when the score is right on the benchmark' do - it 'returns the higher zone' do - expect(measure.zone_for_score(1.5)).to eq measure.watch_zone - expect(measure.zone_for_score(2.5)).to eq measure.growth_zone - expect(measure.zone_for_score(3.5)).to eq measure.approval_zone - expect(measure.zone_for_score(4.5)).to eq measure.ideal_zone - end - end - end -end diff --git a/spec/presenters/category_presenter_spec.rb b/spec/presenters/category_presenter_spec.rb new file mode 100644 index 00000000..fa80b367 --- /dev/null +++ b/spec/presenters/category_presenter_spec.rb @@ -0,0 +1,19 @@ +require 'rails_helper' + +describe CategoryPresenter do + let(:category_presenter) do + subcategory1 = Subcategory.new(name: 'A subcategory') + subcategory2 = Subcategory.new(name: 'Another subcategory') + + category = SqmCategory.new(name: 'Some Category', subcategories: [subcategory1, subcategory2]) + return CategoryPresenter.new(category: category, academic_year: AcademicYear.new, school: School.new) + end + + it 'returns the name of the category' do + expect(category_presenter.name).to eq 'Some Category' + end + + it 'maps subcategories to subcategory presenters' do + expect(category_presenter.subcategories.map(&:name)).to eq ['A subcategory', 'Another subcategory'] + end +end diff --git a/spec/presenters/gauge_presenter_spec.rb b/spec/presenters/gauge_presenter_spec.rb new file mode 100644 index 00000000..6ebc230e --- /dev/null +++ b/spec/presenters/gauge_presenter_spec.rb @@ -0,0 +1,120 @@ +require 'rails_helper' + +describe GaugePresenter do + # let(:academic_year) { create(:academic_year, range: '1989-90') } + # let(:school) { create(:school, name: 'Best School') } + # let(:subcategory_presenter) do + # subcategory = create(:subcategory, name: 'A great subcategory') + + # measure1 = create(:measure, watch_low_benchmark: 4, growth_low_benchmark: 4.25, approval_low_benchmark: 4.5, ideal_low_benchmark: 4.75, subcategory: subcategory) + # survey_item1 = create(:survey_item, measure: measure1) + # create(:survey_item_response, survey_item: survey_item1, academic_year: academic_year, school: school, likert_score: 1) + # create(:survey_item_response, survey_item: survey_item1, academic_year: academic_year, school: school, likert_score: 5) + + # measure2 = create(:measure, watch_low_benchmark: 1.25, growth_low_benchmark: 1.5, approval_low_benchmark: 1.75, ideal_low_benchmark: 2.0, subcategory: subcategory) + # survey_item2 = create(:survey_item, measure: measure2) + # create(:survey_item_response, survey_item: survey_item2, academic_year: academic_year, school: school, likert_score: 1) + # create(:survey_item_response, survey_item: survey_item2, academic_year: academic_year, school: school, likert_score: 5) + + # create_survey_item_responses_for_different_years_and_schools(survey_item1) + + # return SubcategoryPresenter.new(subcategory: subcategory, academic_year: academic_year, school: school) + # end + let(:scale) do + Scale.new( + watch_low_benchmark: 1.5, + growth_low_benchmark: 2.5, + approval_low_benchmark: 3.5, + ideal_low_benchmark: 4.5, + ) + end + let(:score) { 3 } + + + let(:gauge_presenter) { GaugePresenter.new(scale: scale, score: score) } + + it 'returns the key benchmark percentage for the gauge' do + expect(gauge_presenter.key_benchmark_percentage).to eq 0.625 + end + + context 'when the given score is in the Warning zone for the given scale' do + let(:score) { 1 } + + it 'returns the title of the zone' do + expect(gauge_presenter.title).to eq 'Warning' + end + + it 'returns the color class for the gauge' do + expect(gauge_presenter.color_class).to eq 'fill-warning' + end + + it 'returns the score percentage for the gauge' do + expect(gauge_presenter.score_percentage).to eq 0.0 + end + end + + context 'when the given score is in the Watch zone for the given scale' do + let(:score) { 2 } + + it 'returns the title of the zone' do + expect(gauge_presenter.title).to eq 'Watch' + end + + it 'returns the color class for the gauge' do + expect(gauge_presenter.color_class).to eq 'fill-watch' + end + + it 'returns the score percentage for the gauge' do + expect(gauge_presenter.score_percentage).to eq 0.25 + end + end + + context 'when the given score is in the Growth zone for the given scale' do + let(:score) { 3 } + + it 'returns the title of the zone' do + expect(gauge_presenter.title).to eq 'Growth' + end + + it 'returns the color class for the gauge' do + expect(gauge_presenter.color_class).to eq 'fill-growth' + end + + it 'returns the score percentage for the gauge' do + expect(gauge_presenter.score_percentage).to eq 0.5 + end + end + + context 'when the given score is in the Approval zone for the given scale' do + let(:score) { 4 } + + it 'returns the title of the zone' do + expect(gauge_presenter.title).to eq 'Approval' + end + + it 'returns the color class for the gauge' do + expect(gauge_presenter.color_class).to eq 'fill-approval' + end + + it 'returns the score percentage for the gauge' do + expect(gauge_presenter.score_percentage).to eq 0.75 + end + end + + context 'when the given score is in the Ideal zone for the given scale' do + let(:score) { 5 } + + it 'returns the title of the zone' do + expect(gauge_presenter.title).to eq 'Ideal' + end + + it 'returns the color class for the gauge' do + expect(gauge_presenter.color_class).to eq 'fill-ideal' + end + + it 'returns the score percentage for the gauge' do + expect(gauge_presenter.score_percentage).to eq 1.0 + end + end + +end diff --git a/spec/presenters/scale_spec.rb b/spec/presenters/scale_spec.rb new file mode 100644 index 00000000..af27b9dd --- /dev/null +++ b/spec/presenters/scale_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +describe Scale do + describe '#zone_for_score' do + let(:scale) { + Scale.new watch_low_benchmark: 1.5, growth_low_benchmark: 2.5, approval_low_benchmark: 3.5, ideal_low_benchmark: 4.5 + } + + context 'when the score is 1.0' do + it 'returns the warning zone' do + expect(scale.zone_for_score(1.0)).to eq scale.warning_zone + end + end + + context 'when the score is 5.0' do + it 'returns the ideal zone' do + expect(scale.zone_for_score(5.0)).to eq scale.ideal_zone + end + end + + context 'when the score is right on the benchmark' do + it 'returns the higher zone' do + expect(scale.zone_for_score(1.5)).to eq scale.watch_zone + expect(scale.zone_for_score(2.5)).to eq scale.growth_zone + expect(scale.zone_for_score(3.5)).to eq scale.approval_zone + expect(scale.zone_for_score(4.5)).to eq scale.ideal_zone + end + end + end +end diff --git a/spec/presenters/subcategory_presenter_spec.rb b/spec/presenters/subcategory_presenter_spec.rb new file mode 100644 index 00000000..8dea2f02 --- /dev/null +++ b/spec/presenters/subcategory_presenter_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +describe SubcategoryPresenter do + let(:academic_year) { create(:academic_year, range: '1989-90') } + let(:school) { create(:school, name: 'Best School') } + let(:subcategory_presenter) do + subcategory = create(:subcategory, name: 'A great subcategory') + + measure1 = create(:measure, watch_low_benchmark: 4, growth_low_benchmark: 4.25, approval_low_benchmark: 4.5, ideal_low_benchmark: 4.75, subcategory: subcategory) + survey_item1 = create(:survey_item, measure: measure1) + create(:survey_item_response, survey_item: survey_item1, academic_year: academic_year, school: school, likert_score: 1) + create(:survey_item_response, survey_item: survey_item1, academic_year: academic_year, school: school, likert_score: 5) + + measure2 = create(:measure, watch_low_benchmark: 1.25, growth_low_benchmark: 1.5, approval_low_benchmark: 1.75, ideal_low_benchmark: 2.0, subcategory: subcategory) + survey_item2 = create(:survey_item, measure: measure2) + create(:survey_item_response, survey_item: survey_item2, academic_year: academic_year, school: school, likert_score: 1) + create(:survey_item_response, survey_item: survey_item2, academic_year: academic_year, school: school, likert_score: 5) + + create_survey_item_responses_for_different_years_and_schools(survey_item1) + + return SubcategoryPresenter.new(subcategory: subcategory, academic_year: academic_year, school: school) + end + + it 'returns the name of the subcategory' do + expect(subcategory_presenter.name).to eq 'A great subcategory' + end + + it 'returns a gauge presenter responsible for the aggregate survey item response likert scores' do + expect(subcategory_presenter.gauge_presenter.title).to eq 'Growth' + end + + def create_survey_item_responses_for_different_years_and_schools(survey_item) + create(:survey_item_response, survey_item: survey_item, school: school, likert_score: 1) + create(:survey_item_response, survey_item: survey_item, academic_year: academic_year, likert_score: 1) + end + +end diff --git a/spec/support/factory_bot.rb b/spec/support/factory_bot.rb new file mode 100644 index 00000000..c7890e49 --- /dev/null +++ b/spec/support/factory_bot.rb @@ -0,0 +1,3 @@ +RSpec.configure do |config| + config.include FactoryBot::Syntax::Methods +end diff --git a/spec/views/browse/show.html.erb_spec.rb b/spec/views/browse/show.html.erb_spec.rb new file mode 100644 index 00000000..6b92cf6c --- /dev/null +++ b/spec/views/browse/show.html.erb_spec.rb @@ -0,0 +1,39 @@ +require 'rails_helper' + +describe 'browse/show.html.erb' do + before :each do + academic_year = create(:academic_year, range: '1989-90') + school = create(:school, name: 'Best School') + + category = create(:sqm_category, name: 'Some Category') + + subcategory1 = create(:subcategory, sqm_category: category, name: 'A subcategory') + subcategory2 = create(:subcategory_with_measures, sqm_category: category, name: 'Another subcategory') + + measure1 = create(:measure, subcategory: subcategory1, watch_low_benchmark: 1.5, growth_low_benchmark: 2.5, approval_low_benchmark: 3.5, ideal_low_benchmark: 4.5) + + survey_item1 = create(:survey_item, measure: measure1) + + survey_item_response1 = create(:survey_item_response, survey_item: survey_item1, academic_year: academic_year, school: school, likert_score: 3) + + assign :category, CategoryPresenter.new(category: category, academic_year: academic_year, school: school) + + render + end + + it 'renders the category name' do + expect(rendered).to match /Some Category/ + end + + context 'for each subcategory' do + it 'renders the subcategory name' do + expect(rendered).to match /A subcategory/ + expect(rendered).to match /Another subcategory/ + end + + it 'renders the zone title and fill color for the gauge graph' do + expect(rendered).to match /Growth/ + expect(rendered).to match /fill-growth/ + end + end +end