Skip to content

Commit 124bca5

Browse files
authored
Merge pull request #55 from substancelab/objectless_forms
Add support for non-object based forms
2 parents df82c93 + 9ecac9b commit 124bca5

File tree

5 files changed

+174
-1
lines changed

5 files changed

+174
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1010
* Flowbite::Link component to render links.
1111
* Flowbite::Card now displays a title via the title argument/slot.
1212
* Improved error message when an unknown style is requested.
13+
* Input-elements now support forms without an object; ie forms built with `form_tag` - not `form_for` or `form_with`.
1314

1415
### Changed
1516

app/components/flowbite/input/field.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,13 @@ def disabled?
9595
!!@disabled
9696
end
9797

98+
# Returns true if the object has errors. Returns false if there is no
99+
# object.
100+
#
101+
# @return [Boolean] true if there are errors, false otherwise.
98102
def errors?
103+
return false unless @object
104+
99105
@object.errors.include?(@attribute.intern)
100106
end
101107

app/components/flowbite/input/label.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ def call
3838
end
3939

4040
def errors?
41+
return false unless @object
42+
4143
@object.errors.include?(@attribute.intern)
4244
end
4345

app/components/flowbite/input_field.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ class InputField < ViewComponent::Base
6767
renders_one :label
6868

6969
# Returns the errors for attribute
70+
#
71+
# @return [Array<String>] An array of error messages for the attribute.
7072
def errors
73+
return [] unless @object
74+
7175
@object.errors[@attribute] || []
7276
end
7377

@@ -185,7 +189,13 @@ def hint?
185189
end
186190

187191
def id_for_hint_element
188-
"#{@form.object_name}_#{@attribute}_hint"
192+
[
193+
@form.object_name,
194+
@attribute,
195+
"hint"
196+
]
197+
.compact_blank
198+
.join("_")
189199
end
190200

191201
# @return [Hash] The keyword arguments for the input component.

test/components/flowbite/input_field_test.rb

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,157 @@ def test_adds_classes_to_the_root_element
156156
assert_selector("div.custom_class.another")
157157
end
158158
end
159+
160+
class Flowbite::InputFieldWithoutObjectTest < Minitest::Test
161+
include ViewComponent::TestHelpers
162+
163+
def setup
164+
@view_context = ActionController::Base.new.view_context
165+
@form = ActionView::Helpers::FormBuilder.new(nil, nil, @view_context, {})
166+
end
167+
168+
def test_renders_a_hint
169+
render_inline(Flowbite::InputField.new(form: @form, attribute: :title, hint: {content: "What's the title?"}))
170+
171+
assert_selector("input[type='text']")
172+
assert_selector("p", text: "What's the title?")
173+
end
174+
175+
def test_adds_aria_attributes_for_hint
176+
render_inline(Flowbite::InputField.new(form: @form, attribute: :title, hint: {content: "What's the title?"}))
177+
178+
assert_selector("input[aria-describedby='title_hint']")
179+
assert_selector("p#title_hint", text: "What's the title?")
180+
end
181+
182+
def test_adds_extra_attributes_to_hint
183+
render_inline(Flowbite::InputField.new(form: @form, attribute: :title, hint: {content: "What's the title?", options: {class: "custom-hint-class"}}))
184+
185+
assert_selector("p#title_hint.custom-hint-class", text: "What's the title?")
186+
end
187+
188+
def test_renders_an_input_element
189+
render_inline(Flowbite::InputField.new(form: @form, attribute: :title))
190+
191+
assert_component_rendered
192+
assert_selector("input[type='text']")
193+
end
194+
195+
def test_renders_a_label
196+
render_inline(Flowbite::InputField.new(form: @form, attribute: :title))
197+
198+
assert_component_rendered
199+
assert_selector("label[for='title']")
200+
end
201+
202+
def test_renders_a_label_with_specified_content
203+
render_inline(
204+
Flowbite::InputField.new(form: @form, attribute: :title, label: {content: "Custom label"})
205+
)
206+
207+
assert_selector("label[for='title']", text: "Custom label")
208+
end
209+
210+
def test_renders_a_label_with_specified_attributes
211+
render_inline(
212+
Flowbite::InputField.new(form: @form, attribute: :title, label: {options: {class: "custom-label"}})
213+
)
214+
215+
assert_selector("label[for='title'].custom-label")
216+
end
217+
218+
def test_replaces_the_label_component_using_with_label_content
219+
render_inline(Flowbite::InputField.new(form: @form, attribute: :title)) do |field|
220+
field
221+
.with_label_content("<label id=\"my-label\">Custom label</label>".html_safe)
222+
end
223+
224+
assert_no_selector("label[for='title'].custom-class")
225+
assert_selector("label#my-label", text: "Custom label")
226+
end
227+
228+
def test_replaces_the_label_component_using_with_label_and_with_content
229+
render_inline(Flowbite::InputField.new(form: @form, attribute: :title)) do |field|
230+
field
231+
.with_label.with_content("Custom label")
232+
end
233+
234+
assert_no_selector("label[for='title'].custom-class")
235+
assert_text("Custom label")
236+
end
237+
238+
def test_replaces_the_label_component_using_with_label
239+
render_inline(Flowbite::InputField.new(form: @form, attribute: :title)) do |field|
240+
field
241+
.with_label { "Block with a full component" }
242+
end
243+
244+
assert_no_selector("label[for='title']")
245+
assert_text("Block with a full component")
246+
end
247+
248+
def test_passes_input_options_to_input_element
249+
render_inline(Flowbite::InputField.new(form: @form, attribute: :title, input: {options: {placeholder: "Enter title"}}))
250+
251+
assert_component_rendered
252+
assert_selector("input[type='text'][placeholder='Enter title']")
253+
end
254+
255+
def test_renders_a_large_select_element
256+
render_inline(Flowbite::InputField::Select.new(form: @form, attribute: :state, collection: ["read", "unread"], size: :lg))
257+
258+
assert_selector("select.text-base.px-3\\.5.py-3")
259+
end
260+
261+
def test_passes_input_options_to_select_element
262+
render_inline(Flowbite::InputField::Select.new(form: @form, attribute: :state, collection: ["read", "unread"], input: {options: {"data-key": "state-select"}}))
263+
264+
assert_selector("select[data-key='state-select']")
265+
end
266+
267+
def test_renders_disabled_input_field
268+
render_inline(Flowbite::InputField.new(form: @form, attribute: :title, disabled: true))
269+
270+
assert_selector("input[disabled]")
271+
end
272+
273+
def test_renders_enabled_input_field_by_default
274+
render_inline(Flowbite::InputField.new(form: @form, attribute: :title))
275+
276+
assert_no_selector("input[disabled]")
277+
end
278+
279+
def test_renders_disabled_select
280+
render_inline(Flowbite::InputField::Select.new(form: @form, attribute: :state, collection: ["read", "unread"], disabled: true))
281+
282+
assert_selector("select[disabled]")
283+
end
284+
285+
def test_renders_select_with_default_label
286+
render_inline(Flowbite::InputField::Select.new(form: @form, attribute: :state, collection: ["read", "unread"]))
287+
288+
assert_selector("label[for='state']")
289+
end
290+
291+
def test_renders_select_with_custom_label_content
292+
render_inline(
293+
Flowbite::InputField::Select.new(form: @form, attribute: :state, collection: ["read", "unread"], label: {content: "Book Status"})
294+
)
295+
296+
assert_selector("label[for='state']", text: "Book Status")
297+
end
298+
299+
def test_renders_select_with_custom_label_attributes
300+
render_inline(
301+
Flowbite::InputField::Select.new(form: @form, attribute: :state, collection: ["read", "unread"], label: {options: {class: "select-label"}})
302+
)
303+
304+
assert_selector("label[for='state'].select-label")
305+
end
306+
307+
def test_adds_classes_to_the_root_element
308+
render_inline(Flowbite::InputField.new(class: "custom_class another", form: @form, attribute: :title))
309+
310+
assert_selector("div.custom_class.another")
311+
end
312+
end

0 commit comments

Comments
 (0)