diff --git a/.changeset/olive-students-begin.md b/.changeset/olive-students-begin.md new file mode 100644 index 0000000000..f97d234293 --- /dev/null +++ b/.changeset/olive-students-begin.md @@ -0,0 +1,7 @@ +--- +'@primer/view-components': minor +--- + +Allow anonymous forms, mostly useful for tests + + diff --git a/app/helpers/primer/form_helper.rb b/app/helpers/primer/form_helper.rb index 6e7b48a159..fcf7569bcb 100644 --- a/app/helpers/primer/form_helper.rb +++ b/app/helpers/primer/form_helper.rb @@ -19,5 +19,15 @@ def primer_fields_for(record_name, record_object = nil, options = {}, &block) &block ) end + + def inline_form(*args, &block) + Primer::Forms.inline_form(*args, &block) + end + + def render_inline_form(*args, &block) + # rubocop:disable GitHub/RailsViewRenderLiteral + render(inline_form(*args, &block)) + # rubocop:enable GitHub/RailsViewRenderLiteral + end end end diff --git a/lib/primer/forms.rb b/lib/primer/forms.rb new file mode 100644 index 0000000000..ca0b80e2be --- /dev/null +++ b/lib/primer/forms.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Primer + # :nodoc: + module Forms + def self.inline_form(builder, base = nil, &block) + base ||= defined?(ApplicationForm) ? ApplicationForm : Primer::Forms::Base + + klass = Class.new(base) do + form(&block) + end + + klass.new(builder) + end + end +end diff --git a/lib/primer/forms/utils.rb b/lib/primer/forms/utils.rb index 4b16f7d772..595aab72ff 100644 --- a/lib/primer/forms/utils.rb +++ b/lib/primer/forms/utils.rb @@ -15,6 +15,8 @@ module Utils # conventions, so it should work ok. Zeitwerk also has this information but lacks a # public API to map constants to source files. def const_source_location(class_name) + return nil unless class_name + # NOTE: underscore respects namespacing, i.e. will convert Foo::Bar to foo/bar. class_path = "#{class_name.underscore}.rb" diff --git a/test/lib/primer/forms/checkbox_group_input_test.rb b/test/lib/primer/forms/checkbox_group_input_test.rb index 3086e5753f..e0c89d96b7 100644 --- a/test/lib/primer/forms/checkbox_group_input_test.rb +++ b/test/lib/primer/forms/checkbox_group_input_test.rb @@ -5,18 +5,14 @@ class Primer::Forms::CheckboxGroupInputTest < Minitest::Test include Primer::ComponentTestHelpers - class HiddenCheckboxGroupForm < ApplicationForm - form do |check_form| - check_form.check_box_group(label: "Foobar", hidden: true) do |check_group| - check_group.check_box(name: :foo, label: "Foo") - end - end - end - def test_hidden_checkbox_group render_in_view_context do primer_form_with(url: "/foo") do |f| - render(HiddenCheckboxGroupForm.new(f)) + render_inline_form(f) do |check_form| + check_form.check_box_group(label: "Foobar", hidden: true) do |check_group| + check_group.check_box(name: :foo, label: "Foo") + end + end end end @@ -24,36 +20,28 @@ def test_hidden_checkbox_group assert_selector ".FormControl-checkbox-wrap", visible: :hidden end - class DisabledCheckboxGroupForm < ApplicationForm - form do |check_form| - check_form.check_box_group(label: "Foobar", disabled: true) do |check_group| - check_group.check_box(name: :foo, label: "Foo") - end - end - end - def test_disabled_checkbox_group_disables_constituent_checkboxes render_in_view_context do primer_form_with(url: "/foo") do |f| - render(DisabledCheckboxGroupForm.new(f)) + render_inline_form(f) do |check_form| + check_form.check_box_group(label: "Foobar", disabled: true) do |check_group| + check_group.check_box(name: :foo, label: "Foo") + end + end end end assert_selector ".FormControl-checkbox-wrap input[disabled]" end - class DisabledCheckboxInGroupForm < ApplicationForm - form do |check_form| - check_form.check_box_group(label: "Foobar") do |check_group| - check_group.check_box(name: :foo, label: "Foo", disabled: true) - end - end - end - def test_checkbox_can_be_individually_disabled_in_group render_in_view_context do primer_form_with(url: "/foo") do |f| - render(DisabledCheckboxInGroupForm.new(f)) + render_inline_form(f) do |check_form| + check_form.check_box_group(label: "Foobar") do |check_group| + check_group.check_box(name: :foo, label: "Foo", disabled: true) + end + end end end diff --git a/test/lib/primer/forms/checkbox_input_test.rb b/test/lib/primer/forms/checkbox_input_test.rb index 0c51be70ae..6d55cbd5ee 100644 --- a/test/lib/primer/forms/checkbox_input_test.rb +++ b/test/lib/primer/forms/checkbox_input_test.rb @@ -5,17 +5,13 @@ class Primer::Forms::CheckboxInputTest < Minitest::Test include Primer::ComponentTestHelpers - class BadCheckboxesForm < ApplicationForm - form do |check_form| - check_form.check_box(name: :alpha, label: "Alpha", scheme: :array) - end - end - def test_array_checkboxes_require_values error = assert_raises(ArgumentError) do render_in_view_context do primer_form_with(url: "/foo") do |f| - render(BadCheckboxesForm.new(f)) + render_inline_form(f) do |check_form| + check_form.check_box(name: :alpha, label: "Alpha", scheme: :array) + end end end end @@ -32,20 +28,16 @@ class NestedForm < ApplicationForm end end - class HiddenCheckboxForm < ApplicationForm - form do |check_form| - check_form.check_box(name: :foo, label: "Foo", hidden: true) do |foo_check| - foo_check.nested_form do |builder| - NestedForm.new(builder) - end - end - end - end - def test_hidden_checkbox render_in_view_context do primer_form_with(url: "/foo") do |f| - render(HiddenCheckboxForm.new(f)) + render_inline_form(f) do |check_form| + check_form.check_box(name: :foo, label: "Foo", hidden: true) do |foo_check| + foo_check.nested_form do |builder| + NestedForm.new(builder) + end + end + end end end @@ -53,20 +45,16 @@ def test_hidden_checkbox assert_selector "input[name=bar]", visible: :hidden end - class CheckboxWithHiddenNestedForm < ApplicationForm - form do |check_form| - check_form.check_box(name: :foo, label: "Foo") do |foo_check| - foo_check.nested_form(hidden: true) do |builder| - NestedForm.new(builder) - end - end - end - end - def test_nested_form_can_be_hidden_independently render_in_view_context do primer_form_with(url: "/foo") do |f| - render(CheckboxWithHiddenNestedForm.new(f)) + render_inline_form(f) do |check_form| + check_form.check_box(name: :foo, label: "Foo") do |foo_check| + foo_check.nested_form(hidden: true) do |builder| + NestedForm.new(builder) + end + end + end end end diff --git a/test/lib/primer/forms/form_control_test.rb b/test/lib/primer/forms/form_control_test.rb index ce8492fb50..5f4915e23e 100644 --- a/test/lib/primer/forms/form_control_test.rb +++ b/test/lib/primer/forms/form_control_test.rb @@ -6,25 +6,15 @@ class Primer::Forms::FormControlTest < Minitest::Test include Primer::ComponentTestHelpers - class PlainTextFieldForm < ApplicationForm - form do |test_form| - test_form.text_field(name: :ultimate_answer, label: "Ultimate answer") - end - end - - class AutoCheckTextFieldForm < ApplicationForm - form do |test_form| - test_form.text_field(name: :ultimate_answer, label: "Ultimate answer", auto_check_src: "/foo") - end - end - - def test_plain_supports_server_errors + def test_supports_model_errors model = DeepThought.new(41) model.valid? # perform validations render_in_view_context do primer_form_with(url: "/foo", model: model) do |f| - render(PlainTextFieldForm.new(f)) + render_inline_form(f) do |test_form| + test_form.text_field(name: :ultimate_answer, label: "Ultimate answer") + end end end @@ -42,7 +32,9 @@ def test_auto_check_generates_validation_elements render_in_view_context do primer_form_with(url: "/foo", model: model) do |f| - render(AutoCheckTextFieldForm.new(f)) + render_inline_form(f) do |test_form| + test_form.text_field(name: :ultimate_answer, label: "Ultimate answer", auto_check_src: "/foo") + end end end diff --git a/test/lib/primer/forms/multi_input_test.rb b/test/lib/primer/forms/multi_input_test.rb index 6437aa7775..32e0815fe7 100644 --- a/test/lib/primer/forms/multi_input_test.rb +++ b/test/lib/primer/forms/multi_input_test.rb @@ -6,29 +6,16 @@ class Primer::Forms::MultiInputTest < Minitest::Test include Primer::ComponentTestHelpers - class MultipleFieldsVisibleForm < ApplicationForm - form do |test_form| - test_form.multi(name: :foo, label: "Thing") do |multi| - multi.text_field(name: :bar, label: "Text field") - multi.text_field(name: :baz, label: "Text field") - end - end - end - - class FieldsWithDifferentNamesForm < ApplicationForm - form do |test_form| - test_form.multi(name: :foo, label: "Thing") do |multi| - multi.text_field(name: :bar, label: "Text field") - multi.text_field(name: :baz, label: "Text field", hidden: true) - end - end - end - def test_disallows_two_visible_inputs error = assert_raises(ArgumentError) do render_in_view_context do primer_form_with(url: "/foo") do |f| - render(MultipleFieldsVisibleForm.new(f)) + render_inline_form(f) do |test_form| + test_form.multi(name: :foo, label: "Thing") do |multi| + multi.text_field(name: :bar, label: "Text field") + multi.text_field(name: :baz, label: "Text field") + end + end end end end @@ -39,7 +26,12 @@ def test_disallows_two_visible_inputs def test_inputs_have_data_name render_in_view_context do primer_form_with(url: "/foo") do |f| - render(FieldsWithDifferentNamesForm.new(f)) + render_inline_form(f) do |test_form| + test_form.multi(name: :foo, label: "Thing") do |multi| + multi.text_field(name: :bar, label: "Text field") + multi.text_field(name: :baz, label: "Text field", hidden: true) + end + end end end @@ -47,22 +39,18 @@ def test_inputs_have_data_name assert_selector "input[data-name=baz]", visible: :hidden end - class MultiDeepThoughtForm < ApplicationForm - form do |text_area_form| - text_area_form.multi(name: :ultimate_answer, label: "Ultimate answer") do |ultimate_answer_multi| - ultimate_answer_multi.text_field(name: :foo) - ultimate_answer_multi.text_field(name: :bar, hidden: true) - end - end - end - def test_no_error_markup model = DeepThought.new(41) model.valid? # populate validation error messages render_in_view_context do primer_form_with(model: model, url: "/foo") do |f| - render(MultiDeepThoughtForm.new(f)) + render_inline_form(f) do |test_form| + test_form.multi(name: :ultimate_answer, label: "Ultimate answer") do |ultimate_answer_multi| + ultimate_answer_multi.text_field(name: :foo) + ultimate_answer_multi.text_field(name: :bar, hidden: true) + end + end end end diff --git a/test/lib/primer/forms/radio_button_group_input_test.rb b/test/lib/primer/forms/radio_button_group_input_test.rb index ab7940f21b..c5650d9924 100644 --- a/test/lib/primer/forms/radio_button_group_input_test.rb +++ b/test/lib/primer/forms/radio_button_group_input_test.rb @@ -5,18 +5,14 @@ class Primer::Forms::RadioButtonGroupInputTest < Minitest::Test include Primer::ComponentTestHelpers - class HiddenRadioGroupForm < ApplicationForm - form do |radio_form| - radio_form.radio_button_group(name: :foobar, label: "Foobar", hidden: true) do |radio_group| - radio_group.radio_button(name: :foo, value: "Foo", label: "Foo") - end - end - end - def test_hidden_radio_button_group render_in_view_context do primer_form_with(url: "/foo") do |f| - render(HiddenRadioGroupForm.new(f)) + render_inline_form(f) do |radio_form| + radio_form.radio_button_group(name: :foobar, label: "Foobar", hidden: true) do |radio_group| + radio_group.radio_button(name: :foo, value: "Foo", label: "Foo") + end + end end end @@ -24,36 +20,28 @@ def test_hidden_radio_button_group assert_selector ".FormControl-radio-wrap", visible: :hidden end - class DisabledRadioGroupForm < ApplicationForm - form do |radio_form| - radio_form.radio_button_group(name: :foobar, label: "Foobar", disabled: true) do |radio_group| - radio_group.radio_button(name: :foo, value: "Foo", label: "Foo") - end - end - end - def test_disabled_radio_group_disables_constituent_radios render_in_view_context do primer_form_with(url: "/foo") do |f| - render(DisabledRadioGroupForm.new(f)) + render_inline_form(f) do |radio_form| + radio_form.radio_button_group(name: :foobar, label: "Foobar", disabled: true) do |radio_group| + radio_group.radio_button(name: :foo, value: "Foo", label: "Foo") + end + end end end assert_selector ".FormControl-radio-wrap input[disabled]" end - class DisabledRadioInGroupForm < ApplicationForm - form do |radio_form| - radio_form.radio_button_group(name: :foobar, label: "Foobar") do |radio_group| - radio_group.radio_button(name: :foo, value: "Foo", label: "Foo", disabled: true) - end - end - end - def test_radio_can_be_individually_disabled_in_group render_in_view_context do primer_form_with(url: "/foo") do |f| - render(DisabledRadioInGroupForm.new(f)) + render_inline_form(f) do |radio_form| + radio_form.radio_button_group(name: :foobar, label: "Foobar") do |radio_group| + radio_group.radio_button(name: :foo, value: "Foo", label: "Foo", disabled: true) + end + end end end diff --git a/test/lib/primer/forms/radio_button_input_test.rb b/test/lib/primer/forms/radio_button_input_test.rb index 0ff7ae528f..887684c25d 100644 --- a/test/lib/primer/forms/radio_button_input_test.rb +++ b/test/lib/primer/forms/radio_button_input_test.rb @@ -14,22 +14,18 @@ class NestedForm < ApplicationForm end end - class HiddenRadioButtonForm < ApplicationForm - form do |radio_form| - radio_form.radio_button_group(name: :foos, label: "Foos") do |radio_group| - radio_group.radio_button(name: :foo, label: "Foo", value: "foo", hidden: true) do |radio_button| - radio_button.nested_form do |builder| - NestedForm.new(builder) - end - end - end - end - end - def test_hidden_radio_button render_in_view_context do primer_form_with(url: "/foo") do |f| - render(HiddenRadioButtonForm.new(f)) + render_inline_form(f) do |radio_form| + radio_form.radio_button_group(name: :foos, label: "Foos") do |radio_group| + radio_group.radio_button(name: :foo, label: "Foo", value: "foo", hidden: true) do |radio_button| + radio_button.nested_form do |builder| + NestedForm.new(builder) + end + end + end + end end end @@ -38,22 +34,18 @@ def test_hidden_radio_button assert_selector "input[name=bar]", visible: :hidden end - class RadioButtonWithHiddenNestedForm < ApplicationForm - form do |radio_form| - radio_form.radio_button_group(name: :foos, label: "Foos") do |radio_group| - radio_group.radio_button(name: :foo, label: "Foo", value: "foo") do |radio_button| - radio_button.nested_form(hidden: true) do |builder| - NestedForm.new(builder) - end - end - end - end - end - def test_nested_form_can_be_hidden_independently render_in_view_context do primer_form_with(url: "/foo") do |f| - render(RadioButtonWithHiddenNestedForm.new(f)) + render_inline_form(f) do |radio_form| + radio_form.radio_button_group(name: :foos, label: "Foos") do |radio_group| + radio_group.radio_button(name: :foo, label: "Foo", value: "foo") do |radio_button| + radio_button.nested_form(hidden: true) do |builder| + NestedForm.new(builder) + end + end + end + end end end @@ -61,18 +53,14 @@ def test_nested_form_can_be_hidden_independently assert_selector "input[name=bar]", visible: :hidden end - class HiddenRadioButtonGroupForm < ApplicationForm - form do |radio_form| - radio_form.radio_button_group(name: :foos, label: "Foos", hidden: true) do |radio_group| - radio_group.radio_button(name: :foo, label: "Foo", value: "foo") - end - end - end - def test_hidden_radio_button_group render_in_view_context do primer_form_with(url: "/foo") do |f| - render(HiddenRadioButtonGroupForm.new(f)) + render_inline_form(f) do |radio_form| + radio_form.radio_button_group(name: :foos, label: "Foos", hidden: true) do |radio_group| + radio_group.radio_button(name: :foo, label: "Foo", value: "foo") + end + end end end diff --git a/test/lib/primer/forms/select_list_input_test.rb b/test/lib/primer/forms/select_list_input_test.rb index 9217cff035..8df1175eaf 100644 --- a/test/lib/primer/forms/select_list_input_test.rb +++ b/test/lib/primer/forms/select_list_input_test.rb @@ -6,39 +6,31 @@ class Primer::Forms::SelectInputTest < Minitest::Test include Primer::ComponentTestHelpers - class HiddenSelectForm < ApplicationForm - form do |select_form| - select_form.select_list(name: :foo, label: "Foo", hidden: true) do |list| - list.option(label: "Foo", value: "foo") - end - end - end - def test_hidden_select_list render_in_view_context do primer_form_with(url: "/foo") do |f| - render(HiddenSelectForm.new(f)) + render_inline_form(f) do |select_form| + select_form.select_list(name: :foo, label: "Foo", hidden: true) do |list| + list.option(label: "Foo", value: "foo") + end + end end end assert_selector "select#foo", visible: :hidden end - class SelectDeepThoughtForm < ApplicationForm - form do |select_form| - select_form.select_list(name: :ultimate_answer, label: "Ultimate answer") do |list| - list.option(label: "Foo", value: "foo") - end - end - end - def test_no_error_markup model = DeepThought.new(41) model.valid? # populate validation error messages render_in_view_context do primer_form_with(model: model, url: "/foo") do |f| - render(SelectDeepThoughtForm.new(f)) + render_inline_form(f) do |select_form| + select_form.select_list(name: :ultimate_answer, label: "Ultimate answer") do |list| + list.option(label: "Foo", value: "foo") + end + end end end diff --git a/test/lib/primer/forms/text_area_input_test.rb b/test/lib/primer/forms/text_area_input_test.rb index 4b1ac12ad2..4df92d3fdb 100644 --- a/test/lib/primer/forms/text_area_input_test.rb +++ b/test/lib/primer/forms/text_area_input_test.rb @@ -6,35 +6,27 @@ class Primer::Forms::TextAreaInputTest < Minitest::Test include Primer::ComponentTestHelpers - class HiddenTextAreaForm < ApplicationForm - form do |text_area_form| - text_area_form.text_area(name: :foo, label: "Foo", hidden: true) - end - end - def test_hidden_text_area render_in_view_context do primer_form_with(url: "/foo") do |f| - render(HiddenTextAreaForm.new(f)) + render_inline_form(f) do |text_area_form| + text_area_form.text_area(name: :foo, label: "Foo", hidden: true) + end end end assert_selector "textarea#foo", visible: :hidden end - class TextAreaDeepThoughtForm < ApplicationForm - form do |text_area_form| - text_area_form.text_area(name: :ultimate_answer, label: "Ultimate answer") - end - end - def test_no_error_markup model = DeepThought.new(41) model.valid? # populate validation error messages render_in_view_context do primer_form_with(model: model, url: "/foo") do |f| - render(TextAreaDeepThoughtForm.new(f)) + render_inline_form(f) do |text_area_form| + text_area_form.text_area(name: :ultimate_answer, label: "Ultimate answer") + end end end diff --git a/test/lib/primer/forms/text_field_input_test.rb b/test/lib/primer/forms/text_field_input_test.rb index 80cf7690ac..51c624eece 100644 --- a/test/lib/primer/forms/text_field_input_test.rb +++ b/test/lib/primer/forms/text_field_input_test.rb @@ -6,16 +6,12 @@ class Primer::Forms::TextFieldInputTest < Minitest::Test include Primer::ComponentTestHelpers - class HiddenTextFieldForm < ApplicationForm - form do |text_field_form| - text_field_form.text_field(name: :foo, label: "Foo", hidden: true) - end - end - def test_hidden_text_field render_in_view_context do primer_form_with(url: "/foo") do |f| - render(HiddenTextFieldForm.new(f)) + render_inline_form(f) do |text_field_form| + text_field_form.text_field(name: :foo, label: "Foo", hidden: true) + end end end @@ -35,16 +31,12 @@ def test_no_error_markup refute_selector ".field_with_errors", visible: :all end - class LeadingVisualTextFieldForm < ApplicationForm - form do |text_field_form| - text_field_form.text_field(name: :foo, label: "Foo", leading_visual: { icon: :search }) - end - end - def test_leading_visual render_in_view_context do primer_form_with(url: "/foo") do |f| - render(LeadingVisualTextFieldForm.new(f)) + render_inline_form(f) do |text_field_form| + text_field_form.text_field(name: :foo, label: "Foo", leading_visual: { icon: :search }) + end end end