Files
sure/test/components/DS/pill_test.rb
Guillem Arias Fauste d88d6e9e58 fix(ds): canonical transaction-row — stop category pills truncating (#2147)
* fix(ds): canonical transaction-row — stop category pills truncating (#2137)

The desktop transaction grid gave the name column col-span-8 (67%, usually
half-empty) and the category column only col-span-2 (17%), so the category
pill's name area was clamped to ~44px and ellipsized nearly every value
("Misc...", "Shop...", "Rest...") despite the empty space the audit flagged.

- Rebalance the lg:grid-cols-12 row: name col-span-8 -> 7, category 2 -> 3
  (amount unchanged; still 12). Category gains 50% width from the over-wide
  name column.
- categories/_badge: the pill was `flex w-full` (stretched to fill the column);
  make it `inline-flex max-w-full` so it hugs its content and short names render
  fully, capping + truncating only when genuinely too long.

Verified on /transactions: visible-row category truncation dropped 7/7 -> 2/7
even in the compressed (AI-panel-open) view; Payment / Shopping / Restaurants
now render in full. Fixes both the interactive categories/menu and the static
transfer categories/badge (menu reuses the badge partial).

* feat(ds): lift categories/_badge onto DS::Pill

Addresses the Drift Patrol finding (and jjmata's request) properly
instead of patching the bespoke span: the badge becomes a DS::Pill in
badge mode. The pill primitive grows the three capabilities the badge
needed and the bot's one-liner glossed over:

- truncate: pills that may shrink inside min-w-0 columns drop their
  shrink-0/whitespace-nowrap and ellipsize the label instead of
  overflowing (the transaction-row category cell this PR fixes).
- label_testid: stamps data-testid on the label span; five test files
  target [data-testid='category-name'].
- icon_size: passthrough to the icon helper (badge keeps its
  established sm glyph; default stays xs).

custom_color was already the sanctioned escape hatch for user-chosen
hues. Owned visual deltas from pill standardization: px-1.5/py-1 ->
px-2/py-0.5, gap-1 -> gap-1.5, border mix 10% -> 20%; bg mix, hex text,
radius, text scale, icon size, truncation and testid are parity.

Label wrapper only renders when truncate/label_testid ask for it, so
existing pill DOM (and the find("span", text:) assertions on it) is
unchanged. Four new pill tests + a Lookbook case cover the recipe.
2026-06-06 16:28:22 +02:00

127 lines
5.0 KiB
Ruby

require "test_helper"
class DS::PillTest < ViewComponent::TestCase
test "marker mode (default) renders uppercase sub-12px chrome with rounded-md" do
render_inline(DS::Pill.new(label: "Beta", tone: :violet))
pill = page.find("span", text: "Beta")
assert_includes pill[:class], "uppercase"
# Marker keeps sub-12px text via arbitrary value (intentional — see component docs).
assert_match(/text-\[1[01]px\]/, pill[:class])
# Marker uses rounded-md (chip shape).
assert_includes pill[:class], "rounded-md"
refute_includes pill[:class], "rounded-full"
end
test "marker: false renders normal-case DS-scale chrome with rounded-full" do
render_inline(DS::Pill.new(label: "Active", tone: :success, marker: false))
pill = page.find("span", text: "Active")
refute_includes pill[:class], "uppercase"
# Badge mode snaps to text-xs / text-sm — no sub-12px arbitrary values.
assert_match(/text-(xs|sm)/, pill[:class])
refute_match(/text-\[1[01]px\]/, pill[:class])
# Badge uses rounded-full to match the existing _status_pill / _maturity_badge convention.
assert_includes pill[:class], "rounded-full"
refute_includes pill[:class], "rounded-md"
end
test "semantic tone aliases resolve to visual palette tones" do
{
success: :green,
warning: :amber,
error: :red,
destructive: :red,
info: :indigo,
neutral: :gray
}.each do |alias_name, expected_visual|
pill = DS::Pill.new(label: "x", tone: alias_name)
assert_equal expected_visual, pill.tone, "Expected #{alias_name}#{expected_visual}, got #{pill.tone}"
end
end
test "unknown tone falls back to violet" do
pill = DS::Pill.new(label: "x", tone: :nonexistent)
assert_equal :violet, pill.tone
end
test "red tone palette resolves to red-* tokens" do
pill = DS::Pill.new(label: "Failed", tone: :error)
assert_includes pill.palette[:dot], "color-red-500"
assert_includes pill.palette[:bg], "color-red-50"
end
test "marker mode shows the dot by default" do
render_inline(DS::Pill.new(label: "Beta", tone: :violet))
assert_selector "span.inline-block.rounded-full"
end
test "badge mode (marker: false) is dot-less by default" do
render_inline(DS::Pill.new(label: "Member", tone: :neutral, marker: false))
assert_no_selector "span.inline-block.rounded-full"
end
test "badge mode opts back into the dot with show_dot: true" do
render_inline(DS::Pill.new(label: "Active", tone: :success, marker: false, show_dot: true))
assert_selector "span.inline-block.rounded-full"
end
test "marker mode can drop the dot with show_dot: false" do
render_inline(DS::Pill.new(label: "Beta", tone: :violet, show_dot: false))
assert_no_selector "span.inline-block.rounded-full"
end
test "custom color renders dynamic badge styles" do
render_inline(DS::Pill.new(label: "Groceries", marker: false, custom_color: "#f97316"))
pill = page.find("span", text: "Groceries")
assert_includes pill[:style], "color-mix(in oklab, #f97316 10%, transparent)"
assert_includes pill[:style], "color: #f97316"
end
test "icon option renders glyph in place of dot" do
render_inline(DS::Pill.new(label: "Syncing", tone: :info, marker: false, icon: "loader"))
# Lucide icon helper renders the inline SVG; verifying we see at least one <svg>
# is enough — the icon helper is covered by its own tests.
assert_selector "svg"
# And the dot is suppressed when an icon takes its place. The dot is an
# `inline-block` span (parent pill is `inline-flex`), so target it by
# `inline-block.rounded-full` to avoid matching the parent pill.
assert_no_selector "span.inline-block.rounded-full"
end
test "truncate: true lets the pill shrink and ellipsizes the label" do
render_inline(DS::Pill.new(label: "A very long category name", marker: false, truncate: true))
pill = page.find("span[title='A very long category name']")
assert_includes pill[:class], "max-w-full"
assert_includes pill[:class], "min-w-0"
refute_includes pill[:class], "shrink-0"
refute_includes pill[:class], "whitespace-nowrap"
assert_selector "span.min-w-0.truncate", text: "A very long category name"
end
test "default pills keep intrinsic width (no truncation chrome)" do
render_inline(DS::Pill.new(label: "Active", marker: false))
pill = page.find("span", text: "Active")
assert_includes pill[:class], "shrink-0"
assert_includes pill[:class], "whitespace-nowrap"
assert_no_selector "span.truncate"
end
test "label_testid stamps data-testid on the label span" do
render_inline(DS::Pill.new(label: "Groceries", marker: false, label_testid: "category-name"))
assert_selector "span[data-testid='category-name']", text: "Groceries"
end
test "icon_size passes through to the icon helper" do
render_inline(DS::Pill.new(label: "Food", marker: false, icon: "utensils", icon_size: "sm"))
# sm maps to w-4 h-4 in the icon helper's size table.
assert_selector "svg.w-4.h-4"
end
end