diff --git a/app/controllers/practices/questions_controller.rb b/app/controllers/practices/questions_controller.rb index 2a5727d579c..f2b10c81789 100644 --- a/app/controllers/practices/questions_controller.rb +++ b/app/controllers/practices/questions_controller.rb @@ -3,13 +3,11 @@ class Practices::QuestionsController < ApplicationController def index @practice = Practice.find(params[:practice_id]) - allowed_targets = %w[solved not_solved].freeze - target = allowed_targets.include?(params[:target]) ? params[:target] : nil - @questions = @practice.questions - .includes(%i[correct_answer answers]) - .by_target(target) - .order(created_at: :desc) - .page(params[:page]) + @questions = Question.where(practice: question_practices) + .includes(%i[correct_answer answers]) + .by_target(selected_target) + .order(created_at: :desc) + .page(params[:page]) @empty_message = empty_message end @@ -25,4 +23,16 @@ def empty_message '質問はありません。' end end + + def question_practices + if params[:scope] == 'grant_course' + [@practice] + else + [@practice, @practice.source_practice].compact + end + end + + def selected_target + params[:target] if %w[solved not_solved].include?(params[:target]) + end end diff --git a/app/helpers/page_tabs/practices_helper.rb b/app/helpers/page_tabs/practices_helper.rb index e1917e2f1ac..bfcf4b203b0 100644 --- a/app/helpers/page_tabs/practices_helper.rb +++ b/app/helpers/page_tabs/practices_helper.rb @@ -8,13 +8,26 @@ def practice_page_tabs(practice, active_tab:, include_source: nil) tabs = [] tabs << { name: 'プラクティス', link: practice_path(practice) } tabs << { name: '日報', link: practice_reports_path(practice), count: practice.reports_count(include_source:) } - tabs << { name: '質問', link: practice_questions_path(practice), count: practice.questions.length } - tabs << { name: 'Docs', link: practice_pages_path(practice), count: practice.pages.length } - tabs << { name: '動画', link: practice_movies_path(practice), count: practice.movies.length } if movie_available? + tabs << { name: '質問', link: practice_questions_path(practice), count: practice_questions_count(practice, active_tab:) } + tabs << { name: 'Docs', link: practice_pages_path(practice), count: practice.pages.count } + tabs << { name: '動画', link: practice_movies_path(practice), count: practice.movies.count } if movie_available? tabs << { name: '提出物', link: practice_products_path(practice) } if practice.submission tabs << { name: '模範解答', link: practice_submission_answer_path(practice) } if practice.submission_answer.present? tabs << { name: 'コーディングテスト', link: practice_coding_tests_path(practice) } if practice.coding_tests.present? render PageTabsComponent.new(tabs:, active_tab:) end + + private + + def practice_questions_count(practice, active_tab:) + practices = + if active_tab == '質問' && params[:scope] == 'grant_course' + [practice] + else + [practice, practice.source_practice].compact + end + + Question.where(practice: practices).count + end end end diff --git a/app/views/practices/questions/index.html.slim b/app/views/practices/questions/index.html.slim index 29382f7f371..178d380b2ac 100644 --- a/app/views/practices/questions/index.html.slim +++ b/app/views/practices/questions/index.html.slim @@ -13,15 +13,25 @@ nav.tab-nav .container ul.tab-nav__items li.tab-nav__item - = link_to '全ての質問', practice_questions_path(@practice), + = link_to '全ての質問', practice_questions_path(@practice, scope: params[:scope]), class: "tab-nav__item-link #{%w[not_solved solved].include?(params[:target]) ? '' : 'is-active'}" li.tab-nav__item - = link_to '解決済み', practice_questions_path(@practice, target: 'solved'), + = link_to '解決済み', practice_questions_path(@practice, target: 'solved', scope: params[:scope]), class: "tab-nav__item-link #{params[:target] == 'solved' ? 'is-active' : ''}" li.tab-nav__item - = link_to '未解決', practice_questions_path(@practice, target: 'not_solved'), + = link_to '未解決', practice_questions_path(@practice, target: 'not_solved', scope: params[:scope]), class: "tab-nav__item-link #{params[:target] == 'not_solved' ? 'is-active' : ''}" +- if @practice.source_id.present? + nav.pill-nav + ul.pill-nav__items + li.pill-nav__item + = link_to '全て', practice_questions_path(@practice, target: params[:target]), + class: "pill-nav__item-link #{params[:scope] == 'grant_course' ? '' : 'is-active'}" + li.pill-nav__item + = link_to '給付金コース', practice_questions_path(@practice, target: params[:target], scope: 'grant_course'), + class: "pill-nav__item-link #{params[:scope] == 'grant_course' ? 'is-active' : ''}" + .page-body .container.is-md = render 'questions/questions', questions: @questions, empty_message: @empty_message diff --git a/db/fixtures/categories_practices.yml b/db/fixtures/categories_practices.yml index dcfedcee5a9..7d89a2a7d77 100644 --- a/db/fixtures/categories_practices.yml +++ b/db/fixtures/categories_practices.yml @@ -316,63 +316,59 @@ categories_practice63: categories_practice64: practice: practice64 category: category10 + position: 3 -categories_practice111_1: +categories_practice65: practice: practice111 category: category6 position: 14 -categories_practice111_2: +categories_practice66: practice: practice111 category: category23 position: 2 -categories_practice112_1: +categories_practice67: practice: practice112 category: category6 position: 15 -categories_practice112_2: +categories_practice68: practice: practice112 category: category23 position: 3 -categories_practice65_1: +categories_practice69: practice: practice65 category: category6 - position: 14 + position: 16 -categories_practice65_2: +categories_practice70: practice: practice65 category: category22 position: 2 -categories_practice66_1: +categories_practice71: practice: practice66 category: category6 - position: 15 + position: 17 -categories_practice66_2: +categories_practice72: practice: practice66 category: category22 position: 3 -categories_practice67: +categories_practice73: practice: practice113 category: category24 - position: 16 + position: 1 -categories_practice68: +categories_practice74: practice: practice114 - category: category24 - position: 17 - -categories_practice69: - practice: practice115 category: category25 position: 1 -categories_practice70: - practice: practice116 +categories_practice75: + practice: practice115 category: category25 position: 2 diff --git a/db/fixtures/correct_answers.yml b/db/fixtures/correct_answers.yml index eb314f039d2..46d7c4baf51 100644 --- a/db/fixtures/correct_answers.yml +++ b/db/fixtures/correct_answers.yml @@ -15,3 +15,15 @@ correct_answer3: user: machida question: question1 type: "CorrectAnswer" + +correct_answer4: + description: ベストアンサーです。 + user: machida + question: question57 + type: "CorrectAnswer" + +correct_answer5: + description: ベストアンサーです。 + user: machida + question: question59 + type: "CorrectAnswer" diff --git a/db/fixtures/courses_categories.yml b/db/fixtures/courses_categories.yml index 6f3b3313df3..f782760b558 100644 --- a/db/fixtures/courses_categories.yml +++ b/db/fixtures/courses_categories.yml @@ -135,100 +135,10 @@ courses_category27: courses_category28: course: course5 - category: category1 + category: category24 position: 1 courses_category29: - course: course5 - category: category2 - position: 2 - -courses_category30: - course: course5 - category: category3 - position: 3 - -courses_category31: - course: course5 - category: category4 - position: 4 - -courses_category32: - course: course5 - category: category5 - position: 5 - -courses_category33: - course: course5 - category: category6 - position: 6 - -courses_category34: - course: course5 - category: category7 - position: 7 - -courses_category35: - course: course5 - category: category8 - position: 8 - -courses_category36: - course: course5 - category: category9 - position: 9 - -courses_category37: - course: course5 - category: category10 - position: 10 - -courses_category38: - course: course5 - category: category11 - position: 11 - -courses_category39: - course: course5 - category: category12 - position: 12 - -courses_category40: - course: course5 - category: category13 - position: 13 - -courses_category41: - course: course5 - category: category14 - position: 14 - -courses_category42: - course: course5 - category: category15 - position: 15 - -courses_category43: - course: course5 - category: category16 - position: 16 - -courses_category44: - course: course5 - category: category22 - position: 17 - -courses_category45: - course: course5 - category: category23 - position: 18 - -courses_category46: - course: course5 - category: category24 - position: 19 - -courses_category47: course: course5 category: category25 - position: 20 + position: 2 diff --git a/db/fixtures/practices.yml b/db/fixtures/practices.yml index f33cfd19470..2ee1a04547d 100644 --- a/db/fixtures/practices.yml +++ b/db/fixtures/practices.yml @@ -772,18 +772,12 @@ practice113: source_practice: practice23 practice114: - title: "rbenvをインストールする(Reスキル)" - description: "Railsコースのプラクティスをコピーした給付金コースのプラクティスです。" - goal: "goal..." - source_practice: practice24 - -practice115: title: "OS X Mountain Lionをクリーンインストールする(Reスキル)" description: "Railsコースのプラクティスをコピーしたプラクティスです。" goal: "goal..." source_id: <%= ActiveRecord::FixtureSet.identify(:practice1) %> -practice116: +practice115: title: "Terminalの基礎を覚える(Reスキル)" description: "Railsコースのプラクティスをコピーしたプラクティスです。" goal: "goal..." diff --git a/db/fixtures/questions.yml b/db/fixtures/questions.yml index 0a3b8e44a5a..44972b730b1 100644 --- a/db/fixtures/questions.yml +++ b/db/fixtures/questions.yml @@ -160,3 +160,35 @@ question55: created_at: <%= Time.current - 3.month %> updated_at: <%= Time.current - 3.month %> published_at: <%= Time.current - 3.month %> + +question56: + title: 給付金コースのプラクティスの質問 + description: 給付金コースのプラクティスの質問です。 + user: grant-course + practice: practice113 + created_at: "2026-05-15" + published_at: "2026-05-15" + +question57: + title: 給付金コースのプラクティスの質問(解決済み) + description: 解決済みです。 + user: grant-course + practice: practice113 + created_at: "2026-05-15" + published_at: "2026-05-15" + +question58: + title: 給付金コースのコピー元プラクティスの質問 + description: 給付金コースのコピー元プラクティスの質問 + user: grant-course + practice: practice23 + created_at: "2026-05-15" + published_at: "2026-05-15" + +question59: + title: 給付金コースのコピー元プラクティスの質問(解決済み) + description: 解決済みです。 + user: grant-course + practice: practice23 + created_at: "2026-05-15" + published_at: "2026-05-15" diff --git a/test/fixtures/correct_answers.yml b/test/fixtures/correct_answers.yml index f7e809ac41e..cf18afa1f43 100644 --- a/test/fixtures/correct_answers.yml +++ b/test/fixtures/correct_answers.yml @@ -9,3 +9,15 @@ correct_answer2: user: advijirou question: question12 type: "CorrectAnswer" + +correct_answer3: + description: ベストアンサーです。 + user: machida + question: question18 + type: "CorrectAnswer" + +correct_answer4: + description: ベストアンサーです。 + user: machida + question: question20 + type: "CorrectAnswer" diff --git a/test/fixtures/questions.yml b/test/fixtures/questions.yml index b187b615930..1c559cd1112 100644 --- a/test/fixtures/questions.yml +++ b/test/fixtures/questions.yml @@ -143,3 +143,35 @@ question16: practice: practice1 created_at: "2022-01-18" published_at: "2022-01-18" + +question17: + title: 給付金コースのプラクティスの質問 + description: 給付金コースのプラクティスの質問です。 + user: grant-course + practice: practice64 + created_at: "2026-05-15" + published_at: "2026-05-15" + +question18: + title: 給付金コースのプラクティスの質問(解決済み) + description: 解決済みです。 + user: grant-course + practice: practice64 + created_at: "2026-05-15" + published_at: "2026-05-15" + +question19: + title: 給付金コースのコピー元プラクティスの質問 + description: 給付金コースのコピー元プラクティスの質問 + user: grant-course + practice: practice23 + created_at: "2026-05-15" + published_at: "2026-05-15" + +question20: + title: 給付金コースのコピー元プラクティスの質問(解決済み) + description: 解決済みです。 + user: grant-course + practice: practice23 + created_at: "2026-05-15" + published_at: "2026-05-15" diff --git a/test/system/practice/questions_test.rb b/test/system/practice/questions_test.rb index 07e235a1515..ac191ffd4e3 100644 --- a/test/system/practice/questions_test.rb +++ b/test/system/practice/questions_test.rb @@ -18,4 +18,113 @@ class Practice::QuestionsTest < ApplicationSystemTestCase assert_no_text 'wipテスト用の質問(wip中)' assert_selector('a.tab-nav__item-link.is-active', text: '未解決') end + + test 'show grant course filter tabs only for grant course practices' do + visit_with_auth "/practices/#{practices(:practice64).id}/questions", 'grant-course' + assert_selector '.pill-nav__item-link', exact_text: '全て' + assert_selector '.pill-nav__item-link', exact_text: '給付金コース' + + visit_with_auth "/practices/#{practices(:practice1).id}/questions", 'grant-course' + assert_no_selector '.pill-nav__item-link', exact_text: '全て' + assert_no_selector '.pill-nav__item-link', exact_text: '給付金コース' + end + + test 'show all questions from both grant and source practices when all filter is selected' do + visit_with_auth "/practices/#{practices(:practice64).id}/questions", 'grant-course' + assert_selector '.pill-nav__item-link.is-active', exact_text: '全て' + assert_selector '.tab-nav__item-link.is-active', exact_text: '全ての質問' + assert_selector '.card-list-item-title__link', exact_text: '給付金コースのプラクティスの質問' + assert_selector '.card-list-item-title__link', exact_text: '給付金コースのプラクティスの質問(解決済み)' + assert_selector '.card-list-item-title__link', exact_text: '給付金コースのコピー元プラクティスの質問' + assert_selector '.card-list-item-title__link', exact_text: '給付金コースのコピー元プラクティスの質問(解決済み)' + end + + test 'show solved questions from both grant and source practices when solved filter is selected' do + visit_with_auth "/practices/#{practices(:practice64).id}/questions", 'grant-course' + within '.tab-nav' do + click_on '解決済み' + end + assert_selector '.pill-nav__item-link.is-active', exact_text: '全て' + assert_selector '.tab-nav__item-link.is-active', exact_text: '解決済み' + assert_no_selector '.card-list-item-title__link', exact_text: '給付金コースのプラクティスの質問' + assert_selector '.card-list-item-title__link', exact_text: '給付金コースのプラクティスの質問(解決済み)' + assert_no_selector '.card-list-item-title__link', exact_text: '給付金コースのコピー元プラクティスの質問' + assert_selector '.card-list-item-title__link', exact_text: '給付金コースのコピー元プラクティスの質問(解決済み)' + end + + test 'show unsolved questions from both grant and source practices when unsolved filter is selected' do + visit_with_auth "/practices/#{practices(:practice64).id}/questions", 'grant-course' + within '.tab-nav' do + click_on '未解決' + end + assert_selector '.pill-nav__item-link.is-active', exact_text: '全て' + assert_selector '.tab-nav__item-link.is-active', exact_text: '未解決' + assert_selector '.card-list-item-title__link', exact_text: '給付金コースのプラクティスの質問' + assert_no_selector '.card-list-item-title__link', exact_text: '給付金コースのプラクティスの質問(解決済み)' + assert_selector '.card-list-item-title__link', exact_text: '給付金コースのコピー元プラクティスの質問' + assert_no_selector '.card-list-item-title__link', exact_text: '給付金コースのコピー元プラクティスの質問(解決済み)' + end + + test 'show all questions only from the grant practice when grant course scope is selected' do + visit_with_auth "/practices/#{practices(:practice64).id}/questions", 'grant-course' + within '.pill-nav' do + click_on '給付金コース' + end + assert_selector '.pill-nav__item-link.is-active', exact_text: '給付金コース' + assert_selector '.tab-nav__item-link.is-active', exact_text: '全ての質問' + assert_selector '.card-list-item-title__link', exact_text: '給付金コースのプラクティスの質問' + assert_selector '.card-list-item-title__link', exact_text: '給付金コースのプラクティスの質問(解決済み)' + assert_no_selector '.card-list-item-title__link', exact_text: '給付金コースのコピー元プラクティスの質問' + assert_no_selector '.card-list-item-title__link', exact_text: '給付金コースのコピー元プラクティスの質問(解決済み)' + end + + test 'show solved questions only from the grant practice when solved filter and grant course scope are selected' do + visit_with_auth "/practices/#{practices(:practice64).id}/questions", 'grant-course' + within '.pill-nav' do + click_on '給付金コース' + end + within '.tab-nav' do + click_on '解決済み' + end + assert_selector '.pill-nav__item-link.is-active', exact_text: '給付金コース' + assert_selector '.tab-nav__item-link.is-active', exact_text: '解決済み' + assert_no_selector '.card-list-item-title__link', exact_text: '給付金コースのプラクティスの質問' + assert_selector '.card-list-item-title__link', exact_text: '給付金コースのプラクティスの質問(解決済み)' + assert_no_selector '.card-list-item-title__link', exact_text: '給付金コースのコピー元プラクティスの質問' + assert_no_selector '.card-list-item-title__link', exact_text: '給付金コースのコピー元プラクティスの質問(解決済み)' + end + + test 'show unsolved questions only from the grant practice when unsolved filter and grant course scope are selected' do + visit_with_auth "/practices/#{practices(:practice64).id}/questions", 'grant-course' + within '.pill-nav' do + click_on '給付金コース' + end + within '.tab-nav' do + click_on '未解決' + end + assert_selector '.pill-nav__item-link.is-active', exact_text: '給付金コース' + assert_selector '.tab-nav__item-link.is-active', exact_text: '未解決' + assert_selector '.card-list-item-title__link', exact_text: '給付金コースのプラクティスの質問' + assert_no_selector '.card-list-item-title__link', exact_text: '給付金コースのプラクティスの質問(解決済み)' + assert_no_selector '.card-list-item-title__link', exact_text: '給付金コースのコピー元プラクティスの質問' + assert_no_selector '.card-list-item-title__link', exact_text: '給付金コースのコピー元プラクティスの質問(解決済み)' + end + + test 'show total question count from grant and source practices when all scope is selected' do + visit_with_auth "/practices/#{practices(:practice64).id}/questions", 'grant-course' + assert_selector '.page-tabs__item-link.is-active', exact_text: '質問 (4)' + end + + test 'show question count from grant practice only when grant course scope is selected' do + visit_with_auth "/practices/#{practices(:practice64).id}/questions", 'grant-course' + within '.pill-nav' do + click_on '給付金コース' + end + assert_selector '.page-tabs__item-link.is-active', exact_text: '質問 (2)' + end + + test 'show total question count from grant and source practices on non-question pages' do + visit_with_auth "/practices/#{practices(:practice64).id}", 'grant-course' + assert_selector '.page-tabs__item-link', exact_text: '質問 (4)' + end end