Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
* Flowbite::Link component to render links.
* Flowbite::Card now displays a title via the title argument/slot.
* Improved error message when an unknown style is requested.
* Input-elements now support forms without an object; ie forms built with `form_tag` - not `form_for` or `form_with`.

### Changed

Expand Down
6 changes: 6 additions & 0 deletions app/components/flowbite/input/field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@ def disabled?
!!@disabled
end

# Returns true if the object has errors. Returns false if there is no
# object.
#
# @return [Boolean] true if there are errors, false otherwise.
def errors?
return false unless @object

@object.errors.include?(@attribute.intern)
end

Expand Down
2 changes: 2 additions & 0 deletions app/components/flowbite/input/label.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ def call
end

def errors?
return false unless @object

@object.errors.include?(@attribute.intern)
end

Expand Down
12 changes: 11 additions & 1 deletion app/components/flowbite/input_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ class InputField < ViewComponent::Base
renders_one :label

# Returns the errors for attribute
#
# @return [Array<String>] An array of error messages for the attribute.
def errors
return [] unless @object

@object.errors[@attribute] || []
end

Expand Down Expand Up @@ -185,7 +189,13 @@ def hint?
end

def id_for_hint_element
"#{@form.object_name}_#{@attribute}_hint"
[
@form.object_name,
@attribute,
"hint"
]
.compact_blank
.join("_")
end

# @return [Hash] The keyword arguments for the input component.
Expand Down
154 changes: 154 additions & 0 deletions test/components/flowbite/input_field_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,157 @@ def test_adds_classes_to_the_root_element
assert_selector("div.custom_class.another")
end
end

class Flowbite::InputFieldWithoutObjectTest < Minitest::Test
include ViewComponent::TestHelpers

def setup
@view_context = ActionController::Base.new.view_context
@form = ActionView::Helpers::FormBuilder.new(nil, nil, @view_context, {})
end

def test_renders_a_hint
render_inline(Flowbite::InputField.new(form: @form, attribute: :title, hint: {content: "What's the title?"}))

assert_selector("input[type='text']")
assert_selector("p", text: "What's the title?")
end

def test_adds_aria_attributes_for_hint
render_inline(Flowbite::InputField.new(form: @form, attribute: :title, hint: {content: "What's the title?"}))

assert_selector("input[aria-describedby='title_hint']")
assert_selector("p#title_hint", text: "What's the title?")
end

def test_adds_extra_attributes_to_hint
render_inline(Flowbite::InputField.new(form: @form, attribute: :title, hint: {content: "What's the title?", options: {class: "custom-hint-class"}}))

assert_selector("p#title_hint.custom-hint-class", text: "What's the title?")
end

def test_renders_an_input_element
render_inline(Flowbite::InputField.new(form: @form, attribute: :title))

assert_component_rendered
assert_selector("input[type='text']")
end

def test_renders_a_label
render_inline(Flowbite::InputField.new(form: @form, attribute: :title))

assert_component_rendered
assert_selector("label[for='title']")
end

def test_renders_a_label_with_specified_content
render_inline(
Flowbite::InputField.new(form: @form, attribute: :title, label: {content: "Custom label"})
)

assert_selector("label[for='title']", text: "Custom label")
end

def test_renders_a_label_with_specified_attributes
render_inline(
Flowbite::InputField.new(form: @form, attribute: :title, label: {options: {class: "custom-label"}})
)

assert_selector("label[for='title'].custom-label")
end

def test_replaces_the_label_component_using_with_label_content
render_inline(Flowbite::InputField.new(form: @form, attribute: :title)) do |field|
field
.with_label_content("<label id=\"my-label\">Custom label</label>".html_safe)
end

assert_no_selector("label[for='title'].custom-class")
assert_selector("label#my-label", text: "Custom label")
end

def test_replaces_the_label_component_using_with_label_and_with_content
render_inline(Flowbite::InputField.new(form: @form, attribute: :title)) do |field|
field
.with_label.with_content("Custom label")
end

assert_no_selector("label[for='title'].custom-class")
assert_text("Custom label")
end

def test_replaces_the_label_component_using_with_label
render_inline(Flowbite::InputField.new(form: @form, attribute: :title)) do |field|
field
.with_label { "Block with a full component" }
end

assert_no_selector("label[for='title']")
assert_text("Block with a full component")
end

def test_passes_input_options_to_input_element
render_inline(Flowbite::InputField.new(form: @form, attribute: :title, input: {options: {placeholder: "Enter title"}}))

assert_component_rendered
assert_selector("input[type='text'][placeholder='Enter title']")
end

def test_renders_a_large_select_element
render_inline(Flowbite::InputField::Select.new(form: @form, attribute: :state, collection: ["read", "unread"], size: :lg))

assert_selector("select.text-base.px-3\\.5.py-3")
end

def test_passes_input_options_to_select_element
render_inline(Flowbite::InputField::Select.new(form: @form, attribute: :state, collection: ["read", "unread"], input: {options: {"data-key": "state-select"}}))

assert_selector("select[data-key='state-select']")
end

def test_renders_disabled_input_field
render_inline(Flowbite::InputField.new(form: @form, attribute: :title, disabled: true))

assert_selector("input[disabled]")
end

def test_renders_enabled_input_field_by_default
render_inline(Flowbite::InputField.new(form: @form, attribute: :title))

assert_no_selector("input[disabled]")
end

def test_renders_disabled_select
render_inline(Flowbite::InputField::Select.new(form: @form, attribute: :state, collection: ["read", "unread"], disabled: true))

assert_selector("select[disabled]")
end

def test_renders_select_with_default_label
render_inline(Flowbite::InputField::Select.new(form: @form, attribute: :state, collection: ["read", "unread"]))

assert_selector("label[for='state']")
end

def test_renders_select_with_custom_label_content
render_inline(
Flowbite::InputField::Select.new(form: @form, attribute: :state, collection: ["read", "unread"], label: {content: "Book Status"})
)

assert_selector("label[for='state']", text: "Book Status")
end

def test_renders_select_with_custom_label_attributes
render_inline(
Flowbite::InputField::Select.new(form: @form, attribute: :state, collection: ["read", "unread"], label: {options: {class: "select-label"}})
)

assert_selector("label[for='state'].select-label")
end

def test_adds_classes_to_the_root_element
render_inline(Flowbite::InputField.new(class: "custom_class another", form: @form, attribute: :title))

assert_selector("div.custom_class.another")
end
end