From 1089397a0301df42f2f693e690be9a0eed622f5d Mon Sep 17 00:00:00 2001 From: Sean Dickinson Date: Sat, 31 Oct 2026 16:40:01 -0400 Subject: [PATCH 1/2] feat: show module links and games in order on the student home Students see each module's contents through a single ordered list. Co-Authored-By: Claude Opus 5.5 --- app/controllers/student_homes_controller.rb | 2 +- app/views/student_homes/_content.html.erb | 4 +++ .../student_homes/contentables/_game.html.erb | 2 ++ .../student_homes/contentables/_link.html.erb | 3 +++ app/views/student_homes/index.html.erb | 10 +------ .../student_homes_controller_test.rb | 27 ++++++++++++++++--- 6 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 app/views/student_homes/_content.html.erb create mode 100644 app/views/student_homes/contentables/_game.html.erb create mode 100644 app/views/student_homes/contentables/_link.html.erb diff --git a/app/controllers/student_homes_controller.rb b/app/controllers/student_homes_controller.rb index 30386a7..25b94f6 100644 --- a/app/controllers/student_homes_controller.rb +++ b/app/controllers/student_homes_controller.rb @@ -9,7 +9,7 @@ def index .order("programs.name") @active_program = @classroom_programs.find { |cp| cp.id.to_s == params[:classroom_program_id] } || @classroom_programs.first @published_modules = if @active_program - @active_program.classroom_modules.published.includes(content_module: :links) + @active_program.classroom_modules.published.includes(content_module: { contents: :contentable }) else [] end diff --git a/app/views/student_homes/_content.html.erb b/app/views/student_homes/_content.html.erb new file mode 100644 index 0000000..c718285 --- /dev/null +++ b/app/views/student_homes/_content.html.erb @@ -0,0 +1,4 @@ +<%# locals: (content:) %> +
  • + <%= render "student_homes/contentables/#{content.contentable_name}", contentable: content.contentable %> +
  • diff --git a/app/views/student_homes/contentables/_game.html.erb b/app/views/student_homes/contentables/_game.html.erb new file mode 100644 index 0000000..53a6134 --- /dev/null +++ b/app/views/student_homes/contentables/_game.html.erb @@ -0,0 +1,2 @@ +<%# locals: (contentable:) %> +<%= link_to contentable.title, play_game_path(contentable), class: "link inline-flex items-center gap-1" %> diff --git a/app/views/student_homes/contentables/_link.html.erb b/app/views/student_homes/contentables/_link.html.erb new file mode 100644 index 0000000..7bbdc2c --- /dev/null +++ b/app/views/student_homes/contentables/_link.html.erb @@ -0,0 +1,3 @@ +<%# locals: (contentable:) %> +<%= link_to "#{contentable.title} ↗", safe_external_url(contentable.url), + target: "_blank", rel: "noopener noreferrer", class: "link inline-flex items-center gap-1" %> diff --git a/app/views/student_homes/index.html.erb b/app/views/student_homes/index.html.erb index db4cd85..dd9f0a7 100644 --- a/app/views/student_homes/index.html.erb +++ b/app/views/student_homes/index.html.erb @@ -30,15 +30,7 @@
      - <% classroom_module.content_module.links.each do |link| %> -
    • - <%= tag.a link.title + " ↗", - href: safe_external_url(link.url), - target: "_blank", - rel: "noopener noreferrer", - class: "link inline-flex items-center gap-1" %> -
    • - <% end %> + <%= render partial: "student_homes/content", collection: classroom_module.content_module.contents %>
    <% end %> diff --git a/test/controllers/student_homes_controller_test.rb b/test/controllers/student_homes_controller_test.rb index d7567a2..71b712c 100644 --- a/test/controllers/student_homes_controller_test.rb +++ b/test/controllers/student_homes_controller_test.rb @@ -18,11 +18,25 @@ class StudentHomesControllerTest < ActionDispatch::IntegrationTest assert_select "summary", text: /Introduction to Health/ end - test "shows links inside published modules" do + test "links open in a new tab" do get student_homes_url assert_response :success - link = content_modules(:one).links.first - assert_select "a[target='_blank']", text: /#{link.title}/ if link + assert_select "li a[href='#{links(:one).url}'][target='_blank'][rel='noopener noreferrer']", text: /#{links(:one).title}/ + end + + test "games link to the play page in the same tab" do + get student_homes_url + assert_response :success + assert_select "li a[href='#{play_game_path(games(:one))}']:not([target])", text: games(:one).title + end + + test "shows contents in position order" do + contents(:one).update!(position: 2) + contents(:two).update!(position: 1) + + get student_homes_url + titles = listed_titles + assert_operator titles.index(links(:one).title), :<, titles.index(games(:one).title) end test "most recently published module has the open attribute" do @@ -79,4 +93,11 @@ class StudentHomesControllerTest < ActionDispatch::IntegrationTest get student_homes_url(classroom_program_id: enrollment.id) assert_select "a[role='tab'][aria-selected='true'][href*='classroom_program_id=#{enrollment.id}']" end + + private + + # The " ↗" suffix marks external links, so strip it to compare plain titles. + def listed_titles + css_select("details li a").map { |a| a.text.delete_suffix(" ↗") } + end end From 3d3286de9d4a77e1738546403e949b22f7721691 Mon Sep 17 00:00:00 2001 From: Sean Dickinson Date: Sun, 1 Nov 2026 16:10:38 -0500 Subject: [PATCH 2/2] fix: update tests --- .../student_homes_controller_test.rb | 85 ++++++++++--------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/test/controllers/student_homes_controller_test.rb b/test/controllers/student_homes_controller_test.rb index 71b712c..0692ea3 100644 --- a/test/controllers/student_homes_controller_test.rb +++ b/test/controllers/student_homes_controller_test.rb @@ -2,7 +2,10 @@ class StudentHomesControllerTest < ActionDispatch::IntegrationTest setup do + # A fresh classroom has no enrollments, so each test builds the ones it needs. + @classroom = Classroom.create!(name: "Test Classroom", school: schools(:one)) @student = students(:one) + @student.update!(classroom: @classroom) student_sign_in_as @student end @@ -12,74 +15,68 @@ class StudentHomesControllerTest < ActionDispatch::IntegrationTest assert_redirected_to new_student_session_url end - test "shows published module names" do - get student_homes_url - assert_response :success - assert_select "summary", text: /Introduction to Health/ - end + test "shows published module names only" do + enrollment = enroll(program: programs(:one)) + publish_module(enrollment, name: "Intro") + publish_module(enrollment, name: "Scheduled", publish_on: 1.day.from_now) - test "links open in a new tab" do get student_homes_url assert_response :success - assert_select "li a[href='#{links(:one).url}'][target='_blank'][rel='noopener noreferrer']", text: /#{links(:one).title}/ + assert_select "summary", text: /Intro/ + assert_select "summary", text: /Scheduled/, count: 0 end - test "games link to the play page in the same tab" do + test "shows contents in position order" do + content_module = publish_module(enroll(program: programs(:one))).content_module + content_module.contents.create!(contentable: games(:one)) + link_content = content_module.contents.create!(contentable: links(:one)) + get student_homes_url - assert_response :success - assert_select "li a[href='#{play_game_path(games(:one))}']:not([target])", text: games(:one).title - end + assert_equal [ games(:one).title, links(:one).title ], listed_titles - test "shows contents in position order" do - contents(:one).update!(position: 2) - contents(:two).update!(position: 1) + link_content.move(:up) get student_homes_url - titles = listed_titles - assert_operator titles.index(links(:one).title), :<, titles.index(games(:one).title) + assert_equal [ links(:one).title, games(:one).title ], listed_titles end test "most recently published module has the open attribute" do - # Add a second published module so there's a distinct "most recent" - second_module = ContentModule.create!( - program: programs(:one), level: "basic", name: "Second Module", position: 2 - ) - classroom_programs(:one).classroom_modules.create!( - content_module: second_module, publish_on: Date.current - ) + enrollment = enroll(program: programs(:one)) + publish_module(enrollment, name: "Older Module", publish_on: 1.week.ago.to_date) + publish_module(enrollment, name: "Newer Module", publish_on: Date.current) get student_homes_url - assert_select "details[open] summary", text: /Second Module/ + assert_select "details[open]", count: 1 + assert_select "details[open] summary", text: /Newer Module/ end test "all modules published on the same latest date have the open attribute" do - second_module = ContentModule.create!( - program: programs(:one), level: "basic", name: "Second Module", position: 2 - ) - classroom_modules(:one).update!(publish_on: Date.current) - classroom_programs(:one).classroom_modules.create!( - content_module: second_module, publish_on: Date.current - ) + enrollment = enroll(program: programs(:one)) + publish_module(enrollment, name: "First Module", publish_on: Date.current) + publish_module(enrollment, name: "Second Module", publish_on: Date.current) get student_homes_url assert_select "details[open]", count: 2 end test "shows empty state when no modules are published" do - classroom_modules(:one).update!(publish_on: nil) + enrollment = enroll(program: programs(:one)) + publish_module(enrollment, publish_on: nil) + get student_homes_url assert_select "p", text: /No modules published yet/ end test "does not show tab bar for single program enrollment" do + enroll(program: programs(:one)) + get student_homes_url assert_select "[role='tablist']", count: 0 end test "shows tab bar when classroom has multiple program enrollments" do - classroom_programs(:one).classroom.classroom_programs.create!( - program: programs(:two), level: "moderate" - ) + enroll(program: programs(:one)) + enroll(program: programs(:two)) get student_homes_url assert_select "[role='tablist']" @@ -87,15 +84,25 @@ class StudentHomesControllerTest < ActionDispatch::IntegrationTest end test "tab switching shows the selected program as active" do - enrollment = classroom_programs(:one) - enrollment.classroom.classroom_programs.create!(program: programs(:two), level: "moderate") + enroll(program: programs(:one)) + second_enrollment = enroll(program: programs(:two)) - get student_homes_url(classroom_program_id: enrollment.id) - assert_select "a[role='tab'][aria-selected='true'][href*='classroom_program_id=#{enrollment.id}']" + get student_homes_url(classroom_program_id: second_enrollment.id) + assert_select "a[role='tab'][aria-selected='true'][href*='classroom_program_id=#{second_enrollment.id}']" end private + def enroll(program:, level: "basic") + @classroom.classroom_programs.create!(program: program, level: level) + end + + # Creates a module in the enrollment's program and level, and schedules it for the classroom. + def publish_module(enrollment, name: "Module", publish_on: Date.current) + content_module = ContentModule.create!(program: enrollment.program, level: enrollment.level, name: name) + enrollment.classroom_modules.create!(content_module: content_module, publish_on: publish_on) + end + # The " ↗" suffix marks external links, so strip it to compare plain titles. def listed_titles css_select("details li a").map { |a| a.text.delete_suffix(" ↗") }