velvetkit 0.1.0.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +16 -0
- data/LICENSE.txt +21 -0
- data/README.md +197 -0
- data/lib/generators/velvetkit/add/add_generator.rb +111 -0
- data/lib/generators/velvetkit/ai/ai_generator.rb +78 -0
- data/lib/generators/velvetkit/helpers.rb +64 -0
- data/lib/generators/velvetkit/install/install_generator.rb +108 -0
- data/lib/velvetkit/namespace.rb +57 -0
- data/lib/velvetkit/reference.rb +108 -0
- data/lib/velvetkit/registry.rb +162 -0
- data/lib/velvetkit/version.rb +5 -0
- data/lib/velvetkit.rb +28 -0
- data/registry/app/assets/tailwind/velvetkit.css +342 -0
- data/registry/app/components/ui/accordion/content.html.erb +5 -0
- data/registry/app/components/ui/accordion/content.rb +34 -0
- data/registry/app/components/ui/accordion/item.html.erb +1 -0
- data/registry/app/components/ui/accordion/item.rb +40 -0
- data/registry/app/components/ui/accordion/trigger.html.erb +1 -0
- data/registry/app/components/ui/accordion/trigger.rb +37 -0
- data/registry/app/components/ui/accordion.html.erb +7 -0
- data/registry/app/components/ui/accordion.rb +34 -0
- data/registry/app/components/ui/alert/description.html.erb +1 -0
- data/registry/app/components/ui/alert/description.rb +12 -0
- data/registry/app/components/ui/alert/title.html.erb +1 -0
- data/registry/app/components/ui/alert/title.rb +12 -0
- data/registry/app/components/ui/alert.html.erb +16 -0
- data/registry/app/components/ui/alert.rb +55 -0
- data/registry/app/components/ui/avatar/fallback.html.erb +1 -0
- data/registry/app/components/ui/avatar/fallback.rb +12 -0
- data/registry/app/components/ui/avatar/image.rb +20 -0
- data/registry/app/components/ui/avatar.html.erb +1 -0
- data/registry/app/components/ui/avatar.rb +56 -0
- data/registry/app/components/ui/avatar_group.html.erb +1 -0
- data/registry/app/components/ui/avatar_group.rb +35 -0
- data/registry/app/components/ui/badge.html.erb +1 -0
- data/registry/app/components/ui/badge.rb +38 -0
- data/registry/app/components/ui/banner.html.erb +10 -0
- data/registry/app/components/ui/banner.rb +47 -0
- data/registry/app/components/ui/base.rb +53 -0
- data/registry/app/components/ui/breadcrumb/auto_ellipsis.html.erb +23 -0
- data/registry/app/components/ui/breadcrumb/auto_ellipsis.rb +12 -0
- data/registry/app/components/ui/breadcrumb/ellipsis.html.erb +18 -0
- data/registry/app/components/ui/breadcrumb/ellipsis.rb +8 -0
- data/registry/app/components/ui/breadcrumb/item.html.erb +1 -0
- data/registry/app/components/ui/breadcrumb/item.rb +12 -0
- data/registry/app/components/ui/breadcrumb/item_slot.html.erb +10 -0
- data/registry/app/components/ui/breadcrumb/item_slot.rb +47 -0
- data/registry/app/components/ui/breadcrumb/list.html.erb +1 -0
- data/registry/app/components/ui/breadcrumb/list.rb +12 -0
- data/registry/app/components/ui/breadcrumb/separator.html.erb +1 -0
- data/registry/app/components/ui/breadcrumb/separator.rb +12 -0
- data/registry/app/components/ui/breadcrumb.html.erb +30 -0
- data/registry/app/components/ui/breadcrumb.rb +83 -0
- data/registry/app/components/ui/button.html.erb +1 -0
- data/registry/app/components/ui/button.rb +81 -0
- data/registry/app/components/ui/button_to.html.erb +1 -0
- data/registry/app/components/ui/button_to.rb +33 -0
- data/registry/app/components/ui/card/content.html.erb +1 -0
- data/registry/app/components/ui/card/content.rb +12 -0
- data/registry/app/components/ui/card/description.html.erb +1 -0
- data/registry/app/components/ui/card/description.rb +12 -0
- data/registry/app/components/ui/card/footer.html.erb +1 -0
- data/registry/app/components/ui/card/footer.rb +12 -0
- data/registry/app/components/ui/card/header.html.erb +1 -0
- data/registry/app/components/ui/card/header.rb +15 -0
- data/registry/app/components/ui/card/title.html.erb +1 -0
- data/registry/app/components/ui/card/title.rb +12 -0
- data/registry/app/components/ui/card.html.erb +1 -0
- data/registry/app/components/ui/card.rb +18 -0
- data/registry/app/components/ui/checkbox.html.erb +18 -0
- data/registry/app/components/ui/checkbox.rb +133 -0
- data/registry/app/components/ui/data_grid.html.erb +2 -0
- data/registry/app/components/ui/data_grid.rb +84 -0
- data/registry/app/components/ui/date_picker.html.erb +70 -0
- data/registry/app/components/ui/date_picker.rb +111 -0
- data/registry/app/components/ui/dialog/close.html.erb +7 -0
- data/registry/app/components/ui/dialog/close.rb +53 -0
- data/registry/app/components/ui/dialog/content.html.erb +26 -0
- data/registry/app/components/ui/dialog/content.rb +102 -0
- data/registry/app/components/ui/dialog/description.html.erb +1 -0
- data/registry/app/components/ui/dialog/description.rb +16 -0
- data/registry/app/components/ui/dialog/footer.html.erb +1 -0
- data/registry/app/components/ui/dialog/footer.rb +14 -0
- data/registry/app/components/ui/dialog/header.html.erb +1 -0
- data/registry/app/components/ui/dialog/header.rb +16 -0
- data/registry/app/components/ui/dialog/title.html.erb +1 -0
- data/registry/app/components/ui/dialog/title.rb +16 -0
- data/registry/app/components/ui/dialog/trigger.html.erb +5 -0
- data/registry/app/components/ui/dialog/trigger.rb +75 -0
- data/registry/app/components/ui/dialog.html.erb +3 -0
- data/registry/app/components/ui/dialog.rb +41 -0
- data/registry/app/components/ui/divider.html.erb +9 -0
- data/registry/app/components/ui/divider.rb +41 -0
- data/registry/app/components/ui/icon.html.erb +1 -0
- data/registry/app/components/ui/icon.rb +41 -0
- data/registry/app/components/ui/image.html.erb +1 -0
- data/registry/app/components/ui/image.rb +74 -0
- data/registry/app/components/ui/input.html.erb +9 -0
- data/registry/app/components/ui/input.rb +127 -0
- data/registry/app/components/ui/input_group/addon.html.erb +6 -0
- data/registry/app/components/ui/input_group/addon.rb +51 -0
- data/registry/app/components/ui/input_group/field.rb +26 -0
- data/registry/app/components/ui/input_group.html.erb +5 -0
- data/registry/app/components/ui/input_group.rb +55 -0
- data/registry/app/components/ui/label.html.erb +2 -0
- data/registry/app/components/ui/label.rb +48 -0
- data/registry/app/components/ui/link.html.erb +1 -0
- data/registry/app/components/ui/link.rb +64 -0
- data/registry/app/components/ui/menu/content.html.erb +5 -0
- data/registry/app/components/ui/menu/content.rb +106 -0
- data/registry/app/components/ui/menu/group.html.erb +1 -0
- data/registry/app/components/ui/menu/group.rb +19 -0
- data/registry/app/components/ui/menu/item.html.erb +1 -0
- data/registry/app/components/ui/menu/item.rb +68 -0
- data/registry/app/components/ui/menu/label.html.erb +1 -0
- data/registry/app/components/ui/menu/label.rb +33 -0
- data/registry/app/components/ui/menu/separator.html.erb +1 -0
- data/registry/app/components/ui/menu/separator.rb +20 -0
- data/registry/app/components/ui/menu/trigger.html.erb +1 -0
- data/registry/app/components/ui/menu/trigger.rb +29 -0
- data/registry/app/components/ui/menu.html.erb +3 -0
- data/registry/app/components/ui/menu.rb +23 -0
- data/registry/app/components/ui/native_select.html.erb +16 -0
- data/registry/app/components/ui/native_select.rb +109 -0
- data/registry/app/components/ui/pagination.html.erb +34 -0
- data/registry/app/components/ui/pagination.rb +62 -0
- data/registry/app/components/ui/password_input.html.erb +20 -0
- data/registry/app/components/ui/password_input.rb +94 -0
- data/registry/app/components/ui/popover/content.html.erb +12 -0
- data/registry/app/components/ui/popover/content.rb +83 -0
- data/registry/app/components/ui/popover/trigger.html.erb +1 -0
- data/registry/app/components/ui/popover/trigger.rb +29 -0
- data/registry/app/components/ui/popover.html.erb +3 -0
- data/registry/app/components/ui/popover.rb +24 -0
- data/registry/app/components/ui/radio_group/item.html.erb +18 -0
- data/registry/app/components/ui/radio_group/item.rb +97 -0
- data/registry/app/components/ui/radio_group.html.erb +1 -0
- data/registry/app/components/ui/radio_group.rb +35 -0
- data/registry/app/components/ui/select.html.erb +60 -0
- data/registry/app/components/ui/select.rb +181 -0
- data/registry/app/components/ui/sheet/close.rb +4 -0
- data/registry/app/components/ui/sheet/content.html.erb +24 -0
- data/registry/app/components/ui/sheet/content.rb +97 -0
- data/registry/app/components/ui/sheet/trigger.rb +4 -0
- data/registry/app/components/ui/sheet.html.erb +3 -0
- data/registry/app/components/ui/sheet.rb +37 -0
- data/registry/app/components/ui/sidebar/content.html.erb +1 -0
- data/registry/app/components/ui/sidebar/content.rb +18 -0
- data/registry/app/components/ui/sidebar/footer.html.erb +3 -0
- data/registry/app/components/ui/sidebar/footer.rb +16 -0
- data/registry/app/components/ui/sidebar/group.html.erb +1 -0
- data/registry/app/components/ui/sidebar/group.rb +19 -0
- data/registry/app/components/ui/sidebar/group_label.html.erb +1 -0
- data/registry/app/components/ui/sidebar/group_label.rb +18 -0
- data/registry/app/components/ui/sidebar/header.html.erb +1 -0
- data/registry/app/components/ui/sidebar/header.rb +16 -0
- data/registry/app/components/ui/sidebar/inset.html.erb +1 -0
- data/registry/app/components/ui/sidebar/inset.rb +17 -0
- data/registry/app/components/ui/sidebar/menu.html.erb +1 -0
- data/registry/app/components/ui/sidebar/menu.rb +18 -0
- data/registry/app/components/ui/sidebar/menu_button.html.erb +1 -0
- data/registry/app/components/ui/sidebar/menu_button.rb +30 -0
- data/registry/app/components/ui/sidebar/menu_item.html.erb +1 -0
- data/registry/app/components/ui/sidebar/menu_item.rb +23 -0
- data/registry/app/components/ui/sidebar/trigger.html.erb +1 -0
- data/registry/app/components/ui/sidebar/trigger.rb +24 -0
- data/registry/app/components/ui/sidebar/wrapper.html.erb +1 -0
- data/registry/app/components/ui/sidebar/wrapper.rb +40 -0
- data/registry/app/components/ui/sidebar.html.erb +11 -0
- data/registry/app/components/ui/sidebar.rb +29 -0
- data/registry/app/components/ui/spinner.html.erb +1 -0
- data/registry/app/components/ui/spinner.rb +20 -0
- data/registry/app/components/ui/switch.html.erb +12 -0
- data/registry/app/components/ui/switch.rb +109 -0
- data/registry/app/components/ui/table/cell.html.erb +1 -0
- data/registry/app/components/ui/table/cell.rb +16 -0
- data/registry/app/components/ui/table/column.html.erb +1 -0
- data/registry/app/components/ui/table/column.rb +18 -0
- data/registry/app/components/ui/table/head.html.erb +1 -0
- data/registry/app/components/ui/table/head.rb +13 -0
- data/registry/app/components/ui/table/row.html.erb +1 -0
- data/registry/app/components/ui/table/row.rb +26 -0
- data/registry/app/components/ui/table.html.erb +4 -0
- data/registry/app/components/ui/table.rb +45 -0
- data/registry/app/components/ui/tabs/content.html.erb +1 -0
- data/registry/app/components/ui/tabs/content.rb +43 -0
- data/registry/app/components/ui/tabs/list.html.erb +1 -0
- data/registry/app/components/ui/tabs/list.rb +28 -0
- data/registry/app/components/ui/tabs/trigger.html.erb +1 -0
- data/registry/app/components/ui/tabs/trigger.rb +91 -0
- data/registry/app/components/ui/tabs.html.erb +3 -0
- data/registry/app/components/ui/tabs.rb +27 -0
- data/registry/app/components/ui/text.html.erb +1 -0
- data/registry/app/components/ui/text.rb +48 -0
- data/registry/app/components/ui/theme_toggle.html.erb +9 -0
- data/registry/app/components/ui/theme_toggle.rb +63 -0
- data/registry/app/components/ui/tooltip/content.html.erb +1 -0
- data/registry/app/components/ui/tooltip/content.rb +65 -0
- data/registry/app/components/ui/tooltip/trigger.html.erb +1 -0
- data/registry/app/components/ui/tooltip/trigger.rb +33 -0
- data/registry/app/components/ui/tooltip.html.erb +3 -0
- data/registry/app/components/ui/tooltip.rb +24 -0
- data/registry/app/components/ui/typography/blockquote.html.erb +1 -0
- data/registry/app/components/ui/typography/blockquote.rb +15 -0
- data/registry/app/components/ui/typography/code.html.erb +1 -0
- data/registry/app/components/ui/typography/code.rb +25 -0
- data/registry/app/components/ui/typography/h1.rb +7 -0
- data/registry/app/components/ui/typography/h2.rb +7 -0
- data/registry/app/components/ui/typography/h3.rb +7 -0
- data/registry/app/components/ui/typography/h4.rb +7 -0
- data/registry/app/components/ui/typography/large.rb +7 -0
- data/registry/app/components/ui/typography/lead.rb +7 -0
- data/registry/app/components/ui/typography/muted.rb +7 -0
- data/registry/app/components/ui/typography/small.rb +7 -0
- data/registry/app/javascript/ag_grid.ts +26 -0
- data/registry/app/javascript/cally.ts +8 -0
- data/registry/app/javascript/controllers/ui/_internal/use_dismiss.ts +88 -0
- data/registry/app/javascript/controllers/ui/_internal/use_floating.ts +112 -0
- data/registry/app/javascript/controllers/ui/accordion_controller.ts +100 -0
- data/registry/app/javascript/controllers/ui/alert_controller.ts +7 -0
- data/registry/app/javascript/controllers/ui/banner_controller.ts +7 -0
- data/registry/app/javascript/controllers/ui/data_grid_controller.ts +198 -0
- data/registry/app/javascript/controllers/ui/date_picker_controller.ts +141 -0
- data/registry/app/javascript/controllers/ui/dialog_controller.ts +70 -0
- data/registry/app/javascript/controllers/ui/menu_controller.ts +201 -0
- data/registry/app/javascript/controllers/ui/password_visibility_controller.ts +43 -0
- data/registry/app/javascript/controllers/ui/popover_controller.ts +108 -0
- data/registry/app/javascript/controllers/ui/select_controller.ts +274 -0
- data/registry/app/javascript/controllers/ui/sidebar_controller.ts +64 -0
- data/registry/app/javascript/controllers/ui/tabs_controller.ts +74 -0
- data/registry/app/javascript/controllers/ui/theme_controller.ts +101 -0
- data/registry/app/javascript/controllers/ui/tooltip_controller.ts +102 -0
- data/registry/app/javascript/velvetkit_icons.ts +58 -0
- data/registry/app/views/layouts/_theme_script.html.erb +19 -0
- data/registry/components.yml +102 -0
- data/registry/config/locales/velvetkit.en.yml +56 -0
- data/registry/docs/agents.md +62 -0
- data/registry/test/components/previews/ui/accordion_preview/faq_style.html.erb +20 -0
- data/registry/test/components/previews/ui/accordion_preview/multiple.html.erb +27 -0
- data/registry/test/components/previews/ui/accordion_preview/single.html.erb +22 -0
- data/registry/test/components/previews/ui/accordion_preview.rb +31 -0
- data/registry/test/components/previews/ui/alert_preview/title_only.html.erb +12 -0
- data/registry/test/components/previews/ui/alert_preview/variants.html.erb +22 -0
- data/registry/test/components/previews/ui/alert_preview/with_icon.html.erb +12 -0
- data/registry/test/components/previews/ui/alert_preview/with_link.html.erb +18 -0
- data/registry/test/components/previews/ui/alert_preview.rb +34 -0
- data/registry/test/components/previews/ui/avatar_group_preview/default.html.erb +11 -0
- data/registry/test/components/previews/ui/avatar_group_preview/overflow.html.erb +23 -0
- data/registry/test/components/previews/ui/avatar_group_preview/small.html.erb +12 -0
- data/registry/test/components/previews/ui/avatar_group_preview.rb +23 -0
- data/registry/test/components/previews/ui/avatar_preview/colors.html.erb +8 -0
- data/registry/test/components/previews/ui/avatar_preview/shapes.html.erb +10 -0
- data/registry/test/components/previews/ui/avatar_preview/sizes.html.erb +8 -0
- data/registry/test/components/previews/ui/avatar_preview/with_image.html.erb +10 -0
- data/registry/test/components/previews/ui/avatar_preview.rb +24 -0
- data/registry/test/components/previews/ui/badge_preview/sizes.html.erb +5 -0
- data/registry/test/components/previews/ui/badge_preview/variants.html.erb +8 -0
- data/registry/test/components/previews/ui/badge_preview/with_icon.html.erb +14 -0
- data/registry/test/components/previews/ui/badge_preview.rb +20 -0
- data/registry/test/components/previews/ui/banner_preview/introduction.html.erb +9 -0
- data/registry/test/components/previews/ui/banner_preview/promotion.html.erb +9 -0
- data/registry/test/components/previews/ui/banner_preview/with_illustration.html.erb +12 -0
- data/registry/test/components/previews/ui/banner_preview.rb +25 -0
- data/registry/test/components/previews/ui/breadcrumb_preview/collapsed.html.erb +50 -0
- data/registry/test/components/previews/ui/breadcrumb_preview/default.html.erb +8 -0
- data/registry/test/components/previews/ui/breadcrumb_preview/long_path.html.erb +8 -0
- data/registry/test/components/previews/ui/breadcrumb_preview/medium.html.erb +7 -0
- data/registry/test/components/previews/ui/breadcrumb_preview/with_avatar.html.erb +8 -0
- data/registry/test/components/previews/ui/breadcrumb_preview.rb +66 -0
- data/registry/test/components/previews/ui/button_preview/disabled.html.erb +39 -0
- data/registry/test/components/previews/ui/button_preview/icon_only.html.erb +17 -0
- data/registry/test/components/previews/ui/button_preview/loading.html.erb +12 -0
- data/registry/test/components/previews/ui/button_preview/sizes.html.erb +6 -0
- data/registry/test/components/previews/ui/button_preview/variants.html.erb +8 -0
- data/registry/test/components/previews/ui/button_preview/with_both_icons.html.erb +12 -0
- data/registry/test/components/previews/ui/button_preview/with_leading_icon.html.erb +22 -0
- data/registry/test/components/previews/ui/button_preview/with_trailing_icon.html.erb +14 -0
- data/registry/test/components/previews/ui/button_preview.rb +65 -0
- data/registry/test/components/previews/ui/button_to_preview/states.html.erb +8 -0
- data/registry/test/components/previews/ui/button_to_preview/variants.html.erb +5 -0
- data/registry/test/components/previews/ui/button_to_preview/with_icon.html.erb +11 -0
- data/registry/test/components/previews/ui/button_to_preview.rb +21 -0
- data/registry/test/components/previews/ui/card_preview/complete.html.erb +31 -0
- data/registry/test/components/previews/ui/card_preview/default.html.erb +7 -0
- data/registry/test/components/previews/ui/card_preview/settings_form.html.erb +25 -0
- data/registry/test/components/previews/ui/card_preview/with_footer.html.erb +13 -0
- data/registry/test/components/previews/ui/card_preview/with_header.html.erb +11 -0
- data/registry/test/components/previews/ui/card_preview.rb +44 -0
- data/registry/test/components/previews/ui/checkbox_preview/in_form.html.erb +9 -0
- data/registry/test/components/previews/ui/checkbox_preview/states.html.erb +22 -0
- data/registry/test/components/previews/ui/checkbox_preview/with_label.html.erb +5 -0
- data/registry/test/components/previews/ui/checkbox_preview.rb +27 -0
- data/registry/test/components/previews/ui/data_grid_preview.rb +18 -0
- data/registry/test/components/previews/ui/date_picker_preview/default.html.erb +3 -0
- data/registry/test/components/previews/ui/date_picker_preview/states.html.erb +7 -0
- data/registry/test/components/previews/ui/date_picker_preview.rb +13 -0
- data/registry/test/components/previews/ui/dialog_preview/confirmation.html.erb +11 -0
- data/registry/test/components/previews/ui/dialog_preview/custom_trigger.html.erb +16 -0
- data/registry/test/components/previews/ui/dialog_preview/default.html.erb +15 -0
- data/registry/test/components/previews/ui/dialog_preview/long_content.html.erb +16 -0
- data/registry/test/components/previews/ui/dialog_preview/playground.html.erb +12 -0
- data/registry/test/components/previews/ui/dialog_preview/scrollable_body.html.erb +29 -0
- data/registry/test/components/previews/ui/dialog_preview/sizes.html.erb +46 -0
- data/registry/test/components/previews/ui/dialog_preview/without_close_button.html.erb +11 -0
- data/registry/test/components/previews/ui/dialog_preview/without_title.html.erb +13 -0
- data/registry/test/components/previews/ui/dialog_preview.rb +38 -0
- data/registry/test/components/previews/ui/divider_preview/horizontal.html.erb +5 -0
- data/registry/test/components/previews/ui/divider_preview/vertical.html.erb +9 -0
- data/registry/test/components/previews/ui/divider_preview/with_label.html.erb +5 -0
- data/registry/test/components/previews/ui/divider_preview.rb +19 -0
- data/registry/test/components/previews/ui/icon_preview/common.html.erb +8 -0
- data/registry/test/components/previews/ui/icon_preview/sizes.html.erb +7 -0
- data/registry/test/components/previews/ui/icon_preview.rb +20 -0
- data/registry/test/components/previews/ui/image_preview/radii.html.erb +8 -0
- data/registry/test/components/previews/ui/image_preview/sizing.html.erb +9 -0
- data/registry/test/components/previews/ui/image_preview.rb +16 -0
- data/registry/test/components/previews/ui/input_group_preview/addons.html.erb +29 -0
- data/registry/test/components/previews/ui/input_group_preview/playground.html.erb +12 -0
- data/registry/test/components/previews/ui/input_group_preview.rb +17 -0
- data/registry/test/components/previews/ui/input_preview/sizes.html.erb +5 -0
- data/registry/test/components/previews/ui/input_preview/states.html.erb +7 -0
- data/registry/test/components/previews/ui/input_preview/with_labels.html.erb +10 -0
- data/registry/test/components/previews/ui/input_preview.rb +33 -0
- data/registry/test/components/previews/ui/label_preview/sizes.html.erb +6 -0
- data/registry/test/components/previews/ui/label_preview/states.html.erb +6 -0
- data/registry/test/components/previews/ui/label_preview/weights.html.erb +5 -0
- data/registry/test/components/previews/ui/label_preview.rb +22 -0
- data/registry/test/components/previews/ui/link_preview/sizes.html.erb +5 -0
- data/registry/test/components/previews/ui/link_preview/variants.html.erb +8 -0
- data/registry/test/components/previews/ui/link_preview/with_icons.html.erb +11 -0
- data/registry/test/components/previews/ui/link_preview.rb +20 -0
- data/registry/test/components/previews/ui/menu_preview/positions.html.erb +122 -0
- data/registry/test/components/previews/ui/menu_preview/variants.html.erb +218 -0
- data/registry/test/components/previews/ui/menu_preview.rb +26 -0
- data/registry/test/components/previews/ui/native_select_preview/sizes.html.erb +6 -0
- data/registry/test/components/previews/ui/native_select_preview/states.html.erb +6 -0
- data/registry/test/components/previews/ui/native_select_preview.rb +23 -0
- data/registry/test/components/previews/ui/pagination_preview/states.html.erb +15 -0
- data/registry/test/components/previews/ui/pagination_preview.rb +15 -0
- data/registry/test/components/previews/ui/password_input_preview/states.html.erb +19 -0
- data/registry/test/components/previews/ui/password_input_preview.rb +21 -0
- data/registry/test/components/previews/ui/popover_preview/default.html.erb +7 -0
- data/registry/test/components/previews/ui/popover_preview/positions.html.erb +30 -0
- data/registry/test/components/previews/ui/popover_preview/with_close_button.html.erb +8 -0
- data/registry/test/components/previews/ui/popover_preview/with_rich_content.html.erb +17 -0
- data/registry/test/components/previews/ui/popover_preview/without_title.html.erb +9 -0
- data/registry/test/components/previews/ui/popover_preview.rb +32 -0
- data/registry/test/components/previews/ui/radio_group_preview/default.html.erb +10 -0
- data/registry/test/components/previews/ui/radio_group_preview/disabled.html.erb +17 -0
- data/registry/test/components/previews/ui/radio_group_preview/with_required.html.erb +10 -0
- data/registry/test/components/previews/ui/radio_group_preview.rb +25 -0
- data/registry/test/components/previews/ui/select_preview/default.html.erb +4 -0
- data/registry/test/components/previews/ui/select_preview/long_list.html.erb +5 -0
- data/registry/test/components/previews/ui/select_preview/multiple.html.erb +6 -0
- data/registry/test/components/previews/ui/select_preview/sizes.html.erb +5 -0
- data/registry/test/components/previews/ui/select_preview/states.html.erb +6 -0
- data/registry/test/components/previews/ui/select_preview.rb +45 -0
- data/registry/test/components/previews/ui/sheet_preview/bottom.html.erb +12 -0
- data/registry/test/components/previews/ui/sheet_preview/left.html.erb +10 -0
- data/registry/test/components/previews/ui/sheet_preview/playground.html.erb +15 -0
- data/registry/test/components/previews/ui/sheet_preview/right.html.erb +11 -0
- data/registry/test/components/previews/ui/sheet_preview/top.html.erb +8 -0
- data/registry/test/components/previews/ui/sheet_preview.rb +22 -0
- data/registry/test/components/previews/ui/sidebar_preview/_brand.html.erb +14 -0
- data/registry/test/components/previews/ui/sidebar_preview/_shell.html.erb +60 -0
- data/registry/test/components/previews/ui/sidebar_preview/collapsible.html.erb +55 -0
- data/registry/test/components/previews/ui/sidebar_preview/default.html.erb +17 -0
- data/registry/test/components/previews/ui/sidebar_preview/with_dropdown_header.html.erb +33 -0
- data/registry/test/components/previews/ui/sidebar_preview/with_groups.html.erb +22 -0
- data/registry/test/components/previews/ui/sidebar_preview.rb +15 -0
- data/registry/test/components/previews/ui/spinner_preview/in_buttons.html.erb +7 -0
- data/registry/test/components/previews/ui/spinner_preview/sizes.html.erb +7 -0
- data/registry/test/components/previews/ui/spinner_preview.rb +19 -0
- data/registry/test/components/previews/ui/switch_preview/default.html.erb +4 -0
- data/registry/test/components/previews/ui/switch_preview/sizes.html.erb +5 -0
- data/registry/test/components/previews/ui/switch_preview/states.html.erb +7 -0
- data/registry/test/components/previews/ui/switch_preview.rb +26 -0
- data/registry/test/components/previews/ui/table_preview/customers.html.erb +15 -0
- data/registry/test/components/previews/ui/table_preview/empty.html.erb +8 -0
- data/registry/test/components/previews/ui/table_preview/with_caption.html.erb +14 -0
- data/registry/test/components/previews/ui/table_preview.rb +12 -0
- data/registry/test/components/previews/ui/tabs_preview/default.html.erb +39 -0
- data/registry/test/components/previews/ui/tabs_preview/pill_variant.html.erb +18 -0
- data/registry/test/components/previews/ui/tabs_preview/settings_form.html.erb +42 -0
- data/registry/test/components/previews/ui/tabs_preview/with_icons.html.erb +34 -0
- data/registry/test/components/previews/ui/tabs_preview.rb +33 -0
- data/registry/test/components/previews/ui/text_preview/sizes.html.erb +10 -0
- data/registry/test/components/previews/ui/text_preview/tones.html.erb +5 -0
- data/registry/test/components/previews/ui/text_preview/weights.html.erb +6 -0
- data/registry/test/components/previews/ui/text_preview.rb +26 -0
- data/registry/test/components/previews/ui/theme_toggle_preview/in_a_header.html.erb +14 -0
- data/registry/test/components/previews/ui/theme_toggle_preview/palette.html.erb +47 -0
- data/registry/test/components/previews/ui/theme_toggle_preview.rb +15 -0
- data/registry/test/components/previews/ui/tooltip_preview/default.html.erb +8 -0
- data/registry/test/components/previews/ui/tooltip_preview/positions.html.erb +34 -0
- data/registry/test/components/previews/ui/tooltip_preview/with_button.html.erb +8 -0
- data/registry/test/components/previews/ui/tooltip_preview/with_icon.html.erb +10 -0
- data/registry/test/components/previews/ui/tooltip_preview.rb +29 -0
- data/registry/test/components/previews/ui/typography_preview/article.html.erb +11 -0
- data/registry/test/components/previews/ui/typography_preview/code_and_quotes.html.erb +6 -0
- data/registry/test/components/previews/ui/typography_preview/headings.html.erb +6 -0
- data/registry/test/components/previews/ui/typography_preview/text_styles.html.erb +7 -0
- data/registry/test/components/previews/ui/typography_preview.rb +23 -0
- data/registry/test/components/ui/accordion/content_test.rb +64 -0
- data/registry/test/components/ui/accordion/item_test.rb +61 -0
- data/registry/test/components/ui/accordion/trigger_test.rb +80 -0
- data/registry/test/components/ui/accordion_test.rb +96 -0
- data/registry/test/components/ui/alert/description_test.rb +37 -0
- data/registry/test/components/ui/alert/title_test.rb +38 -0
- data/registry/test/components/ui/alert_test.rb +184 -0
- data/registry/test/components/ui/avatar_group_test.rb +79 -0
- data/registry/test/components/ui/avatar_test.rb +129 -0
- data/registry/test/components/ui/badge_test.rb +112 -0
- data/registry/test/components/ui/banner_test.rb +116 -0
- data/registry/test/components/ui/base_test.rb +98 -0
- data/registry/test/components/ui/breadcrumb/ellipsis_test.rb +51 -0
- data/registry/test/components/ui/breadcrumb/item_test.rb +39 -0
- data/registry/test/components/ui/breadcrumb/list_test.rb +34 -0
- data/registry/test/components/ui/breadcrumb/separator_test.rb +36 -0
- data/registry/test/components/ui/breadcrumb_test.rb +276 -0
- data/registry/test/components/ui/button_test.rb +276 -0
- data/registry/test/components/ui/button_to_test.rb +74 -0
- data/registry/test/components/ui/card/content_test.rb +41 -0
- data/registry/test/components/ui/card/description_test.rb +32 -0
- data/registry/test/components/ui/card/footer_test.rb +34 -0
- data/registry/test/components/ui/card/header_test.rb +57 -0
- data/registry/test/components/ui/card/title_test.rb +34 -0
- data/registry/test/components/ui/card_test.rb +67 -0
- data/registry/test/components/ui/checkbox_test.rb +236 -0
- data/registry/test/components/ui/data_grid_test.rb +88 -0
- data/registry/test/components/ui/date_picker_test.rb +168 -0
- data/registry/test/components/ui/dialog/content_test.rb +156 -0
- data/registry/test/components/ui/dialog/trigger_test.rb +74 -0
- data/registry/test/components/ui/dialog_test.rb +54 -0
- data/registry/test/components/ui/divider_test.rb +69 -0
- data/registry/test/components/ui/icon_test.rb +82 -0
- data/registry/test/components/ui/image_test.rb +74 -0
- data/registry/test/components/ui/input_group_test.rb +143 -0
- data/registry/test/components/ui/input_test.rb +251 -0
- data/registry/test/components/ui/label_test.rb +96 -0
- data/registry/test/components/ui/link_test.rb +95 -0
- data/registry/test/components/ui/menu/content_test.rb +233 -0
- data/registry/test/components/ui/menu/group_test.rb +24 -0
- data/registry/test/components/ui/menu/item_test.rb +76 -0
- data/registry/test/components/ui/menu/label_test.rb +43 -0
- data/registry/test/components/ui/menu/separator_test.rb +32 -0
- data/registry/test/components/ui/menu/trigger_test.rb +109 -0
- data/registry/test/components/ui/menu_test.rb +152 -0
- data/registry/test/components/ui/native_select_test.rb +118 -0
- data/registry/test/components/ui/pagination_test.rb +92 -0
- data/registry/test/components/ui/password_input_test.rb +98 -0
- data/registry/test/components/ui/popover/content_test.rb +226 -0
- data/registry/test/components/ui/popover/trigger_test.rb +83 -0
- data/registry/test/components/ui/popover_test.rb +101 -0
- data/registry/test/components/ui/radio_group/item_test.rb +14 -0
- data/registry/test/components/ui/radio_group_test.rb +139 -0
- data/registry/test/components/ui/select_test.rb +181 -0
- data/registry/test/components/ui/sheet/content_test.rb +135 -0
- data/registry/test/components/ui/sheet/trigger_test.rb +23 -0
- data/registry/test/components/ui/sheet_test.rb +43 -0
- data/registry/test/components/ui/sidebar/menu_button_test.rb +43 -0
- data/registry/test/components/ui/sidebar/trigger_test.rb +17 -0
- data/registry/test/components/ui/sidebar/wrapper_test.rb +15 -0
- data/registry/test/components/ui/sidebar_test.rb +43 -0
- data/registry/test/components/ui/spinner_test.rb +63 -0
- data/registry/test/components/ui/switch_test.rb +206 -0
- data/registry/test/components/ui/table_test.rb +106 -0
- data/registry/test/components/ui/tabs/content_test.rb +93 -0
- data/registry/test/components/ui/tabs/list_test.rb +65 -0
- data/registry/test/components/ui/tabs/trigger_test.rb +159 -0
- data/registry/test/components/ui/tabs_test.rb +88 -0
- data/registry/test/components/ui/text_test.rb +84 -0
- data/registry/test/components/ui/theme_toggle_test.rb +88 -0
- data/registry/test/components/ui/tooltip/content_test.rb +135 -0
- data/registry/test/components/ui/tooltip/trigger_test.rb +52 -0
- data/registry/test/components/ui/tooltip_test.rb +97 -0
- data/registry/test/components/ui/typography/code_blockquote_test.rb +57 -0
- data/registry/test/components/ui/typography/heading_test.rb +71 -0
- data/registry/test/components/ui/typography/text_variants_test.rb +71 -0
- metadata +540 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class UI::CheckboxTest < ViewComponent::TestCase
|
|
6
|
+
def setup_fixtures(*); end
|
|
7
|
+
def teardown_fixtures(*); end
|
|
8
|
+
|
|
9
|
+
def test_renders_input_type_checkbox_with_appearance_none
|
|
10
|
+
render_inline(UI::Checkbox.new(name: "test"))
|
|
11
|
+
input = page.find("input[type='checkbox']")
|
|
12
|
+
classes = input[:class].split
|
|
13
|
+
assert_includes classes, "appearance-none"
|
|
14
|
+
assert_includes classes, "peer"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_checked_adds_checked_attribute
|
|
18
|
+
render_inline(UI::Checkbox.new(name: "test", checked: true))
|
|
19
|
+
input = page.find("input[type='checkbox']")
|
|
20
|
+
assert input["checked"].present?, "expected checked attribute"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_value_defaults_to_one
|
|
24
|
+
render_inline(UI::Checkbox.new(name: "test"))
|
|
25
|
+
input = page.find("input[type='checkbox']")
|
|
26
|
+
assert_equal "1", input[:value]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_value_can_be_customized
|
|
30
|
+
render_inline(UI::Checkbox.new(name: "test", value: "yes"))
|
|
31
|
+
input = page.find("input[type='checkbox']")
|
|
32
|
+
assert_equal "yes", input[:value]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_disabled_sets_disabled_attribute
|
|
36
|
+
render_inline(UI::Checkbox.new(name: "test", disabled: true))
|
|
37
|
+
input = page.find("input[disabled]")
|
|
38
|
+
assert input.disabled?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_disabled_wrapper_has_cursor_and_opacity_classes
|
|
42
|
+
render_inline(UI::Checkbox.new(name: "test", disabled: true, label: "Disabled"))
|
|
43
|
+
label_el = page.find("label")
|
|
44
|
+
classes = label_el[:class].split
|
|
45
|
+
assert_includes classes, "cursor-not-allowed"
|
|
46
|
+
assert_includes classes, "opacity-60"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_error_sets_aria_invalid_and_border_destructive
|
|
50
|
+
render_inline(UI::Checkbox.new(name: "test", error: true))
|
|
51
|
+
input = page.find("input[type='checkbox']")
|
|
52
|
+
assert_equal "true", input["aria-invalid"]
|
|
53
|
+
classes = input[:class].split
|
|
54
|
+
assert_includes classes, "border-destructive"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_required_sets_required_attribute
|
|
58
|
+
render_inline(UI::Checkbox.new(name: "test", required: true))
|
|
59
|
+
input = page.find("input[required]")
|
|
60
|
+
assert input["required"].present?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_label_renders_label_element_with_for_id
|
|
64
|
+
render_inline(UI::Checkbox.new(name: "terms", label: "Accept terms"))
|
|
65
|
+
label_el = page.find("label")
|
|
66
|
+
input_el = page.find("input[type='checkbox']")
|
|
67
|
+
assert label_el["for"].present?, "label should have for attribute"
|
|
68
|
+
assert_equal label_el["for"], input_el[:id]
|
|
69
|
+
assert_match(/Accept terms/, label_el.text)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_without_label_no_label_element_renders
|
|
73
|
+
render_inline(UI::Checkbox.new(name: "test"))
|
|
74
|
+
refute_selector "label"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_check_overlay_span_present_with_peer_checked_class
|
|
78
|
+
render_inline(UI::Checkbox.new(name: "test"))
|
|
79
|
+
wrapper = page.find(".relative.inline-flex")
|
|
80
|
+
overlay = wrapper.find("span.pointer-events-none")
|
|
81
|
+
classes = overlay[:class].split
|
|
82
|
+
assert_includes classes, "peer-checked:opacity-100"
|
|
83
|
+
assert_includes classes, "opacity-0"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_check_icon_inside_overlay
|
|
87
|
+
render_inline(UI::Checkbox.new(name: "test"))
|
|
88
|
+
overlay = page.find("span.pointer-events-none")
|
|
89
|
+
icon = overlay.find("i", visible: :all)
|
|
90
|
+
assert_equal "check", icon["data-lucide"]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_user_class_override_via_tailwind_merge
|
|
94
|
+
render_inline(UI::Checkbox.new(name: "test", class: "border-red-500"))
|
|
95
|
+
input = page.find("input[type='checkbox']")
|
|
96
|
+
classes = input[:class].split
|
|
97
|
+
refute_includes classes, "border-input"
|
|
98
|
+
assert_includes classes, "border-red-500"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_name_and_id_passthrough
|
|
102
|
+
render_inline(UI::Checkbox.new(name: "user[agree]", id: "agree-cb"))
|
|
103
|
+
input = page.find("input#agree-cb[name='user[agree]']")
|
|
104
|
+
assert input.present?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def test_data_attributes_passthrough
|
|
108
|
+
render_inline(UI::Checkbox.new(name: "test", data: { test_id: "my-cb" }))
|
|
109
|
+
assert_selector "input[data-test-id='my-cb']"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_size_sm_renders_smaller_box
|
|
113
|
+
render_inline(UI::Checkbox.new(name: "test", size: :sm))
|
|
114
|
+
classes = page.find("input[type='checkbox']")[:class].split
|
|
115
|
+
assert_includes classes, "w-3.5"
|
|
116
|
+
assert_includes classes, "h-3.5"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_size_lg_renders_larger_box
|
|
120
|
+
render_inline(UI::Checkbox.new(name: "test", size: :lg))
|
|
121
|
+
classes = page.find("input[type='checkbox']")[:class].split
|
|
122
|
+
assert_includes classes, "w-5"
|
|
123
|
+
assert_includes classes, "h-5"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_auto_generated_id_when_label_present
|
|
127
|
+
render_inline(UI::Checkbox.new(name: "test", label: "Label"))
|
|
128
|
+
input = page.find("input[type='checkbox']")
|
|
129
|
+
assert input[:id].present?, "id should be auto-generated when label is provided"
|
|
130
|
+
assert input[:id].start_with?("ui-cb-"), "id should start with ui-cb-"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def test_disabled_and_checked_combined
|
|
134
|
+
render_inline(UI::Checkbox.new(name: "test", disabled: true, checked: true))
|
|
135
|
+
input = page.find("input[disabled]")
|
|
136
|
+
assert input["checked"].present?
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def test_label_text_span_uses_correct_classes
|
|
140
|
+
render_inline(UI::Checkbox.new(name: "test", label: "Terms"))
|
|
141
|
+
label_el = page.find("label")
|
|
142
|
+
span = label_el.all("span").find { |s| s.text.strip == "Terms" }
|
|
143
|
+
assert span, "label text span should exist"
|
|
144
|
+
classes = span[:class].split
|
|
145
|
+
assert_includes classes, "text-sm"
|
|
146
|
+
assert_includes classes, "leading-none"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def test_required_label_shows_styled_asterisk_span
|
|
150
|
+
render_inline(UI::Checkbox.new(name: "test", label: "Agree", required: true))
|
|
151
|
+
label_el = page.find("label")
|
|
152
|
+
assert_match(/Agree/, label_el.text)
|
|
153
|
+
asterisk = label_el.find("span.text-destructive")
|
|
154
|
+
assert_match(/\*/, asterisk.text)
|
|
155
|
+
classes = asterisk[:class].split
|
|
156
|
+
assert_includes classes, "text-destructive"
|
|
157
|
+
assert_includes classes, "ml-0.5"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def test_non_required_label_has_no_destructive_span
|
|
161
|
+
render_inline(UI::Checkbox.new(name: "test", label: "Agree", required: false))
|
|
162
|
+
refute_selector "label span.text-destructive"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def test_disabled_box_has_no_redundant_opacity_class
|
|
166
|
+
render_inline(UI::Checkbox.new(name: "test", disabled: true, label: "x"))
|
|
167
|
+
classes = page.find("input[type='checkbox']")[:class].split
|
|
168
|
+
# Wrapper owns the dimming via opacity-60; box should not double-dim.
|
|
169
|
+
refute_includes classes, "disabled:opacity-50"
|
|
170
|
+
refute_includes classes, "disabled:opacity-50!"
|
|
171
|
+
refute_includes classes, "disabled:cursor-not-allowed"
|
|
172
|
+
refute_includes classes, "disabled:cursor-not-allowed!"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Radii scale with the box so the corners stay visually consistent.
|
|
176
|
+
def test_size_sm_radius
|
|
177
|
+
render_inline(UI::Checkbox.new(name: "test", size: :sm))
|
|
178
|
+
assert_includes page.find("input[type='checkbox']")[:class].split, "rounded-[3px]"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def test_size_default_radius
|
|
182
|
+
render_inline(UI::Checkbox.new(name: "test", size: :default))
|
|
183
|
+
assert_includes page.find("input[type='checkbox']")[:class].split, "rounded-[4px]"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def test_size_lg_radius
|
|
187
|
+
render_inline(UI::Checkbox.new(name: "test", size: :lg))
|
|
188
|
+
assert_includes page.find("input[type='checkbox']")[:class].split, "rounded-[5px]"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# --- form-builder binding + automatic labels (Rails convention) ---
|
|
192
|
+
|
|
193
|
+
# Stands in for an app model: the builder needs only a name and attributes.
|
|
194
|
+
class User
|
|
195
|
+
include ActiveModel::Model
|
|
196
|
+
attr_accessor :email, :password, :remember_me
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def form_builder_for(record)
|
|
200
|
+
ActionView::Helpers::FormBuilder.new(:user, record, vc_test_controller.view_context, {})
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def test_form_binding_scopes_the_field_name
|
|
204
|
+
render_inline(UI::Checkbox.new(form: form_builder_for(User.new), field: :remember_me))
|
|
205
|
+
assert_selector "input[type='checkbox'][name='user[remember_me]']", visible: :all
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def test_label_is_derived_from_the_attribute_name
|
|
209
|
+
render_inline(UI::Checkbox.new(form: form_builder_for(User.new), field: :remember_me))
|
|
210
|
+
assert_text "Remember me"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def test_derived_label_is_associated_with_the_input
|
|
214
|
+
render_inline(UI::Checkbox.new(form: form_builder_for(User.new), field: :remember_me))
|
|
215
|
+
assert_equal page.find("label")[:for],
|
|
216
|
+
page.find("input[type='checkbox']", visible: :all)[:id]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Rails' check_box emits the companion hidden field so an unchecked box still
|
|
220
|
+
# submits a value; the plain `name:` path has no such helper.
|
|
221
|
+
def test_form_binding_emits_the_unchecked_companion_field
|
|
222
|
+
render_inline(UI::Checkbox.new(form: form_builder_for(User.new), field: :remember_me))
|
|
223
|
+
hidden = page.find("input[type='hidden'][name='user[remember_me]']", visible: :all)
|
|
224
|
+
assert_equal "0", hidden[:value]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def test_explicit_label_overrides_the_derived_one
|
|
228
|
+
render_inline(UI::Checkbox.new(form: form_builder_for(User.new), field: :remember_me, label: "Keep me signed in"))
|
|
229
|
+
assert_text "Keep me signed in"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def test_label_false_opts_out
|
|
233
|
+
render_inline(UI::Checkbox.new(form: form_builder_for(User.new), field: :remember_me, label: false))
|
|
234
|
+
assert_no_selector "label"
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class UI::DataGridTest < ViewComponent::TestCase
|
|
6
|
+
def setup_fixtures(*); end
|
|
7
|
+
def teardown_fixtures(*); end
|
|
8
|
+
|
|
9
|
+
URL = "/customers.json"
|
|
10
|
+
|
|
11
|
+
def render_grid(**options)
|
|
12
|
+
render_inline(UI::DataGrid.new(url: URL, **options)) do |grid|
|
|
13
|
+
grid.column(:id, "ID", width: 90, type: :number)
|
|
14
|
+
grid.column(:name, "Name")
|
|
15
|
+
grid.column(:owner, "Owner")
|
|
16
|
+
grid.column(:notes, "Notes", sortable: false, filterable: false)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def grid = page.find("div[data-controller='ui--data-grid']")
|
|
21
|
+
def columns = JSON.parse(grid["data-ui--data-grid-columns-value"])
|
|
22
|
+
|
|
23
|
+
def test_declares_the_stimulus_controller
|
|
24
|
+
render_grid
|
|
25
|
+
assert_selector "div[data-controller='ui--data-grid']"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Rows are never embedded: the grid fetches each page from this URL, sending
|
|
29
|
+
# the sort and filter it wants applied.
|
|
30
|
+
def test_points_at_the_url_it_fetches_pages_from
|
|
31
|
+
render_grid
|
|
32
|
+
assert_equal URL, grid["data-ui--data-grid-url-value"]
|
|
33
|
+
assert_nil grid["data-ui--data-grid-rows-value"]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_columns_carry_the_field_header_type_and_width
|
|
37
|
+
render_grid
|
|
38
|
+
assert_equal %w[id name owner notes], columns.map { |c| c["field"] }
|
|
39
|
+
assert_equal [ "ID", "Name", "Owner", "Notes" ], columns.map { |c| c["headerName"] }
|
|
40
|
+
assert_equal 90, columns.first["width"]
|
|
41
|
+
assert_equal "number", columns.first["type"]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Only the declared width is sent, so AG Grid keeps sizing the rest itself.
|
|
45
|
+
def test_omits_width_when_not_given
|
|
46
|
+
render_grid
|
|
47
|
+
refute columns.last.key?("width")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# A column the server has no SQL expression for must not offer the control:
|
|
51
|
+
# a sort arrow that does nothing is worse than no sort arrow.
|
|
52
|
+
def test_columns_declare_whether_the_server_can_sort_or_filter_them
|
|
53
|
+
render_grid
|
|
54
|
+
assert columns.first["sortable"]
|
|
55
|
+
assert columns.first["filterable"]
|
|
56
|
+
refute columns.last["sortable"]
|
|
57
|
+
refute columns.last["filterable"]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_empty_message_reaches_the_grid
|
|
61
|
+
render_grid(empty_message: "No customers yet")
|
|
62
|
+
assert_equal "No customers yet", grid["data-ui--data-grid-empty-message-value"]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_page_size_defaults_to_a_screenful_and_can_be_set
|
|
66
|
+
render_grid
|
|
67
|
+
assert_equal "15", grid["data-ui--data-grid-page-size-value"]
|
|
68
|
+
render_grid(page_size: 50)
|
|
69
|
+
assert_equal "50", grid["data-ui--data-grid-page-size-value"]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The height is reserved before the grid paints, so the page does not jump
|
|
73
|
+
# when its chunk arrives.
|
|
74
|
+
def test_reserves_height_for_a_full_page_of_rows
|
|
75
|
+
render_grid(page_size: 10)
|
|
76
|
+
assert_equal "height: 448px", grid[:style]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_user_class_merges
|
|
80
|
+
render_grid(class: "mt-8")
|
|
81
|
+
assert_includes grid[:class].split, "mt-8"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_html_attrs_pass_through
|
|
85
|
+
render_grid(id: "customers")
|
|
86
|
+
assert_selector "div#customers"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class UI::DatePickerTest < ViewComponent::TestCase
|
|
6
|
+
def setup_fixtures(*); end
|
|
7
|
+
def teardown_fixtures(*); end
|
|
8
|
+
|
|
9
|
+
def render_picker(**options)
|
|
10
|
+
render_inline(UI::DatePicker.new(**{ name: "holiday[date]" }.merge(options)))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def typed = page.find("input[type='text']")
|
|
14
|
+
def hidden = page.find("input[type='hidden']", visible: :all)
|
|
15
|
+
def root = page.find("div[data-controller='ui--date-picker']")
|
|
16
|
+
|
|
17
|
+
# --- the two fields ------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
# The typed field carries no name, so a half-typed date can never reach the
|
|
20
|
+
# server: only the hidden ISO field is submitted.
|
|
21
|
+
def test_only_the_hidden_field_is_submitted
|
|
22
|
+
render_picker
|
|
23
|
+
assert_nil typed[:name]
|
|
24
|
+
assert_equal "holiday[date]", hidden[:name]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_the_typed_field_shows_the_locale_format_and_the_hidden_one_iso
|
|
28
|
+
render_picker(value: Date.new(2012, 5, 2))
|
|
29
|
+
assert_equal "02/05/2012", typed[:value]
|
|
30
|
+
assert_equal "2012-05-02", hidden[:value]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_both_fields_are_empty_without_a_value
|
|
34
|
+
render_picker
|
|
35
|
+
assert_equal "", typed[:value].to_s
|
|
36
|
+
assert_equal "", hidden[:value].to_s
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# --- the format is not hardcoded ----------------------------------------
|
|
40
|
+
#
|
|
41
|
+
# date.formats.typed is the authority: the placeholder, the parse order and
|
|
42
|
+
# the separator are all derived from it, so they cannot drift apart.
|
|
43
|
+
|
|
44
|
+
def test_the_placeholder_is_derived_from_the_locale_format
|
|
45
|
+
render_picker
|
|
46
|
+
assert_equal "DD/MM/YYYY", typed[:placeholder]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_the_parse_order_and_separator_are_derived_from_the_locale_format
|
|
50
|
+
render_picker
|
|
51
|
+
assert_equal "DMY", root["data-ui--date-picker-order-value"]
|
|
52
|
+
assert_equal "/", root["data-ui--date-picker-separator-value"]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# A locale that writes dates the other way round changes all three together
|
|
56
|
+
# and needs no change here.
|
|
57
|
+
def test_a_different_locale_format_changes_the_placeholder_and_the_parser
|
|
58
|
+
with_format("%m-%d-%Y") do
|
|
59
|
+
render_picker(value: Date.new(2012, 5, 2))
|
|
60
|
+
assert_equal "MM-DD-YYYY", typed[:placeholder]
|
|
61
|
+
assert_equal "MDY", root["data-ui--date-picker-order-value"]
|
|
62
|
+
assert_equal "-", root["data-ui--date-picker-separator-value"]
|
|
63
|
+
assert_equal "05-02-2012", typed[:value]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def with_format(format)
|
|
68
|
+
original = I18n.backend.translate(:en, "date.formats.typed")
|
|
69
|
+
I18n.backend.store_translations(:en, date: { formats: { typed: format } })
|
|
70
|
+
yield
|
|
71
|
+
ensure
|
|
72
|
+
I18n.backend.store_translations(:en, date: { formats: { typed: original } })
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# --- form binding --------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
Holiday = Struct.new(:date) do
|
|
78
|
+
def self.human_attribute_name(name, *) = name.to_s.humanize
|
|
79
|
+
def to_model = self
|
|
80
|
+
def model_name = ActiveModel::Name.new(self.class, nil, "Holiday")
|
|
81
|
+
def persisted? = false
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def form_for(record = Holiday.new(Date.new(2026, 8, 15)))
|
|
85
|
+
ActionView::Helpers::FormBuilder.new(:holiday, record, vc_test_controller.view_context, {})
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_form_binding_names_the_hidden_field_and_reads_the_value
|
|
89
|
+
render_inline(UI::DatePicker.new(form: form_for, field: :date))
|
|
90
|
+
assert_equal "holiday[date]", hidden[:name]
|
|
91
|
+
assert_equal "2026-08-15", hidden[:value]
|
|
92
|
+
assert_equal "15/08/2026", typed[:value]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_form_binding_derives_the_label
|
|
96
|
+
render_inline(UI::DatePicker.new(form: form_for, field: :date))
|
|
97
|
+
assert_selector "label", text: "Date"
|
|
98
|
+
assert_equal page.find("label")[:for], typed[:id]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# --- state ---------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
def test_required_marks_the_typed_field
|
|
104
|
+
render_picker(required: true)
|
|
105
|
+
assert_selector "input[type='text'][required]"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_required_marks_the_label_with_an_asterisk
|
|
109
|
+
render_picker(required: true, label: "Date of birth")
|
|
110
|
+
assert_selector "label span.text-destructive", text: "*"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_min_and_max_reach_the_calendar
|
|
114
|
+
render_picker(min: "2026-04-01", max: "2027-03-31")
|
|
115
|
+
calendar = page.find("calendar-date", visible: :all)
|
|
116
|
+
assert_equal "2026-04-01", calendar[:min]
|
|
117
|
+
assert_equal "2027-03-31", calendar[:max]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The calendar chunk is only fetched when the button is pressed, so a form
|
|
121
|
+
# nobody opens a calendar on downloads nothing.
|
|
122
|
+
def test_the_calendar_panel_starts_hidden_and_carries_the_module_url
|
|
123
|
+
render_picker
|
|
124
|
+
assert_selector "div[role='dialog'][hidden]", visible: :all
|
|
125
|
+
assert_match(/cally/, root["data-ui--date-picker-module-url-value"])
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The hint teaches the format on a data-entry form; in a filter toolbar the
|
|
129
|
+
# value is already on screen and three wrapped lines of hint per field push
|
|
130
|
+
# the page heading down.
|
|
131
|
+
def test_the_hint_can_be_turned_off
|
|
132
|
+
render_picker
|
|
133
|
+
assert_selector "p", text: "DD/MM/YYYY"
|
|
134
|
+
assert_equal "#{typed[:id]}-hint", typed["aria-describedby"]
|
|
135
|
+
|
|
136
|
+
render_picker(hint: false)
|
|
137
|
+
assert_no_selector "p"
|
|
138
|
+
assert_nil typed["aria-describedby"]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def test_the_toggle_is_labelled_and_marked_closed
|
|
142
|
+
render_picker
|
|
143
|
+
button = page.find("button[aria-haspopup='dialog']")
|
|
144
|
+
assert_equal "false", button["aria-expanded"]
|
|
145
|
+
assert_equal I18n.t("ui.date_picker.open"), button["aria-label"]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# --- the calendar header ---------------------------------------------------
|
|
149
|
+
|
|
150
|
+
# Month and year sit in Cally's heading slot, between the arrows, rather
|
|
151
|
+
# than under a second, duplicate heading.
|
|
152
|
+
def test_month_and_year_selects_fill_the_calendar_heading
|
|
153
|
+
render_picker
|
|
154
|
+
heading = page.find("calendar-date [slot='heading']", visible: :all)
|
|
155
|
+
assert heading.has_css?("calendar-select-month", visible: :all)
|
|
156
|
+
assert heading.has_css?("calendar-select-year", visible: :all)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Cally wraps the arrows in its own buttons, so ours must not be buttons.
|
|
160
|
+
def test_arrows_are_labelled_icons_not_nested_buttons
|
|
161
|
+
render_picker
|
|
162
|
+
[ [ "previous", "ui.date_picker.previous" ], [ "next", "ui.date_picker.next" ] ].each do |slot, key|
|
|
163
|
+
arrow = page.find("calendar-date [slot='#{slot}']", visible: :all)
|
|
164
|
+
refute_equal "button", arrow.tag_name
|
|
165
|
+
assert_includes arrow.text(:all), I18n.t(key)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class UI::Dialog::ContentTest < ViewComponent::TestCase
|
|
6
|
+
def setup_fixtures(*); end
|
|
7
|
+
def teardown_fixtures(*); end
|
|
8
|
+
|
|
9
|
+
def render_dialog(content_opts = {}, &block)
|
|
10
|
+
render_inline(UI::Dialog.new(id: "profile-dialog")) do |dlg|
|
|
11
|
+
dlg.with_trigger { "Open" }
|
|
12
|
+
dlg.with_content(**content_opts) { |c| block.call(c) }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# ── Native dialog wrapper ────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
def test_renders_native_dialog_element
|
|
19
|
+
render_dialog { |c| c.with_title("Hi") }
|
|
20
|
+
assert_selector "dialog[data-ui--dialog-target='content']", visible: :all
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_dialog_has_data_state_closed_by_default
|
|
24
|
+
render_dialog { |c| c.with_title("Hi") }
|
|
25
|
+
assert_equal "closed", page.find("dialog", visible: :all)["data-state"]
|
|
26
|
+
assert_nil page.find("dialog", visible: :all)[:open]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_dialog_id_matches_root
|
|
30
|
+
render_dialog { |c| c.with_title("Hi") }
|
|
31
|
+
assert_equal "profile-dialog", page.find("dialog", visible: :all)[:id]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_panel_present_inside_dialog
|
|
35
|
+
render_dialog { |c| c.with_title("Hi") }
|
|
36
|
+
assert_selector "[data-ui--dialog-part='panel']", visible: :all
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# ── Title / description ──────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
def test_title_text_arg_renders_h2
|
|
42
|
+
render_dialog { |c| c.with_title("Are you sure?") }
|
|
43
|
+
title = page.find("dialog h2", visible: :all)
|
|
44
|
+
assert_equal "Are you sure?", title.text.strip
|
|
45
|
+
classes = title[:class].split
|
|
46
|
+
assert_includes classes, "text-lg"
|
|
47
|
+
assert_includes classes, "font-semibold"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_title_block_renders_h2_with_block_content
|
|
51
|
+
render_dialog do |c|
|
|
52
|
+
c.with_title { "Block title" }
|
|
53
|
+
end
|
|
54
|
+
title = page.find("dialog h2", visible: :all)
|
|
55
|
+
assert_equal "Block title", title.text.strip
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_title_links_via_aria_labelledby
|
|
59
|
+
render_dialog { |c| c.with_title("Hi") }
|
|
60
|
+
dialog = page.find("dialog", visible: :all)
|
|
61
|
+
title = page.find("dialog h2", visible: :all)
|
|
62
|
+
assert_equal "profile-dialog-title", title[:id]
|
|
63
|
+
assert_equal title[:id], dialog["aria-labelledby"]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_description_text_arg_renders_p
|
|
67
|
+
render_dialog do |c|
|
|
68
|
+
c.with_title("Hi")
|
|
69
|
+
c.with_description("This action cannot be undone.")
|
|
70
|
+
end
|
|
71
|
+
desc = page.find("dialog p", visible: :all)
|
|
72
|
+
assert_equal "This action cannot be undone.", desc.text.strip
|
|
73
|
+
assert_includes desc[:class].split, "text-muted-foreground"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_description_links_via_aria_describedby
|
|
77
|
+
render_dialog do |c|
|
|
78
|
+
c.with_title("Hi")
|
|
79
|
+
c.with_description("Body")
|
|
80
|
+
end
|
|
81
|
+
dialog = page.find("dialog", visible: :all)
|
|
82
|
+
desc = page.find("dialog p", visible: :all)
|
|
83
|
+
assert_equal "profile-dialog-description", desc[:id]
|
|
84
|
+
assert_equal desc[:id], dialog["aria-describedby"]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_no_title_means_no_aria_labelledby
|
|
88
|
+
render_dialog { |c| c.with_description("Body") }
|
|
89
|
+
refute page.find("dialog", visible: :all)["aria-labelledby"]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# ── Body ─────────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
def test_body_content_renders_inside_dialog
|
|
95
|
+
render_dialog do |c|
|
|
96
|
+
c.with_title("Hi")
|
|
97
|
+
"raw body text"
|
|
98
|
+
end
|
|
99
|
+
assert_selector "dialog", text: "raw body text", visible: :all
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# ── Footer ───────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
def test_footer_block_renders_in_footer_element
|
|
105
|
+
render_dialog do |c|
|
|
106
|
+
c.with_title("Hi")
|
|
107
|
+
c.with_footer do
|
|
108
|
+
'<button type="button">Confirm</button>'.html_safe
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
assert_selector "dialog footer", visible: :all
|
|
112
|
+
assert_selector "dialog footer button", text: "Confirm", visible: :all
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_no_footer_means_no_footer_element
|
|
116
|
+
render_dialog { |c| c.with_title("Hi") }
|
|
117
|
+
assert_no_selector "dialog footer", visible: :all
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# ── Close button ─────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
def test_close_button_default_present
|
|
123
|
+
render_dialog { |c| c.with_title("Hi") }
|
|
124
|
+
btn = page.find("dialog button[aria-label='Close']", visible: :all)
|
|
125
|
+
assert_match(/click->ui--dialog#close/, btn["data-action"])
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def test_close_button_can_be_disabled
|
|
129
|
+
render_dialog(show_close: false) { |c| c.with_title("Hi") }
|
|
130
|
+
assert_no_selector "dialog button[aria-label='Close']", visible: :all
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# ── Sizes ────────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
def test_size_default
|
|
136
|
+
render_dialog { |c| c.with_title("Hi") }
|
|
137
|
+
panel = page.find("[data-ui--dialog-part='panel']", visible: :all)
|
|
138
|
+
assert_includes panel[:class].split, "max-w-lg"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def test_other_sizes_apply_max_width_token
|
|
142
|
+
{ sm: "max-w-md", lg: "max-w-2xl", xl: "max-w-4xl" }.each do |size, expected|
|
|
143
|
+
render_dialog(size: size) { |c| c.with_title("Hi") }
|
|
144
|
+
panel = page.find("[data-ui--dialog-part='panel']", visible: :all)
|
|
145
|
+
assert_includes panel[:class].split, expected, "size #{size}"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# ── User class merge on panel ────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
def test_user_class_merges_into_panel
|
|
152
|
+
render_dialog(class: "border-red-500") { |c| c.with_title("Hi") }
|
|
153
|
+
panel = page.find("[data-ui--dialog-part='panel']", visible: :all)
|
|
154
|
+
assert_includes panel[:class].split, "border-red-500"
|
|
155
|
+
end
|
|
156
|
+
end
|