Files
sure/test/models/period_test.rb
Guillem Arias Fauste 96079188a2 feat(dashboard): unify per-widget period selectors into one picker (#2162)
* feat(ds): elevate dropdown overlays and stabilize selection check gutter

Menus and popovers floated at the same elevation as inline cards
(shadow-border-xs), so dropdowns blended into the content beneath them.
Bump DS::Menu and DS::Popover panels to shadow-border-lg.

DS::MenuItem rendered its leading icon only when present, so a selection
check shifted the row's text out of alignment with the unselected rows.
Add a `selected:` param that reserves a fixed-width check gutter (check
when selected, empty otherwise) so row text stays aligned. Apply the same
reserved gutter to the bespoke category dropdown row, and add a
`selectable` menu preview.

* feat(dashboard): unify per-widget period selectors into one picker

The dashboard rendered three identical period <select>s (cashflow,
outflows, net worth), each writing the same global User#default_period
and full-reloading the page via turbo_frame "_top" — so changing one
changed all. Replace them with a single shared UI::PeriodPicker
(DS::Menu of period links) in a toolbar, and wrap the sections grid in a
"dashboard_sections" Turbo frame so a period change swaps only the
dashboard (no full-page reload). Reuse the same picker on the account
chart, removing its duplicate select.

* refactor(dashboard): use DS::MenuItem selected: gutter in period picker

Now that DS::MenuItem reserves a check gutter, the period picker passes
selected: instead of a conditional leading check icon, so the current
period's row stays aligned with the rest.

* fix(ds): expose menu selection via menuitemradio + aria-checked

Selectable DS::MenuItem rows conveyed selection only visually. Render them
as role="menuitemradio" with aria-checked so assistive tech gets the
selection state of single-select lists, merging the menu ARIA contract with
any caller-supplied aria. Addresses CodeRabbit review feedback.

* refactor(dashboard): drop redundant aria-current from period picker

DS::MenuItem now exposes selection via menuitemradio + aria-checked, so the
period picker no longer needs its own aria-current. Update the component
test to assert the new ARIA.

* fix(ds): include selectable roles in menu roving-focus query

DS::MenuItem selectable rows render as role=menuitemradio, but the menu
controller built its roving-focus list from [role=menuitem] only, leaving
single-select menus with no keyboard focus/arrow handling. Query the
menuitemradio/menuitemcheckbox roles too. Addresses Codex review feedback.

* fix(dashboard): keep non-picker links out of frame + fix custom-month picker

- turbo_frame_tag "dashboard_sections" now targets _top so ordinary links
  inside the sections (e.g. Balance Sheet account links) navigate the page
  instead of failing to resolve inside the frame.
- Period.current_month_for / last_month_for carry their semantic key for
  custom-month families, so the picker shows the right label and checks the
  right option instead of falling back to 30D.

Addresses Codex review feedback.

* fix(dashboard): announce selected period in picker trigger's accessible name

The static "Select time period" aria-label overrode the visible selected
label as the trigger's accessible name, so assistive tech kept announcing
the same name regardless of selection. Interpolate the selected short
label into the aria-label and pin it with a component test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 23:11:43 +02:00

108 lines
3.9 KiB
Ruby

require "test_helper"
class PeriodTest < ActiveSupport::TestCase
test "raises validation error when start_date or end_date is missing" do
error = assert_raises(ActiveModel::ValidationError) do
Period.new(start_date: nil, end_date: nil)
end
assert_includes error.message, "Start date can't be blank"
assert_includes error.message, "End date can't be blank"
end
test "raises validation error when start_date is not before end_date" do
error = assert_raises(ActiveModel::ValidationError) do
Period.new(start_date: Date.current, end_date: Date.current - 1.day)
end
assert_includes error.message, "Start date must be before end date"
end
test "can create custom period" do
period = Period.new(start_date: Date.current - 15.days, end_date: Date.current)
assert_equal "Custom Period", period.label
end
test "from_key returns period for valid key" do
period = Period.from_key("last_30_days")
assert_equal 30.days.ago.to_date, period.start_date
assert_equal Date.current, period.end_date
end
test "from_key with invalid key and no fallback raises error" do
error = assert_raises(Period::InvalidKeyError) do
Period.from_key("invalid_key")
end
end
test "label returns correct label for known period" do
period = Period.from_key("last_30_days")
assert_equal "Last 30 Days", period.label
end
test "label returns Custom Period for unknown period" do
period = Period.new(start_date: Date.current - 15.days, end_date: Date.current)
assert_equal "Custom Period", period.label
end
test "all_time period can be created" do
period = Period.from_key("all_time")
assert_equal "all_time", period.key
assert_equal "All Time", period.label
assert_equal "All", period.label_short
end
test "all_time period uses family's oldest entry date" do
# Mock Current.family to return a family with oldest_entry_date
mock_family = mock("family")
mock_family.expects(:oldest_entry_date).returns(2.years.ago.to_date)
Current.expects(:family).at_least_once.returns(mock_family)
period = Period.from_key("all_time")
assert_equal 2.years.ago.to_date, period.start_date
assert_equal Date.current, period.end_date
end
test "current_month_for preserves the current_month key for custom-month families" do
family = mock("family")
family.stubs(:uses_custom_month_start?).returns(true)
family.stubs(:current_custom_month_period).returns(
Period.custom(start_date: Date.new(2026, 6, 5), end_date: Date.new(2026, 7, 4))
)
period = Period.current_month_for(family)
assert_equal "current_month", period.key
assert_equal Date.new(2026, 6, 5), period.start_date
assert_equal Date.new(2026, 7, 4), period.end_date
end
test "last_month_for preserves the last_month key for custom-month families" do
family = mock("family")
family.stubs(:uses_custom_month_start?).returns(true)
family.stubs(:custom_month_start_for).returns(Date.new(2026, 5, 5))
family.stubs(:custom_month_end_for).returns(Date.new(2026, 6, 4))
period = Period.last_month_for(family)
assert_equal "last_month", period.key
end
test "all_time period uses fallback when no family or entries exist" do
Current.expects(:family).returns(nil)
period = Period.from_key("all_time")
assert_equal 5.years.ago.to_date, period.start_date
assert_equal Date.current, period.end_date
end
test "all_time period uses fallback when oldest_entry_date equals current date" do
# Mock a family that has no historical entries (oldest_entry_date returns today)
mock_family = mock("family")
mock_family.expects(:oldest_entry_date).returns(Date.current)
Current.expects(:family).at_least_once.returns(mock_family)
period = Period.from_key("all_time")
assert_equal 5.years.ago.to_date, period.start_date
assert_equal Date.current, period.end_date
end
end