inline_forms_installer 8.1.5 → 8.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ebb695af4c902a20604e51deb4fc8a2eefaf32e625cf86805633ffd65e048d4
4
- data.tar.gz: 1b630b3fc3f578fbde245ec2957c9cedc60c0dc27cf28d506b56a41361fb9d5c
3
+ metadata.gz: 8780337213fbfb45e061f0f5309c9cb73826461aacca4dd4cc1662214b8654f4
4
+ data.tar.gz: c355bf659cc460fa83ca210a712c91b466dc4d7e30c0c8477906b2d867770674
5
5
  SHA512:
6
- metadata.gz: 50c22db56f53d41b37f9aaffb44298478a707fbd8f36d962a280e8d952f1c77a25306f6c9e2ecd10f59b825807ced7f172c6dfa547d613efa3569cee6c3e88ec
7
- data.tar.gz: 1c4b64a00674ca2096c1b40c2f48f7bed3c2bd696f0fdf0cd295c6f7df093b244047040e2b07059c42364dd26e842040218973ea002324aafc6780a388ed7d62
6
+ metadata.gz: 1e663a9f2bfdf0ad9e8bab1a93cb397a12374d7918a7f08d069c10b84ca3e635c95cd702c87e1164fb78173f6992cd99191930c1814ffdddc7d996273303c5c5
7
+ data.tar.gz: 210aef3002d3c58c01f226c5f29e6f0a7e548c8b273cb3eca3f08e524beb407f16b75d4cd3ec46abcac4e4a65b21d05c3d2132e79d347cac5a528b54119ac5a4
@@ -1039,6 +1039,202 @@ if ENV['install_example'] == 'true'
1039
1039
  say "- Running migrations for owner + seed (owners + apartments.owner_id + 3 apts/3 owners)..."
1040
1040
  run "bundle exec rake db:migrate"
1041
1041
 
1042
+ # ---------------------------------------------------------------------
1043
+ # FormElementShowcase: a fourth example resource that exercises every
1044
+ # kept Tier 1 form_element helper on a single object. The kept set
1045
+ # (and the rationale for what was dropped) is documented in
1046
+ # stuff/form-element-showcase-plan.md. The runtime helpers handle
1047
+ # every element generically; this block wires up the model, value
1048
+ # rows, uploaders, join table, and seed data.
1049
+ # ---------------------------------------------------------------------
1050
+ say "- Generating FormElementShowcase (one resource per kept Tier 1 form_element)..."
1051
+ sleep 1
1052
+ run %q{bundle exec rails g inline_forms FormElementShowcase title:string body_plain_area:plain_text_area count:integer_field price:decimal_field meeting_date:date_select meeting_time:time_select birth_month:month_select start_month:month_year_picker is_active:check_box gender:radio_button rating_int:dropdown_with_integers priority:dropdown_with_values priority2:dropdown_with_values stars:dropdown_with_values_with_stars attachment:file_field jingle:audio_field cover:image_field description:rich_text roles:has_and_belongs_to_many _enabled:yes _list_order:title _list_search:title _presentation:'#{title}'}
1053
+
1054
+ say "- Generating Attachment + Jingle uploaders (Cover reuses ImageUploader)..."
1055
+ run "bundle exec rails generate uploader Attachment"
1056
+ run "bundle exec rails generate uploader Jingle"
1057
+
1058
+ # Cover reuses ImageUploader (already generated for Photo). CarrierWave
1059
+ # is happy to mount one uploader class on multiple models; the global
1060
+ # `remove_previously_stored_files_after_update = false` keeps PaperTrail
1061
+ # revertable for both.
1062
+ gsub_file "app/models/form_element_showcase.rb",
1063
+ "mount_uploader :cover, CoverUploader",
1064
+ "mount_uploader :cover, ImageUploader"
1065
+
1066
+ # First field-level validation in the example app. `allow_blank: true`
1067
+ # keeps PaperTrail revert paths (and the seeded second row, which leaves
1068
+ # count nil) valid. The integration test covers the explicit error
1069
+ # path by POSTing `count: "abc"` to /form_element_showcases.
1070
+ inject_into_file "app/models/form_element_showcase.rb",
1071
+ "\n validates :count, numericality: { only_integer: true }, allow_blank: true\n mount_uploader :attachment, AttachmentUploader\n",
1072
+ after: "class FormElementShowcase < ApplicationRecord\n"
1073
+
1074
+ # Value-bearing rows for every form_element that needs a values hash
1075
+ # (or hash + options_disabled). 8.1.5 row shape: [:attr, :form_element]
1076
+ # for bare rows, [:attr, :form_element, values, options_disabled] for
1077
+ # choice rows.
1078
+ showcase_value_rows = {
1079
+ "[ :is_active, :check_box ]" => "[ :is_active, :check_box, { 0 => 'no', 1 => 'yes' } ]",
1080
+ "[ :gender, :radio_button ]" => "[ :gender, :radio_button, { 1 => 'male', 2 => 'female' } ]",
1081
+ "[ :rating_int, :dropdown_with_integers ]" => "[ :rating_int, :dropdown_with_integers, { 1 => 'one', 2 => 'two', 3 => 'three' } ]",
1082
+ "[ :priority, :dropdown_with_values ]" => "[ :priority, :dropdown_with_values, { 1 => 'low', 2 => 'mid', 3 => 'high' } ]",
1083
+ "[ :priority2, :dropdown_with_values ]" => "[ :priority2, :dropdown_with_values, { 1 => 'low', 2 => 'mid', 3 => 'high' }, [ 2 ] ]",
1084
+ "[ :stars, :dropdown_with_values_with_stars ]" => "[ :stars, :dropdown_with_values_with_stars, { 1 => '1stars', 2 => '2stars', 3 => '3stars', 4 => '4stars', 5 => '5stars' } ]",
1085
+ }
1086
+ showcase_value_rows.each do |from, to|
1087
+ gsub_file "app/models/form_element_showcase.rb", from, to
1088
+ end
1089
+
1090
+ # Section headers + the trailing info/info_list block. `roles` is a
1091
+ # has_and_belongs_to_many relation so the generator skips its
1092
+ # attribute_list row; we hand-insert it as `:info_list` (read-only
1093
+ # checklist of role _presentation strings).
1094
+ showcase_header_inserts = {
1095
+ "[ :title, :text_field ]," => "[ :header_basics, :header ], \n [ :title, :text_field ],",
1096
+ "[ :count, :integer_field ]," => "[ :header_numbers, :header ], \n [ :count, :integer_field ],",
1097
+ "[ :meeting_date, :date_select ]," => "[ :header_dates, :header ], \n [ :meeting_date, :date_select ],",
1098
+ "[ :is_active, :check_box, { 0 => 'no'" => "[ :header_choices, :header ], \n [ :is_active, :check_box, { 0 => 'no'",
1099
+ "[ :attachment, :file_field ]," => "[ :header_files, :header ], \n [ :attachment, :file_field ],",
1100
+ "[ :description, :rich_text ]," => "[ :header_rich, :header ], \n [ :description, :rich_text ],",
1101
+ }
1102
+ showcase_header_inserts.each do |from, to|
1103
+ gsub_file "app/models/form_element_showcase.rb", from, to
1104
+ end
1105
+
1106
+ # Insert :roles (info_list), :header_meta, and the timestamps after
1107
+ # the rich_text row. The generator does not emit a row for
1108
+ # `roles:has_and_belongs_to_many` (relation? is true), so we add it
1109
+ # manually here.
1110
+ gsub_file "app/models/form_element_showcase.rb",
1111
+ "[ :description, :rich_text ], \n",
1112
+ "[ :description, :rich_text ], \n [ :roles, :info_list ], \n [ :header_meta, :header ], \n [ :created_at, :info ], \n [ :updated_at, :info ], \n"
1113
+
1114
+ # Locale keys for the showcase attributes. Headers + the timestamps
1115
+ # need explicit labels so `human_attribute_name` does not fall back to
1116
+ # "Header basics" / "Created at". The leading two-space indent puts
1117
+ # the `activerecord:` key under the existing `en:` root.
1118
+ showcase_locale = <<~END_SHOWCASE_LOCALE
1119
+ activerecord:
1120
+ attributes:
1121
+ form_element_showcase:
1122
+ header_basics: Basics
1123
+ header_numbers: Numbers
1124
+ header_dates: Dates and times
1125
+ header_choices: Choices and scales
1126
+ header_files: Files
1127
+ header_rich: Rich text
1128
+ header_meta: Metadata
1129
+ title: Title
1130
+ body_plain_area: Plain text area
1131
+ count: Count
1132
+ price: Price
1133
+ meeting_date: Meeting date
1134
+ meeting_time: Meeting time
1135
+ birth_month: Birth month
1136
+ start_month: Start month
1137
+ is_active: Is active
1138
+ gender: Gender
1139
+ rating_int: Rating
1140
+ priority: Priority
1141
+ priority2: Priority (with disabled option)
1142
+ stars: Stars
1143
+ attachment: Attachment
1144
+ jingle: Jingle
1145
+ cover: Cover
1146
+ description: Description
1147
+ roles: Roles
1148
+ created_at: Created at
1149
+ updated_at: Updated at
1150
+ END_SHOWCASE_LOCALE
1151
+ append_to_file "config/locales/inline_forms_local.en.yml", showcase_locale.lines.map { |l| " #{l}" }.join
1152
+
1153
+ # Star images for dropdown_with_values_with_stars. The runtime helper
1154
+ # calls `image_tag("\#{n}stars.png")`, so we ship 5 tiny PNGs in
1155
+ # lib/installer_templates/example_app_assets and copy them into
1156
+ # app/assets/images at install time.
1157
+ showcase_assets_root = File.join(INSTALLER_ROOT, "lib/installer_templates/example_app_assets")
1158
+ (1..5).each do |n|
1159
+ src = File.join(showcase_assets_root, "#{n}stars.png")
1160
+ copy_file src, File.join("app/assets/images", "#{n}stars.png") if File.exist?(src)
1161
+ end
1162
+
1163
+ # Join table for has_and_belongs_to_many :roles. Mirrors the
1164
+ # roles_users join migration created for the user model above.
1165
+ say "- Creating form_element_showcases_roles join migration..."
1166
+ sleep 1
1167
+ habtm_ts = Time.now.utc.strftime("%Y%m%d%H%M%S")
1168
+ create_file "db/migrate/#{habtm_ts}_create_join_table_form_element_showcases_roles.rb", <<-HABTM_MIGRATION.strip_heredoc
1169
+ class CreateJoinTableFormElementShowcasesRoles < ActiveRecord::Migration[8.1]
1170
+ def self.up
1171
+ create_table :form_element_showcases_roles, id: false, force: true do |t|
1172
+ t.integer :form_element_showcase_id
1173
+ t.integer :role_id
1174
+ end
1175
+ end
1176
+
1177
+ def self.down
1178
+ drop_table :form_element_showcases_roles
1179
+ end
1180
+ end
1181
+ HABTM_MIGRATION
1182
+
1183
+ # Seed two showcase rows: one fully populated (so every show branch
1184
+ # has data), and one with no roles + no uploads (so info_list's empty
1185
+ # branch and the uploader empty branches render). Idempotent.
1186
+ say "- Creating FormElementShowcase seed migration..."
1187
+ sleep 1
1188
+ showcase_seed_ts = Time.now.utc.strftime("%Y%m%d%H%M%S")
1189
+ create_file "db/migrate/#{showcase_seed_ts}_seed_form_element_showcases.rb", <<-SHOWCASE_SEED.strip_heredoc
1190
+ class SeedFormElementShowcases < ActiveRecord::Migration[8.1]
1191
+ def up
1192
+ return unless defined?(FormElementShowcase)
1193
+
1194
+ full = FormElementShowcase.find_or_create_by!(title: "Full demo") do |s|
1195
+ s.body_plain_area = "A short plain-text paragraph."
1196
+ s.count = 7
1197
+ s.price = "12.34"
1198
+ s.meeting_date = Date.new(2026, 6, 1)
1199
+ s.meeting_time = Time.utc(2000, 1, 1, 14, 30)
1200
+ s.birth_month = 7
1201
+ s.start_month = Date.new(2026, 9, 1)
1202
+ s.is_active = true
1203
+ s.gender = 1
1204
+ s.rating_int = 2
1205
+ s.priority = 2
1206
+ s.priority2 = 3
1207
+ s.stars = 4
1208
+ s.description = "<p>A rich-text paragraph for the showcase.</p>"
1209
+ end
1210
+ if defined?(Role) && Role.exists?(1) && full.roles.empty?
1211
+ full.roles << Role.find(1)
1212
+ end
1213
+
1214
+ # "Empty" refers to the role and uploader fields (their empty
1215
+ # branches need to render). The other fields keep valid values
1216
+ # because several show helpers (e.g. dropdown_with_integers) raise
1217
+ # on nil/out-of-range integers.
1218
+ FormElementShowcase.find_or_create_by!(title: "Empty demo") do |s|
1219
+ s.is_active = false
1220
+ s.gender = 1
1221
+ s.rating_int = 1
1222
+ s.priority = 1
1223
+ s.priority2 = 1
1224
+ s.stars = 1
1225
+ end
1226
+ end
1227
+
1228
+ def down
1229
+ return unless defined?(FormElementShowcase)
1230
+ FormElementShowcase.where(title: ["Full demo", "Empty demo"]).destroy_all
1231
+ end
1232
+ end
1233
+ SHOWCASE_SEED
1234
+
1235
+ say "- Running showcase migrations (create table + join + seed)..."
1236
+ run "bundle exec rake db:migrate"
1237
+
1042
1238
  example_views_root = File.join(INSTALLER_ROOT, "lib/installer_templates/example_app_views")
1043
1239
  Dir.glob(File.join(example_views_root, "**", "*")).sort.each do |abs|
1044
1240
  next unless File.file?(abs)
@@ -1,6 +1,6 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module InlineFormsInstaller
3
- VERSION = "8.1.5"
3
+ VERSION = "8.1.6"
4
4
 
5
5
  # Written into generated apps' `.ruby-version` (must match gemspec `required_ruby_version`).
6
6
  TARGET_RUBY_VERSION = "ruby-4.0.4"
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../example_app/example_integration_test_case"
4
+
5
+ # Choice helpers on FormElementShowcase:
6
+ # check_box (is_active, 0|1)
7
+ # radio_button (gender, hash)
8
+ # dropdown_with_integers (rating_int)
9
+ # dropdown_with_values (priority normal + priority2 options_disabled)
10
+ # dropdown_with_values_with_stars (stars)
11
+ # (scale_with_integers / scale_with_values are dropped from the showcase
12
+ # until their runtime show helpers are repaired; see CHANGELOG 8.1.6.)
13
+ class ExampleAppShowcaseChoiceScaleFieldsTest < ExampleAppIntegrationTestCase
14
+ setup do
15
+ @showcase = FormElementShowcase.find_or_create_by!(title: "Choice/scale demo")
16
+ end
17
+
18
+ def field_frame(attr)
19
+ "form_element_showcase_#{@showcase.id}_#{attr}"
20
+ end
21
+
22
+ def field_headers(attr)
23
+ { "Turbo-Frame" => field_frame(attr), "Accept" => "text/html" }
24
+ end
25
+
26
+ def underscored_param_root
27
+ "_form_element_showcase"
28
+ end
29
+
30
+ test "check_box toggles is_active from 0 to 1" do
31
+ @showcase.update!(is_active: false)
32
+ frame = field_frame(:is_active)
33
+ put form_element_showcase_path(
34
+ @showcase,
35
+ attribute: "is_active",
36
+ form_element: "check_box",
37
+ update: frame
38
+ ), params: { is_active: 1 }, headers: field_headers(:is_active)
39
+
40
+ assert_response :success
41
+ assert_equal 1, @showcase.reload.is_active ? 1 : 0
42
+ end
43
+
44
+ test "check_box toggles is_active back to 0 when param missing" do
45
+ @showcase.update!(is_active: true)
46
+ frame = field_frame(:is_active)
47
+ put form_element_showcase_path(
48
+ @showcase,
49
+ attribute: "is_active",
50
+ form_element: "check_box",
51
+ update: frame
52
+ ), params: {}, headers: field_headers(:is_active)
53
+
54
+ assert_response :success
55
+ assert_equal false, !!@showcase.reload.is_active
56
+ end
57
+
58
+ test "radio_button sets gender via attribute param" do
59
+ frame = field_frame(:gender)
60
+ put form_element_showcase_path(
61
+ @showcase,
62
+ attribute: "gender",
63
+ form_element: "radio_button",
64
+ update: frame
65
+ ), params: { gender: 2 }, headers: field_headers(:gender)
66
+
67
+ assert_response :success
68
+ assert_equal 2, @showcase.reload.gender
69
+ end
70
+
71
+ test "dropdown_with_integers sets rating_int" do
72
+ frame = field_frame(:rating_int)
73
+ put form_element_showcase_path(
74
+ @showcase,
75
+ attribute: "rating_int",
76
+ form_element: "dropdown_with_integers",
77
+ update: frame
78
+ ), params: { underscored_param_root => { rating_int: 2 } }, headers: field_headers(:rating_int)
79
+
80
+ assert_response :success
81
+ assert_equal 2, @showcase.reload.rating_int
82
+ end
83
+
84
+ test "dropdown_with_values priority round-trips" do
85
+ frame = field_frame(:priority)
86
+ put form_element_showcase_path(
87
+ @showcase,
88
+ attribute: "priority",
89
+ form_element: "dropdown_with_values",
90
+ update: frame
91
+ ), params: { underscored_param_root => { priority: 3 } }, headers: field_headers(:priority)
92
+
93
+ assert_response :success
94
+ assert_equal 3, @showcase.reload.priority
95
+ end
96
+
97
+ test "dropdown_with_values priority2 disables the third option in the edit form" do
98
+ @showcase.update!(priority2: 1)
99
+ frame = field_frame(:priority2)
100
+ get edit_form_element_showcase_path(
101
+ @showcase,
102
+ attribute: "priority2",
103
+ form_element: "dropdown_with_values",
104
+ update: frame
105
+ ), headers: field_headers(:priority2)
106
+
107
+ assert_response :success
108
+ assert_includes @response.body, %(<turbo-frame id="#{frame}">)
109
+ # options_disabled is [2] for priority2; that option should render with `disabled`.
110
+ assert_match(%r{<option [^>]*disabled[^>]*value="2"|<option [^>]*value="2"[^>]*disabled}, @response.body)
111
+ end
112
+
113
+ test "dropdown_with_values_with_stars sets stars" do
114
+ frame = field_frame(:stars)
115
+ put form_element_showcase_path(
116
+ @showcase,
117
+ attribute: "stars",
118
+ form_element: "dropdown_with_values_with_stars",
119
+ update: frame
120
+ ), params: { underscored_param_root => { stars: 5 } }, headers: field_headers(:stars)
121
+
122
+ assert_response :success
123
+ assert_equal 5, @showcase.reload.stars
124
+ end
125
+
126
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../example_app/example_integration_test_case"
4
+
5
+ # Date/time-shaped Tier 1 helpers on FormElementShowcase:
6
+ # date_select (meeting_date)
7
+ # time_select (meeting_time)
8
+ # month_select (birth_month, 1..12 integer)
9
+ # month_year_picker (start_month, date)
10
+ class ExampleAppShowcaseDateTimeFieldsTest < ExampleAppIntegrationTestCase
11
+ setup do
12
+ @showcase = FormElementShowcase.find_or_create_by!(title: "Date/time demo")
13
+ end
14
+
15
+ test "date_select meeting_date renders datepicker on edit" do
16
+ frame = "form_element_showcase_#{@showcase.id}_meeting_date"
17
+ headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
18
+
19
+ get edit_form_element_showcase_path(
20
+ @showcase,
21
+ attribute: "meeting_date",
22
+ form_element: "date_select",
23
+ update: frame
24
+ ), headers: headers
25
+
26
+ assert_response :success
27
+ assert_includes @response.body, %(<turbo-frame id="#{frame}">)
28
+ assert_includes @response.body, %(class="datepicker")
29
+ assert_includes @response.body, %(name="meeting_date")
30
+ end
31
+
32
+ test "time_select meeting_time renders timepicker on edit" do
33
+ @showcase.update!(meeting_time: Time.utc(2000, 1, 1, 9, 15))
34
+ frame = "form_element_showcase_#{@showcase.id}_meeting_time"
35
+ headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
36
+
37
+ get edit_form_element_showcase_path(
38
+ @showcase,
39
+ attribute: "meeting_time",
40
+ form_element: "time_select",
41
+ update: frame
42
+ ), headers: headers
43
+
44
+ assert_response :success
45
+ assert_includes @response.body, %(<turbo-frame id="#{frame}">)
46
+ assert_includes @response.body, %(class="timepicker")
47
+ assert_includes @response.body, %(name="meeting_time")
48
+ end
49
+
50
+ test "month_select birth_month renders 12 options on edit" do
51
+ @showcase.update!(birth_month: 6)
52
+ frame = "form_element_showcase_#{@showcase.id}_birth_month"
53
+ headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
54
+
55
+ get edit_form_element_showcase_path(
56
+ @showcase,
57
+ attribute: "birth_month",
58
+ form_element: "month_select",
59
+ update: frame
60
+ ), headers: headers
61
+
62
+ assert_response :success
63
+ assert_includes @response.body, %(<turbo-frame id="#{frame}">)
64
+ assert_match(/<select[^>]+name="date\[birth_month\]"/, @response.body)
65
+ end
66
+
67
+ test "month_year_picker start_month renders datepicker class hook" do
68
+ @showcase.update!(start_month: Date.new(2026, 5, 1))
69
+ frame = "form_element_showcase_#{@showcase.id}_start_month"
70
+ headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
71
+
72
+ get edit_form_element_showcase_path(
73
+ @showcase,
74
+ attribute: "start_month",
75
+ form_element: "month_year_picker",
76
+ update: frame
77
+ ), headers: headers
78
+
79
+ assert_response :success
80
+ assert_includes @response.body, %(<turbo-frame id="#{frame}">)
81
+ assert_includes @response.body, %(datepicker-month-year)
82
+ assert_includes @response.body, %(name="start_month")
83
+ end
84
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../example_app/example_integration_test_case"
4
+
5
+ # Numeric Tier 1 helpers on FormElementShowcase:
6
+ # integer_field (count) + numericality validation regression
7
+ # decimal_field (price)
8
+ # (money_field is dropped from the showcase; the runtime helper depends on
9
+ # money-rails' `humanized_money_with_symbol`, which is not in the installer
10
+ # Gemfile. See CHANGELOG 8.1.6 "Dropped" section.)
11
+ class ExampleAppShowcaseNumericFieldsTest < ExampleAppIntegrationTestCase
12
+ setup do
13
+ @showcase = FormElementShowcase.find_or_create_by!(title: "Numeric demo")
14
+ @list_frame = "form_element_showcases_list"
15
+ @list_headers = { "Turbo-Frame" => @list_frame, "Accept" => "text/html" }
16
+ end
17
+
18
+ test "integer_field count round-trips" do
19
+ frame = "form_element_showcase_#{@showcase.id}_count"
20
+ headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
21
+
22
+ put form_element_showcase_path(
23
+ @showcase,
24
+ attribute: "count",
25
+ form_element: "integer_field",
26
+ update: frame
27
+ ), params: { count: "42" }, headers: headers
28
+
29
+ assert_response :success
30
+ assert_includes @response.body, %(<turbo-frame id="#{frame}">)
31
+ assert_includes @response.body, "42"
32
+ assert_equal 42, @showcase.reload.count
33
+ end
34
+
35
+ test "decimal_field price round-trips" do
36
+ frame = "form_element_showcase_#{@showcase.id}_price"
37
+ headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
38
+
39
+ put form_element_showcase_path(
40
+ @showcase,
41
+ attribute: "price",
42
+ form_element: "decimal_field",
43
+ update: frame
44
+ ), params: { price: "99.95" }, headers: headers
45
+
46
+ assert_response :success
47
+ assert_includes @response.body, %(<turbo-frame id="#{frame}">)
48
+ assert_includes @response.body, "99.95"
49
+ assert_equal "99.95", @showcase.reload.price
50
+ end
51
+
52
+ test "integer_field count rejects non-integer via top-level create" do
53
+ # The controller's create iterates every attribute and calls each
54
+ # form_element's update method. Several helpers crash on nil params:
55
+ # - month_year_picker_update -> Date.parse("") raises
56
+ # - dropdown_with_integers_update / dropdown_with_values{_with_stars}_update
57
+ # -> params[:_form_element_showcase][:attr] dereferences nil
58
+ # We supply just enough to let the create walk reach the validation step.
59
+ assert_no_difference "FormElementShowcase.count" do
60
+ post form_element_showcases_path(update: @list_frame),
61
+ params: {
62
+ title: "Bad Count",
63
+ count: "abc",
64
+ start_month: "September 2026",
65
+ _form_element_showcase: {
66
+ rating_int: 1,
67
+ priority: 1,
68
+ priority2: 1,
69
+ stars: 1,
70
+ },
71
+ },
72
+ headers: @list_headers
73
+ end
74
+ assert_response :success
75
+ body = @response.body
76
+ assert_match(/is not a number|count[^<]*is not a number/i, body,
77
+ "expected numericality error to render after invalid count")
78
+ end
79
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../example_app/example_integration_test_case"
4
+
5
+ # Smoke test for the full FormElementShowcase show page. Covers the
6
+ # display-only helpers (`header`, `info`, `info_list`) and the show
7
+ # branches of every uploader helper + rich_text.
8
+ class ExampleAppShowcasePageRenderTest < ExampleAppIntegrationTestCase
9
+ # All non-header attributes on FormElementShowcase that should each
10
+ # be wrapped in a `<turbo-frame id="form_element_showcase_<id>_<attr>">`.
11
+ SHOWCASE_FIELD_ATTRIBUTES = %i[
12
+ title
13
+ body_plain_area
14
+ count
15
+ price
16
+ meeting_date
17
+ meeting_time
18
+ birth_month
19
+ start_month
20
+ is_active
21
+ gender
22
+ rating_int
23
+ priority
24
+ priority2
25
+ stars
26
+ attachment
27
+ jingle
28
+ cover
29
+ description
30
+ roles
31
+ created_at
32
+ updated_at
33
+ ].freeze
34
+
35
+ SHOWCASE_HEADER_ATTRIBUTES = %i[
36
+ header_basics
37
+ header_numbers
38
+ header_dates
39
+ header_choices
40
+ header_files
41
+ header_rich
42
+ header_meta
43
+ ].freeze
44
+
45
+ setup do
46
+ locale = Locale.find_or_create_by!(name: "en") { |l| l.title = "English" }
47
+ @role = Role.find_or_create_by!(name: "superadmin") { |r| r.description = "Super Admin" }
48
+
49
+ @full = FormElementShowcase.find_or_create_by!(title: "Full demo") do |s|
50
+ s.body_plain_area = "Plain text body"
51
+ s.count = 7
52
+ s.price = "12.34"
53
+ s.meeting_date = Date.new(2026, 6, 1)
54
+ s.meeting_time = Time.utc(2000, 1, 1, 14, 30)
55
+ s.birth_month = 7
56
+ s.start_month = Date.new(2026, 9, 1)
57
+ s.is_active = true
58
+ s.gender = 1
59
+ s.rating_int = 2
60
+ s.priority = 2
61
+ s.priority2 = 3
62
+ s.stars = 4
63
+ s.description = "<p>A rich-text body.</p>"
64
+ end
65
+ @full.roles << @role unless @full.roles.where(id: @role.id).exists?
66
+
67
+ @empty = FormElementShowcase.find_or_create_by!(title: "Empty demo") do |s|
68
+ # Keep dropdown/scale integers at valid indices so the show helpers
69
+ # do not crash on nil. The point of "empty demo" is that roles and
70
+ # uploads are blank, not every integer attribute.
71
+ s.gender = 1
72
+ s.rating_int = 1
73
+ s.priority = 1
74
+ s.priority2 = 1
75
+ s.stars = 1
76
+ end
77
+ end
78
+
79
+ test "full showcase show page renders every per-attribute turbo-frame" do
80
+ row_frame = "form_element_showcase_#{@full.id}"
81
+ get form_element_showcase_path(@full, update: row_frame),
82
+ headers: { "Turbo-Frame" => row_frame, "Accept" => "text/html" }
83
+
84
+ assert_response :success
85
+
86
+ SHOWCASE_FIELD_ATTRIBUTES.each do |attr|
87
+ frame = "form_element_showcase_#{@full.id}_#{attr}"
88
+ assert_includes @response.body, %(<turbo-frame id="#{frame}">),
89
+ "expected per-attribute frame for #{attr}"
90
+ end
91
+
92
+ SHOWCASE_HEADER_ATTRIBUTES.each do |attr|
93
+ label = FormElementShowcase.human_attribute_name(attr)
94
+ assert_includes @response.body, label,
95
+ "expected header label for #{attr} (#{label.inspect})"
96
+ end
97
+
98
+ assert_includes @response.body, @role.name,
99
+ "expected info_list to render the role's _presentation"
100
+ end
101
+
102
+ test "empty showcase info_list renders the no-roles placeholder" do
103
+ row_frame = "form_element_showcase_#{@empty.id}"
104
+ get form_element_showcase_path(@empty, update: row_frame),
105
+ headers: { "Turbo-Frame" => row_frame, "Accept" => "text/html" }
106
+
107
+ assert_response :success
108
+ frame = "form_element_showcase_#{@empty.id}_roles"
109
+ body = @response.body
110
+ assert_includes body, %(<turbo-frame id="#{frame}">)
111
+ frame_block = body[body.index(%(<turbo-frame id="#{frame}">"))..-1] rescue nil
112
+ # info_list_show emits a `--` placeholder for empty associations.
113
+ assert_match(/<turbo-frame id="#{frame}">[^<]*<div class='row [^']+'>--<\/div>/m, body,
114
+ "expected info_list empty placeholder `--` inside roles frame")
115
+ end
116
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../example_app/example_integration_test_case"
4
+
5
+ # Round-trip the text-shaped Tier 1 helpers on FormElementShowcase:
6
+ # text_field (title)
7
+ # plain_text_area (body_plain_area)
8
+ class ExampleAppShowcaseTextFieldsTest < ExampleAppIntegrationTestCase
9
+ setup do
10
+ @showcase = FormElementShowcase.find_or_create_by!(title: "TextFields demo")
11
+ end
12
+
13
+ test "text_field title round-trips through the field turbo-frame" do
14
+ frame = "form_element_showcase_#{@showcase.id}_title"
15
+ headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
16
+
17
+ put form_element_showcase_path(
18
+ @showcase,
19
+ attribute: "title",
20
+ form_element: "text_field",
21
+ update: frame
22
+ ), params: { title: "TextFields updated" }, headers: headers
23
+
24
+ assert_response :success
25
+ assert_includes @response.body, %(<turbo-frame id="#{frame}">)
26
+ assert_includes @response.body, "TextFields updated"
27
+ assert_equal "TextFields updated", @showcase.reload.title
28
+ end
29
+
30
+ test "plain_text_area body round-trips through the field turbo-frame" do
31
+ frame = "form_element_showcase_#{@showcase.id}_body_plain_area"
32
+ headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
33
+
34
+ body = "Hello plain text\nMulti-line content"
35
+ put form_element_showcase_path(
36
+ @showcase,
37
+ attribute: "body_plain_area",
38
+ form_element: "plain_text_area",
39
+ update: frame
40
+ ), params: { body_plain_area: body }, headers: headers
41
+
42
+ assert_response :success
43
+ assert_includes @response.body, %(<turbo-frame id="#{frame}">)
44
+ assert_includes @response.body, "Hello plain text"
45
+ assert_equal body, @showcase.reload.body_plain_area
46
+ end
47
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ # Pins the FormElementShowcase model wiring:
6
+ # - plain_text columns are present (no rich_text drift)
7
+ # - every kept Tier 1 attribute is in the attribute list
8
+ # - priority2 keeps its options_disabled tuple slot (8.1.5 row shape)
9
+ # - integer numericality is enforced on :count
10
+ class ExampleAppFormElementShowcaseTest < ActiveSupport::TestCase
11
+ EXPECTED_ATTRIBUTES = %i[
12
+ header_basics
13
+ title
14
+ body_plain_area
15
+ header_numbers
16
+ count
17
+ price
18
+ header_dates
19
+ meeting_date
20
+ meeting_time
21
+ birth_month
22
+ start_month
23
+ header_choices
24
+ is_active
25
+ gender
26
+ rating_int
27
+ priority
28
+ priority2
29
+ stars
30
+ header_files
31
+ attachment
32
+ jingle
33
+ cover
34
+ header_rich
35
+ description
36
+ roles
37
+ header_meta
38
+ created_at
39
+ updated_at
40
+ ].freeze
41
+
42
+ test "plain_text helper configuration validates without drift" do
43
+ assert_nothing_raised do
44
+ InlineForms.validate_plain_text_configuration_for!(FormElementShowcase)
45
+ end
46
+ end
47
+
48
+ test "inline_forms_attribute_list contains every expected attribute" do
49
+ keys = FormElementShowcase.new.inline_forms_attribute_list.map { |row| row.first }
50
+ EXPECTED_ATTRIBUTES.each do |attr|
51
+ assert_includes keys, attr, "expected #{attr} in inline_forms_attribute_list"
52
+ end
53
+ end
54
+
55
+ test "priority2 keeps options_disabled at tuple slot 3 (8.1.5 row shape)" do
56
+ row = FormElementShowcase.new.inline_forms_attribute_list.assoc(:priority2)
57
+ assert_not_nil row, "expected a :priority2 row"
58
+ assert_equal :dropdown_with_values, row[1]
59
+ assert_kind_of Hash, row[2]
60
+ assert_equal [2], row[3],
61
+ "expected options_disabled at index 3 (was the 8.1.5 row-shape shift)"
62
+ end
63
+
64
+ test "count rejects non-integer input with a numericality error" do
65
+ showcase = FormElementShowcase.new(title: "X", count: "abc")
66
+ assert_not showcase.valid?
67
+ assert_includes showcase.errors.attribute_names, :count
68
+ assert showcase.errors[:count].any? { |m| m.match?(/not a number/i) },
69
+ "expected `not a number` error on :count, got #{showcase.errors[:count].inspect}"
70
+ end
71
+
72
+ test "count accepts a valid integer" do
73
+ showcase = FormElementShowcase.new(title: "X", count: 5)
74
+ assert showcase.valid?, "expected showcase to be valid with count=5, got #{showcase.errors.full_messages.inspect}"
75
+ end
76
+
77
+ test "count allows blank" do
78
+ showcase = FormElementShowcase.new(title: "X", count: nil)
79
+ assert showcase.valid?, "expected showcase to be valid with count=nil (allow_blank), got #{showcase.errors.full_messages.inspect}"
80
+ end
81
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inline_forms_installer
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.5
4
+ version: 8.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ace Suares
@@ -89,6 +89,11 @@ files:
89
89
  - lib/installer_templates/dartsass/devise_main.scss
90
90
  - lib/installer_templates/dartsass/inline_forms_dartsass_builds.rb
91
91
  - lib/installer_templates/dartsass/inline_forms_main.scss
92
+ - lib/installer_templates/example_app_assets/1stars.png
93
+ - lib/installer_templates/example_app_assets/2stars.png
94
+ - lib/installer_templates/example_app_assets/3stars.png
95
+ - lib/installer_templates/example_app_assets/4stars.png
96
+ - lib/installer_templates/example_app_assets/5stars.png
92
97
  - lib/installer_templates/example_app_tests/test/example_app/example_integration_test_case.rb
93
98
  - lib/installer_templates/example_app_tests/test/integration/example_app_apartment_field_turbo_test.rb
94
99
  - lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_list_test.rb
@@ -104,10 +109,16 @@ files:
104
109
  - lib/installer_templates/example_app_tests/test/integration/example_app_photo_revert_test.rb
105
110
  - lib/installer_templates/example_app_tests/test/integration/example_app_photos_test.rb
106
111
  - lib/installer_templates/example_app_tests/test/integration/example_app_routing_test.rb
112
+ - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_choice_scale_fields_test.rb
113
+ - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_date_time_fields_test.rb
114
+ - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_numeric_fields_test.rb
115
+ - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_page_render_test.rb
116
+ - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_text_fields_test.rb
107
117
  - lib/installer_templates/example_app_tests/test/integration/example_app_turbo_layout_test.rb
108
118
  - lib/installer_templates/example_app_tests/test/integration/example_app_validation_hints_test.rb
109
119
  - lib/installer_templates/example_app_tests/test/models/example_app_apartment_name_validation_test.rb
110
120
  - lib/installer_templates/example_app_tests/test/models/example_app_apartment_photo_test.rb
121
+ - lib/installer_templates/example_app_tests/test/models/example_app_form_element_showcase_test.rb
111
122
  - lib/installer_templates/example_app_tests/test/models/example_app_paper_trail_changeset_test.rb
112
123
  - lib/installer_templates/example_app_tests/test/models/example_app_plain_text_rich_text_edge_cases_test.rb
113
124
  - lib/installer_templates/example_app_views/apartments/name_list.html.erb