From 206dc5c50a73c5402b90d7fdc8945d3ba9356758 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Sat, 4 Jul 2026 01:24:55 +0200 Subject: Add self-service event creation from nodes#show nodes#show's events table now renders unconditionally (previously hidden entirely for a zero-event node) and gains an "add event" link. The suggested tag_list is derived from the page's own category tags via NodesHelper::DEFAULT_EVENT_TAG_BY_PAGE_TAG (erfa-detail/ chaostreff-detail -> open-day) rather than hardcoded, and degrades to a blank field for any node whose tags don't match - deliberately universal, not chapter-specific, since Updates have historically carried event dates the same way. events#new surfaces *why* the tag was pre-filled via flash.now (not flash - this is a same-request render, not a redirect), using an explicit auto_tag_source param passed alongside tag_list rather than having the controller re-derive the reason from node_id. No destroy link added to nodes#show's events list - deliberate, per existing subnav-semantics convention (destructive actions live on the resource's own views). Covered directly by test. Adds real coverage for EventsController, previously 100% commented-out scaffold, plus unit tests for the new tag-mapping helper and the weekday-abbreviation helpers from the prior commit. --- app/controllers/events_controller.rb | 4 + app/helpers/nodes_helper.rb | 14 +++ app/views/nodes/show.html.erb | 4 +- test/controllers/events_controller_test.rb | 167 ++++++++++++++++++++------- test/controllers/nodes_controller_test.rb | 33 ++++++ test/models/concerns/rrule_humanizer_test.rb | 15 +++ test/models/helpers/content_helper_test.rb | 4 + test/models/helpers/nodes_helper_test.rb | 21 ++++ 8 files changed, 219 insertions(+), 43 deletions(-) diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index 9bed5dd..b98a38e 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -37,6 +37,10 @@ class EventsController < ApplicationController tag_list: params[:tag_list] ) + if params[:tag_list].present? && params[:auto_tag_source].present? + flash.now[:notice] = "Tag '#{params[:tag_list]}' was pre-filled because this page is tagged '#{params[:auto_tag_source]}'. You can remove it below." + end + respond_to do |format| format.html # new.html.erb format.xml { render :xml => @event } diff --git a/app/helpers/nodes_helper.rb b/app/helpers/nodes_helper.rb index 329bcc5..093bfc6 100644 --- a/app/helpers/nodes_helper.rb +++ b/app/helpers/nodes_helper.rb @@ -44,6 +44,20 @@ module NodesHelper ]) end + DEFAULT_EVENT_TAG_BY_PAGE_TAG = { + 'erfa-detail' => 'open-day', + 'chaostreff-detail' => 'open-day' + }.freeze + + def default_event_tag_mapping(page) + page_tags = page.tag_list + DEFAULT_EVENT_TAG_BY_PAGE_TAG.find { |page_tag, _| page_tags.include?(page_tag) } + end + + def default_event_tag_list(page) + default_event_tag_mapping(page)&.last + end + def event_schedule_text(event) if event.rrule.present? recurrence = event.humanize_rrule(I18n.locale) diff --git a/app/views/nodes/show.html.erb b/app/views/nodes/show.html.erb index 2ce3853..c533a55 100644 --- a/app/views/nodes/show.html.erb +++ b/app/views/nodes/show.html.erb @@ -42,7 +42,6 @@ Tagged with: <%= @page.tag_list %> - <% if @node.events.any? %> Events @@ -55,9 +54,10 @@ <% end %> + <% mapping = default_event_tag_mapping(@page) %> + <%= link_to 'add event', new_event_path(node_id: @node.id, tag_list: mapping&.last, auto_tag_source: mapping&.first, return_to: request.path) %> - <% end %> Title <%= sanitize( @page.title ) %> diff --git a/test/controllers/events_controller_test.rb b/test/controllers/events_controller_test.rb index 14e534e..9371ca7 100644 --- a/test/controllers/events_controller_test.rb +++ b/test/controllers/events_controller_test.rb @@ -1,45 +1,130 @@ require 'test_helper' class EventsControllerTest < ActionController::TestCase - # test "should get index" do - # get :index - # assert_response :success - # assert_not_nil assigns(:events) - # end - # - # test "should get new" do - # get :new - # assert_response :success - # end - # - # test "should create event" do - # assert_difference('Event.count') do - # post :create, params: { :event => { } } - # end - # - # assert_redirected_to event_path(assigns(:event)) - # end - # - # test "should show event" do - # get :show, params: { :id => events(:one).to_param } - # assert_response :success - # end - # - # test "should get edit" do - # get :edit, params: { :id => events(:one).to_param } - # assert_response :success - # end - # - # test "should update event" do - # put :update, params: { :id => events(:one).to_param, :event => { } } - # assert_redirected_to event_path(assigns(:event)) - # end - # - # test "should destroy event" do - # assert_difference('Event.count', -1) do - # delete :destroy, params: { :id => events(:one).to_param } - # end - # - # assert_redirected_to events_path - # end + + test "should get index" do + login_as :quentin + get :index + assert_response :success + assert_not_nil assigns(:events) + end + + test "should get new" do + login_as :quentin + get :new + assert_response :success + end + + test "new pre-fills tag_list and explains it via flash when auto_tag_source is given" do + login_as :quentin + node = create_node_with_published_page + + get :new, params: { node_id: node.id, tag_list: "open-day", auto_tag_source: "erfa-detail" } + + assert_response :success + assert_equal "open-day", assigns(:event).tag_list.to_s + assert_match "open-day", flash[:notice] + assert_match "erfa-detail", flash[:notice] + end + + test "new does not flash without an auto_tag_source" do + login_as :quentin + + get :new, params: { tag_list: "open-day" } + + assert_response :success + assert_nil flash[:notice] + end + + test "new with no params renders a blank, unflashed form" do + login_as :quentin + + get :new + + assert_response :success + assert_nil assigns(:event).tag_list.presence + assert_nil flash[:notice] + end + + test "should show event" do + login_as :quentin + node = create_node_with_published_page + event = Event.create!(node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour) + + get :show, params: { id: event.id } + assert_response :success + end + + test "should get edit" do + login_as :quentin + node = create_node_with_published_page + event = Event.create!(node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour) + + get :edit, params: { id: event.id } + assert_response :success + end + + test "should create event attached to a node" do + login_as :quentin + node = create_node_with_published_page + + assert_difference('Event.count') do + post :create, params: { + event: { + node_id: node.id, + start_time: Time.now, + end_time: Time.now + 1.hour, + tag_list: "open-day" + } + } + end + + assert_redirected_to edit_node_path(node) + assert_equal 'Event was successfully created.', flash[:notice] + end + + test "should not create an event without a title or a node_id" do + login_as :quentin + + assert_no_difference('Event.count') do + post :create, params: { event: { start_time: Time.now, end_time: Time.now + 1.hour } } + end + + assert_response :success # re-renders :new, not a redirect + end + + test "should honour return_to on create" do + login_as :quentin + node = create_node_with_published_page + + post :create, params: { + event: { node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour }, + return_to: node_path(node) + } + + assert_redirected_to node_path(node) + end + + test "should update event" do + login_as :quentin + node = create_node_with_published_page + event = Event.create!(node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour) + + put :update, params: { id: event.id, event: { title: "Updated title" } } + + assert_redirected_to events_path + assert_equal "Updated title", event.reload.title + end + + test "should destroy event" do + login_as :quentin + node = create_node_with_published_page + event = Event.create!(node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour) + + assert_difference('Event.count', -1) do + delete :destroy, params: { id: event.id } + end + + assert_redirected_to events_url + end end diff --git a/test/controllers/nodes_controller_test.rb b/test/controllers/nodes_controller_test.rb index 53799f1..f14e27c 100644 --- a/test/controllers/nodes_controller_test.rb +++ b/test/controllers/nodes_controller_test.rb @@ -379,4 +379,37 @@ class NodesControllerTest < ActionController::TestCase get :index end + test "show renders events row and add-link for zero-event chapter node" do + login_as :quentin + node = create_node_with_published_page + node.head.tag_list = "erfa-detail" + node.head.save! + + get :show, params: { id: node.id } + assert_response :success + assert_select "a", text: "add event" + assert_select "a[href*='tag_list=open-day']" + assert_select "a[href*='auto_tag_source=erfa-detail']" + end + + test "show renders events row without a tag default for untagged node" do + login_as :quentin + node = create_node_with_published_page + + get :show, params: { id: node.id } + assert_response :success + assert_select "a", text: "add event" + assert_select "a[href*='tag_list=']", count: 0 + end + + test "show never renders a destroy link for events" do + login_as :quentin + node = create_node_with_published_page + Event.create!(node_id: node.id, start_time: Time.now, end_time: Time.now + 1.hour) + + get :show, params: { id: node.id } + assert_response :success + assert_select "form.button_to.destructive", count: 0 + end + end diff --git a/test/models/concerns/rrule_humanizer_test.rb b/test/models/concerns/rrule_humanizer_test.rb index 500dbc7..279ff73 100644 --- a/test/models/concerns/rrule_humanizer_test.rb +++ b/test/models/concerns/rrule_humanizer_test.rb @@ -81,4 +81,19 @@ class RruleHumanizerTest < ActiveSupport::TestCase test "falls back to english for unknown locale" do assert_equal "Every Tuesday", humanize("FREQ=WEEKLY;BYDAY=TU", :fr) end + + test "wday_abbr returns the correct German abbreviation for each day" do + monday = Time.parse("2026-07-06") # confirmed Monday + assert_equal "Mo", RruleHumanizer.wday_abbr(monday, :de) + assert_equal "Di", RruleHumanizer.wday_abbr(monday + 1.day, :de) + assert_equal "Mi", RruleHumanizer.wday_abbr(monday + 2.days, :de) + assert_equal "Do", RruleHumanizer.wday_abbr(monday + 3.days, :de) + assert_equal "Fr", RruleHumanizer.wday_abbr(monday + 4.days, :de) + assert_equal "Sa", RruleHumanizer.wday_abbr(monday + 5.days, :de) + assert_equal "So", RruleHumanizer.wday_abbr(monday + 6.days, :de) + end + + test "wday_abbr falls back to :de for an unrecognized locale" do + assert_equal "Mo", RruleHumanizer.wday_abbr(Time.parse("2026-07-06"), :fr) + end end diff --git a/test/models/helpers/content_helper_test.rb b/test/models/helpers/content_helper_test.rb index 2da82d7..a7ed478 100644 --- a/test/models/helpers/content_helper_test.rb +++ b/test/models/helpers/content_helper_test.rb @@ -1,4 +1,8 @@ require 'test_helper' class ContentHelperTest < ActionView::TestCase + test "weekday_abbr delegates through the current I18n locale" do + I18n.locale = :de + assert_equal "Mo", weekday_abbr(Time.parse("2026-07-06")) + end end diff --git a/test/models/helpers/nodes_helper_test.rb b/test/models/helpers/nodes_helper_test.rb index 13011de..5d91a88 100644 --- a/test/models/helpers/nodes_helper_test.rb +++ b/test/models/helpers/nodes_helper_test.rb @@ -1,4 +1,25 @@ require 'test_helper' class NodesHelperTest < ActionView::TestCase + FakePage = Struct.new(:tag_list) + + test "default_event_tag_mapping matches erfa-detail" do + page = FakePage.new(["erfa-detail"]) + assert_equal ["erfa-detail", "open-day"], default_event_tag_mapping(page) + end + + test "default_event_tag_mapping matches chaostreff-detail" do + page = FakePage.new(["chaostreff-detail"]) + assert_equal ["chaostreff-detail", "open-day"], default_event_tag_mapping(page) + end + + test "default_event_tag_mapping returns nil for unrelated tags" do + page = FakePage.new(["update"]) + assert_nil default_event_tag_mapping(page) + end + + test "default_event_tag_list is nil without a matching tag" do + page = FakePage.new([]) + assert_nil default_event_tag_list(page) + end end -- cgit v1.3