'
+ assert_includes html, 'class="max-w-6xl mx-auto px-6 text-center mb-16"'
+ assert_includes html, 'class="max-w-6xl mx-auto grid md:grid-cols-3 gap-8 px-6"'
+ end
+
+ test "renders the section header with proper styling" do
+ html = render_html
+ assert_includes html, 'Everything you need to shine
'
+ assert_includes html, 'Whoami gives you the tools to present yourself authentically online.
'
+ end
+
+ test "renders all six feature cards" do
+ html = render_html
+ feature_cards = html.scan(/class="rounded-xl bg-\[var\(--card\)\] ring-1 ring-\[var\(--border\)\] p-6 text-left"/).length
+ assert_equal 6, feature_cards, "Should render exactly 6 feature cards"
+ end
+
+ # --- content checks (now decode & normalize) ------------------------------
+
+ test "renders Profile & Bio feature" do
+ titles = feature_titles(render_html)
+ assert_includes titles, "Profile & Bio"
+ # also ensure description exists for that card
+ descs = feature_descriptions(render_html)
+ assert_includes descs, "Create a beautiful profile with your name, bio, and avatar. Think of it as your digital business card."
+ end
+
+ test "renders Links feature" do
+ titles = feature_titles(render_html)
+ assert_includes titles, "Links"
+ descs = feature_descriptions(render_html)
+ assert_includes descs, "Share your portfolio, social media, or favorite resources. Track clicks and see what resonates."
+ end
+
+ test "renders Blog feature" do
+ titles = feature_titles(render_html)
+ assert_includes titles, "Blog"
+ descs = feature_descriptions(render_html)
+ assert_includes descs, "Publish posts with a clean editor and rich text. Grow your voice, your way."
+ end
+
+ test "renders CV & Experience feature" do
+ titles = feature_titles(render_html)
+ assert_includes titles, "CV & Experience"
+ descs = feature_descriptions(render_html)
+ assert_includes descs, "Add your professional journey and let visitors download your CV instantly."
+ end
+
+ test "renders Analytics feature" do
+ titles = feature_titles(render_html)
+ assert_includes titles, "Analytics"
+ # curly vs straight apostrophe tolerant
+ descs = feature_descriptions(render_html)
+ expected = "Track profile views, link clicks, and post reads—see what's working."
+ assert_includes descs.map { |d| normalize_text(d) }, expected
+ end
+
+ test "renders Simple & Secure feature" do
+ titles = feature_titles(render_html)
+ assert_includes titles, "Simple & Secure"
+ descs = feature_descriptions(render_html)
+ expected = "Backed by Rails 8, Turbo, and secure authentication. You're in safe hands."
+ assert_includes descs.map { |d| normalize_text(d) }, expected
+ end
+
+ # --- consistency & theming (unchanged) ------------------------------------
+
+ test "all feature cards have consistent styling" do
+ html = render_html
+ expected_card_class = 'class="rounded-xl bg-[var(--card)] ring-1 ring-[var(--border)] p-6 text-left"'
+ card_count = html.scan(/#{Regexp.escape(expected_card_class)}/).length
+ assert_equal 6, card_count, "All 6 feature cards should have consistent styling"
+ end
+
+ test "all feature titles have consistent styling" do
+ html = render_html
+ h3_count = html.scan(/class="text-xl font-semibold mb-2"/).length
+ assert_equal 6, h3_count, "All 6 feature titles should have consistent styling"
+ end
+
+ test "all feature descriptions have consistent styling" do
+ html = render_html
+ muted_paragraphs = html.scan(/(?!Whoami gives you)/).length
+ assert_equal 6, muted_paragraphs, "All 6 feature descriptions should have muted text styling"
+ end
+
+ test "has proper responsive design classes" do
+ html = render_html
+ assert_includes html, "grid md:grid-cols-3"
+ assert_includes html, "text-3xl md:text-4xl"
+ max_width_containers = html.scan(/max-w-6xl/).length
+ assert_equal 2, max_width_containers, "Should have two max-w-6xl containers"
+ end
+
+ test "uses CSS custom properties for theming" do
+ html = render_html
+ assert_includes html, "var(--muted)"
+ assert_includes html, "var(--card)"
+ assert_includes html, "var(--border)"
+ end
+
+ test "has proper semantic structure" do
+ html = render_html
+ assert_includes html, "Hello, components!),
- # render_inline(FlashComponent.new(message: "Hello, components!")).css("span").to_html
- # )
+ test "renders notice message when notice is present" do
+ html = render_inline(FlashComponent.new(notice: "Successfully saved!", alert: nil)).to_html
+
+ # Check for notice container
+ assert_includes html, 'class="w-full fixed top-0 left-0 z-50"'
+
+ # Check for notice styling
+ assert_includes html, 'class="mx-auto max-w-2xl bg-[color-mix(in_srgb,var(--success)_12%,transparent)] text-[var(--success)] border border-[var(--success)] rounded-b-lg shadow-md px-4 py-3 text-center"'
+
+ # Check for notice content
+ assert_includes html, "Successfully saved!"
+ end
+
+ test "renders alert message when alert is present" do
+ html = render_inline(FlashComponent.new(notice: nil, alert: "Something went wrong!")).to_html
+
+ # Check for alert container
+ assert_includes html, 'class="w-full fixed top-0 left-0 z-50"'
+
+ # Check for alert styling
+ assert_includes html, 'class="mx-auto max-w-2xl bg-[color-mix(in_srgb,var(--danger)_12%,transparent)] text-[var(--danger)] border border-[var(--danger)] rounded-b-lg shadow-md px-4 py-3 text-center"'
+
+ # Check for alert content
+ assert_includes html, "Something went wrong!"
+ end
+
+ test "renders both notice and alert when both are present" do
+ html = render_inline(FlashComponent.new(notice: "Success message", alert: "Error message")).to_html
+
+ # Check that both messages are present
+ assert_includes html, "Success message"
+ assert_includes html, "Error message"
+
+ # Check that both have their respective styling
+ assert_includes html, "var(--success)"
+ assert_includes html, "var(--danger)"
+ end
+
+ test "renders nothing when both notice and alert are nil" do
+ html = render_inline(FlashComponent.new(notice: nil, alert: nil)).to_html
+
+ # Should be empty or just whitespace
+ assert html.strip.empty?, "Component should render nothing when both notice and alert are nil"
+ end
+
+ test "renders nothing when both notice and alert are blank strings" do
+ html = render_inline(FlashComponent.new(notice: "", alert: "")).to_html
+
+ # Should be empty or just whitespace
+ assert html.strip.empty?, "Component should render nothing when both notice and alert are blank"
+ end
+
+ test "notice uses success color scheme" do
+ html = render_inline(FlashComponent.new(notice: "Test notice", alert: nil)).to_html
+
+ # Check for success color variables
+ assert_includes html, "var(--success)"
+ assert_includes html, "color-mix(in_srgb,var(--success)_12%,transparent)"
+ assert_includes html, "border-[var(--success)]"
+ assert_includes html, "text-[var(--success)]"
+ end
+
+ test "alert uses danger color scheme" do
+ html = render_inline(FlashComponent.new(notice: nil, alert: "Test alert")).to_html
+
+ # Check for danger color variables
+ assert_includes html, "var(--danger)"
+ assert_includes html, "color-mix(in_srgb,var(--danger)_12%,transparent)"
+ assert_includes html, "border-[var(--danger)]"
+ assert_includes html, "text-[var(--danger)]"
+ end
+
+ test "flash messages have proper positioning and layout" do
+ html = render_inline(FlashComponent.new(notice: "Test", alert: nil)).to_html
+
+ # Check for fixed positioning at top
+ assert_includes html, "fixed top-0 left-0"
+
+ # Check for high z-index
+ assert_includes html, "z-50"
+
+ # Check for full width
+ assert_includes html, "w-full"
+
+ # Check for centered content with max width
+ assert_includes html, "mx-auto max-w-2xl"
+ end
+
+ test "flash messages have proper visual styling" do
+ html = render_inline(FlashComponent.new(notice: "Test", alert: nil)).to_html
+
+ # Check for rounded bottom corners
+ assert_includes html, "rounded-b-lg"
+
+ # Check for shadow
+ assert_includes html, "shadow-md"
+
+ # Check for padding
+ assert_includes html, "px-4 py-3"
+
+ # Check for centered text
+ assert_includes html, "text-center"
+ end
+
+ test "handles HTML content safely" do
+ html_content = "Safe content"
+ html = render_inline(FlashComponent.new(notice: html_content, alert: nil)).to_html
+
+ # Check that HTML is escaped (ViewComponent should handle this automatically)
+ assert_includes html, "<script>"
+ assert_includes html, "Safe content"
+ end
+
+ test "handles long messages" do
+ long_message = "This is a very long message that should still be displayed properly within the flash component " * 3
+ html = render_inline(FlashComponent.new(notice: long_message, alert: nil)).to_html
+
+ # Check that the message is included
+ assert_includes html, long_message
+
+ # Check that styling is still applied
+ assert_includes html, "max-w-2xl"
+ end
+
+ test "handles special characters in messages" do
+ special_message = "Üser nämé wäs sävéd! 💾 Success! 🎉"
+ html = render_inline(FlashComponent.new(notice: special_message, alert: nil)).to_html
+
+ # Check that special characters are handled properly
+ assert_includes html, special_message
+ end
+
+ test "uses modern CSS color-mix function for background" do
+ html = render_inline(FlashComponent.new(notice: "Test", alert: nil)).to_html
+
+ # Check for modern color-mix syntax
+ assert_includes html, "color-mix(in_srgb,var(--success)_12%,transparent)"
+
+ html = render_inline(FlashComponent.new(notice: nil, alert: "Test")).to_html
+ assert_includes html, "color-mix(in_srgb,var(--danger)_12%,transparent)"
+ end
+
+ test "multiple flash messages render in separate containers" do
+ html = render_inline(FlashComponent.new(notice: "Notice", alert: "Alert")).to_html
+
+ # Count the number of fixed containers
+ fixed_containers = html.scan(/class="w-full fixed top-0 left-0 z-50"/).length
+ assert_equal 2, fixed_containers, "Should have two separate fixed containers for notice and alert"
end
end
diff --git a/test/components/footer_component_test.rb b/test/components/footer_component_test.rb
new file mode 100644
index 0000000..04e1cba
--- /dev/null
+++ b/test/components/footer_component_test.rb
@@ -0,0 +1,128 @@
+require "test_helper"
+
+class FooterComponentTest < ViewComponent::TestCase
+ test "renders the footer with proper structure and styling" do
+ html = render_inline(FooterComponent.new).to_html
+
+ # Check for main footer wrapper
+ assert_includes html, '