inline_forms 8.1.21 → 8.1.42

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.
Files changed (424) hide show
  1. checksums.yaml +4 -4
  2. data/.forgejo/workflows/ci.yml +63 -0
  3. data/.forgejo/workflows/full-gate.yml +77 -0
  4. data/.gitignore +10 -5
  5. data/.rubocop.yml +31 -0
  6. data/.rubocop_todo.yml +151 -0
  7. data/CHANGELOG.md +305 -0
  8. data/CLAUDE.md +68 -0
  9. data/Gemfile +18 -2
  10. data/README.rdoc +23 -6
  11. data/Rakefile +53 -12
  12. data/app/assets/fonts/opensans-v44-latin-italic.woff2 +0 -0
  13. data/app/assets/fonts/opensans-v44-latin-regular.woff2 +0 -0
  14. data/app/assets/javascripts/inline_forms/inline_forms.js +30 -43
  15. data/app/assets/stylesheets/inline_forms/_foundation_icons.scss +6 -12
  16. data/app/assets/stylesheets/inline_forms/_theme.scss +134 -0
  17. data/app/assets/stylesheets/inline_forms/devise.scss +14 -13
  18. data/app/assets/stylesheets/inline_forms/foundation_and_overrides.scss +4 -0
  19. data/app/assets/stylesheets/inline_forms/inline_forms.scss +59 -68
  20. data/app/assets/stylesheets/inline_forms_fonts.css.erb +39 -0
  21. data/app/controllers/inline_forms_application_controller.rb +8 -4
  22. data/app/controllers/inline_forms_controller.rb +88 -14
  23. data/app/helpers/inline_forms_helper.rb +58 -15
  24. data/app/models/concerns/inline_forms/soft_deletable.rb +11 -12
  25. data/app/validators/curacao_id_number_validator.rb +5 -6
  26. data/app/validators/is_curacao_phone_validator.rb +2 -3
  27. data/app/validators/is_email_address_validator.rb +3 -3
  28. data/app/validators/must_be_a_value_validator.rb +6 -6
  29. data/app/views/inline_forms/_new.html.erb +4 -1
  30. data/app/views/inline_forms/_pending_migration_row.html.erb +15 -0
  31. data/app/views/inline_forms/_show.html.erb +5 -2
  32. data/app/views/inline_forms/field_pending.html.erb +10 -0
  33. data/app/views/layouts/application.html.erb +6 -3
  34. data/app/views/layouts/devise.html.erb +1 -0
  35. data/app/views/layouts/inline_forms.html.erb +7 -4
  36. data/archived/README.md +2 -0
  37. data/archived/form_elements/dns_records/README.md +25 -0
  38. data/archived/form_elements/question_list/README.md +25 -0
  39. data/inline_forms.gemspec +8 -7
  40. data/lib/generators/inline_forms_addto/USAGE +31 -0
  41. data/lib/generators/inline_forms_addto_generator.rb +198 -26
  42. data/lib/generators/inline_forms_attribute_overrides.rb +3 -2
  43. data/lib/generators/templates/application_record.rb +1 -1
  44. data/lib/inline_forms/archived_form_elements.rb +23 -10
  45. data/lib/inline_forms/attribute_list.rb +191 -0
  46. data/lib/inline_forms/form_element_from_callee.rb +1 -0
  47. data/lib/inline_forms/form_element_registry.rb +32 -32
  48. data/lib/inline_forms/form_elements/color_field_helper.rb +44 -0
  49. data/lib/inline_forms/form_elements/date_helper.rb +4 -1
  50. data/lib/inline_forms/form_elements/dropdown_with_other_helper.rb +25 -113
  51. data/lib/inline_forms/form_elements/month_year_picker_helper.rb +13 -2
  52. data/lib/inline_forms/form_elements/multi_image_field_helper.rb +18 -6
  53. data/lib/inline_forms/form_elements/slider_with_values_helper.rb +31 -64
  54. data/lib/inline_forms/form_elements/time_helper.rb +3 -1
  55. data/lib/inline_forms/gem_files.rb +27 -0
  56. data/lib/inline_forms/schema_apply.rb +85 -0
  57. data/lib/inline_forms/schema_intent.rb +76 -0
  58. data/lib/inline_forms/schema_label.rb +65 -0
  59. data/lib/inline_forms/schema_preview.rb +135 -0
  60. data/lib/inline_forms/tabs.rb +175 -0
  61. data/lib/inline_forms/turbo_tabs_builder.rb +20 -13
  62. data/lib/inline_forms/version.rb +2 -1
  63. data/lib/inline_forms.rb +145 -45
  64. data/lib/locales/inline_forms.en.yml +2 -0
  65. data/lib/locales/inline_forms.nl.yml +2 -0
  66. data/test/archived_form_elements_test.rb +34 -4
  67. data/test/attribute_list_test.rb +165 -0
  68. data/test/dummy/app/controllers/application_controller.rb +24 -0
  69. data/test/dummy/app/controllers/gizmos_controller.rb +4 -0
  70. data/test/dummy/app/controllers/machines_controller.rb +4 -0
  71. data/test/dummy/app/controllers/parts_controller.rb +4 -0
  72. data/test/dummy/app/controllers/widgets_controller.rb +4 -0
  73. data/test/dummy/app/helpers/application_helper.rb +13 -0
  74. data/test/dummy/app/models/application_record.rb +26 -0
  75. data/test/dummy/app/models/gizmo.rb +25 -0
  76. data/test/dummy/app/models/kind.rb +8 -0
  77. data/test/dummy/app/models/machine.rb +23 -0
  78. data/test/dummy/app/models/part.rb +16 -0
  79. data/test/dummy/app/models/widget.rb +39 -0
  80. data/test/dummy/app/views/_inline_forms_tabs.html.erb +40 -0
  81. data/test/dummy/config/application.rb +37 -0
  82. data/test/dummy/config/database.yml +3 -0
  83. data/test/dummy/config/environment.rb +5 -0
  84. data/test/dummy/config/initializers/inline_forms.rb +6 -0
  85. data/test/dummy/config/initializers/paper_trail_yaml_safe_load.rb +28 -0
  86. data/test/dummy/config/routes.rb +31 -0
  87. data/test/inline_forms_addto_generator_test.rb +220 -0
  88. data/test/integration/inline_forms_crud_test.rb +141 -0
  89. data/test/integration/legacy_elements_test.rb +42 -0
  90. data/test/integration/native_inputs_test.rb +138 -0
  91. data/test/integration/open_after_create_test.rb +85 -0
  92. data/test/integration/pending_migration_gate_test.rb +75 -0
  93. data/test/integration/schema_batch_pipeline_test.rb +189 -0
  94. data/test/integration/schema_edit_test.rb +141 -0
  95. data/test/integration/schema_preview_test.rb +65 -0
  96. data/test/integration_test_helper.rb +121 -0
  97. data/test/plain_text_configuration_test.rb +3 -3
  98. data/test/schema_apply_test.rb +73 -0
  99. data/test/schema_intent_test.rb +53 -0
  100. data/test/schema_label_test.rb +61 -0
  101. data/test/tabs_test.rb +98 -0
  102. data/test/user_model_config_test.rb +1 -0
  103. data/vendor/assets/javascripts/trix.min.js +7 -0
  104. data/vendor/assets/stylesheets/trix.css +470 -0
  105. metadata +91 -320
  106. data/app/assets/images/jquery-ui/animated-overlay.gif +0 -0
  107. data/app/assets/images/jquery-ui/ui-bg_diagonal-maze_20_6e4f1c_10x10.png +0 -0
  108. data/app/assets/images/jquery-ui/ui-bg_diagonal-maze_40_000000_10x10.png +0 -0
  109. data/app/assets/images/jquery-ui/ui-bg_diagonals-medium_20_d34d17_40x40.png +0 -0
  110. data/app/assets/images/jquery-ui/ui-bg_diagonals-small_0_aaaaaa_40x40.png +0 -0
  111. data/app/assets/images/jquery-ui/ui-bg_diagonals-small_25_c5ddfc_40x40.png +0 -0
  112. data/app/assets/images/jquery-ui/ui-bg_diagonals-small_40_db4865_40x40.png +0 -0
  113. data/app/assets/images/jquery-ui/ui-bg_diagonals-small_50_262626_40x40.png +0 -0
  114. data/app/assets/images/jquery-ui/ui-bg_diagonals-small_50_93c3cd_40x40.png +0 -0
  115. data/app/assets/images/jquery-ui/ui-bg_diagonals-small_50_ff3853_40x40.png +0 -0
  116. data/app/assets/images/jquery-ui/ui-bg_diagonals-small_75_ccd232_40x40.png +0 -0
  117. data/app/assets/images/jquery-ui/ui-bg_diagonals-thick_15_0b3e6f_40x40.png +0 -0
  118. data/app/assets/images/jquery-ui/ui-bg_diagonals-thick_15_444444_40x40.png +0 -0
  119. data/app/assets/images/jquery-ui/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  120. data/app/assets/images/jquery-ui/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  121. data/app/assets/images/jquery-ui/ui-bg_diagonals-thick_20_e69700_40x40.png +0 -0
  122. data/app/assets/images/jquery-ui/ui-bg_diagonals-thick_22_1484e6_40x40.png +0 -0
  123. data/app/assets/images/jquery-ui/ui-bg_diagonals-thick_26_2293f7_40x40.png +0 -0
  124. data/app/assets/images/jquery-ui/ui-bg_diagonals-thick_75_f3d8d8_40x40.png +0 -0
  125. data/app/assets/images/jquery-ui/ui-bg_diagonals-thick_8_333333_40x40.png +0 -0
  126. data/app/assets/images/jquery-ui/ui-bg_diagonals-thick_90_eeeeee_40x40.png +0 -0
  127. data/app/assets/images/jquery-ui/ui-bg_diagonals-thick_95_ffdc2e_40x40.png +0 -0
  128. data/app/assets/images/jquery-ui/ui-bg_diamond_10_4f4221_10x8.png +0 -0
  129. data/app/assets/images/jquery-ui/ui-bg_diamond_20_372806_10x8.png +0 -0
  130. data/app/assets/images/jquery-ui/ui-bg_diamond_25_675423_10x8.png +0 -0
  131. data/app/assets/images/jquery-ui/ui-bg_diamond_25_d5ac5d_10x8.png +0 -0
  132. data/app/assets/images/jquery-ui/ui-bg_diamond_8_261803_10x8.png +0 -0
  133. data/app/assets/images/jquery-ui/ui-bg_diamond_8_443113_10x8.png +0 -0
  134. data/app/assets/images/jquery-ui/ui-bg_dots-medium_30_0b58a2_4x4.png +0 -0
  135. data/app/assets/images/jquery-ui/ui-bg_dots-medium_80_ffff38_4x4.png +0 -0
  136. data/app/assets/images/jquery-ui/ui-bg_dots-small_20_333333_2x2.png +0 -0
  137. data/app/assets/images/jquery-ui/ui-bg_dots-small_30_a32d00_2x2.png +0 -0
  138. data/app/assets/images/jquery-ui/ui-bg_dots-small_35_35414f_2x2.png +0 -0
  139. data/app/assets/images/jquery-ui/ui-bg_dots-small_40_00498f_2x2.png +0 -0
  140. data/app/assets/images/jquery-ui/ui-bg_dots-small_65_a6a6a6_2x2.png +0 -0
  141. data/app/assets/images/jquery-ui/ui-bg_fine-grain_10_eceadf_60x60.png +0 -0
  142. data/app/assets/images/jquery-ui/ui-bg_fine-grain_10_f8f7f6_60x60.png +0 -0
  143. data/app/assets/images/jquery-ui/ui-bg_fine-grain_15_eceadf_60x60.png +0 -0
  144. data/app/assets/images/jquery-ui/ui-bg_fine-grain_15_f7f3de_60x60.png +0 -0
  145. data/app/assets/images/jquery-ui/ui-bg_fine-grain_15_ffffff_60x60.png +0 -0
  146. data/app/assets/images/jquery-ui/ui-bg_fine-grain_65_654b24_60x60.png +0 -0
  147. data/app/assets/images/jquery-ui/ui-bg_fine-grain_68_b83400_60x60.png +0 -0
  148. data/app/assets/images/jquery-ui/ui-bg_flat_0_303030_40x100.png +0 -0
  149. data/app/assets/images/jquery-ui/ui-bg_flat_0_333333_40x100.png +0 -0
  150. data/app/assets/images/jquery-ui/ui-bg_flat_0_4c4c4c_40x100.png +0 -0
  151. data/app/assets/images/jquery-ui/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  152. data/app/assets/images/jquery-ui/ui-bg_flat_0_e69700_40x100.png +0 -0
  153. data/app/assets/images/jquery-ui/ui-bg_flat_0_e6b900_40x100.png +0 -0
  154. data/app/assets/images/jquery-ui/ui-bg_flat_0_eeeeee_40x100.png +0 -0
  155. data/app/assets/images/jquery-ui/ui-bg_flat_10_000000_40x100.png +0 -0
  156. data/app/assets/images/jquery-ui/ui-bg_flat_15_cd0a0a_40x100.png +0 -0
  157. data/app/assets/images/jquery-ui/ui-bg_flat_30_cccccc_40x100.png +0 -0
  158. data/app/assets/images/jquery-ui/ui-bg_flat_40_292929_40x100.png +0 -0
  159. data/app/assets/images/jquery-ui/ui-bg_flat_50_5c5c5c_40x100.png +0 -0
  160. data/app/assets/images/jquery-ui/ui-bg_flat_55_994d53_40x100.png +0 -0
  161. data/app/assets/images/jquery-ui/ui-bg_flat_55_999999_40x100.png +0 -0
  162. data/app/assets/images/jquery-ui/ui-bg_flat_55_c0402a_40x100.png +0 -0
  163. data/app/assets/images/jquery-ui/ui-bg_flat_55_eeeeee_40x100.png +0 -0
  164. data/app/assets/images/jquery-ui/ui-bg_flat_55_fafafa_40x100.png +0 -0
  165. data/app/assets/images/jquery-ui/ui-bg_flat_55_fbec88_40x100.png +0 -0
  166. data/app/assets/images/jquery-ui/ui-bg_flat_55_ffffff_40x100.png +0 -0
  167. data/app/assets/images/jquery-ui/ui-bg_flat_65_ffffff_40x100.png +0 -0
  168. data/app/assets/images/jquery-ui/ui-bg_flat_75_aaaaaa_40x100.png +0 -0
  169. data/app/assets/images/jquery-ui/ui-bg_flat_75_ba9217_40x100.png +0 -0
  170. data/app/assets/images/jquery-ui/ui-bg_flat_75_ddd4b0_40x100.png +0 -0
  171. data/app/assets/images/jquery-ui/ui-bg_flat_75_ffffff_40x100.png +0 -0
  172. data/app/assets/images/jquery-ui/ui-bg_glass_100_e4f1fb_1x400.png +0 -0
  173. data/app/assets/images/jquery-ui/ui-bg_glass_100_f5f0e5_1x400.png +0 -0
  174. data/app/assets/images/jquery-ui/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  175. data/app/assets/images/jquery-ui/ui-bg_glass_100_f8f8f8_1x400.png +0 -0
  176. data/app/assets/images/jquery-ui/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  177. data/app/assets/images/jquery-ui/ui-bg_glass_15_5f391b_1x400.png +0 -0
  178. data/app/assets/images/jquery-ui/ui-bg_glass_20_555555_1x400.png +0 -0
  179. data/app/assets/images/jquery-ui/ui-bg_glass_25_cb842e_1x400.png +0 -0
  180. data/app/assets/images/jquery-ui/ui-bg_glass_35_dddddd_1x400.png +0 -0
  181. data/app/assets/images/jquery-ui/ui-bg_glass_40_0078a3_1x400.png +0 -0
  182. data/app/assets/images/jquery-ui/ui-bg_glass_40_0a0a0a_1x400.png +0 -0
  183. data/app/assets/images/jquery-ui/ui-bg_glass_40_111111_1x400.png +0 -0
  184. data/app/assets/images/jquery-ui/ui-bg_glass_40_ffc73d_1x400.png +0 -0
  185. data/app/assets/images/jquery-ui/ui-bg_glass_45_0078ae_1x400.png +0 -0
  186. data/app/assets/images/jquery-ui/ui-bg_glass_50_3baae3_1x400.png +0 -0
  187. data/app/assets/images/jquery-ui/ui-bg_glass_55_1c1c1c_1x400.png +0 -0
  188. data/app/assets/images/jquery-ui/ui-bg_glass_55_f1fbe5_1x400.png +0 -0
  189. data/app/assets/images/jquery-ui/ui-bg_glass_55_f8da4e_1x400.png +0 -0
  190. data/app/assets/images/jquery-ui/ui-bg_glass_55_fbf5d0_1x400.png +0 -0
  191. data/app/assets/images/jquery-ui/ui-bg_glass_55_fbf8ee_1x400.png +0 -0
  192. data/app/assets/images/jquery-ui/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  193. data/app/assets/images/jquery-ui/ui-bg_glass_55_fcf0ba_1x400.png +0 -0
  194. data/app/assets/images/jquery-ui/ui-bg_glass_60_000000_1x400.png +0 -0
  195. data/app/assets/images/jquery-ui/ui-bg_glass_60_eeeeee_1x400.png +0 -0
  196. data/app/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png +0 -0
  197. data/app/assets/images/jquery-ui/ui-bg_glass_70_ede4d4_1x400.png +0 -0
  198. data/app/assets/images/jquery-ui/ui-bg_glass_75_79c9ec_1x400.png +0 -0
  199. data/app/assets/images/jquery-ui/ui-bg_glass_75_d0e5f5_1x400.png +0 -0
  200. data/app/assets/images/jquery-ui/ui-bg_glass_75_dadada_1x400.png +0 -0
  201. data/app/assets/images/jquery-ui/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  202. data/app/assets/images/jquery-ui/ui-bg_glass_80_d7ebf9_1x400.png +0 -0
  203. data/app/assets/images/jquery-ui/ui-bg_glass_85_dfeffc_1x400.png +0 -0
  204. data/app/assets/images/jquery-ui/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  205. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_100_ece8da_500x100.png +0 -0
  206. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_16_121212_500x100.png +0 -0
  207. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_20_111111_500x100.png +0 -0
  208. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_20_1c160d_500x100.png +0 -0
  209. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_25_333333_500x100.png +0 -0
  210. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_25_453326_500x100.png +0 -0
  211. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_30_3d3644_500x100.png +0 -0
  212. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_30_44372c_500x100.png +0 -0
  213. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  214. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_45_817865_500x100.png +0 -0
  215. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_45_e14f1c_500x100.png +0 -0
  216. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_50_6eac2c_500x100.png +0 -0
  217. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_55_000000_500x100.png +0 -0
  218. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_55_5c9ccc_500x100.png +0 -0
  219. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_60_fece2f_500x100.png +0 -0
  220. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_70_ffdd57_500x100.png +0 -0
  221. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_75_2191c0_500x100.png +0 -0
  222. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_85_9fda58_500x100.png +0 -0
  223. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_90_fff9e5_500x100.png +0 -0
  224. data/app/assets/images/jquery-ui/ui-bg_gloss-wave_95_f6ecd5_500x100.png +0 -0
  225. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_100_eeeeee_1x100.png +0 -0
  226. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_100_f2f5f7_1x100.png +0 -0
  227. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_100_f4f0ec_1x100.png +0 -0
  228. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_100_f5f3e5_1x100.png +0 -0
  229. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_100_f6f6f6_1x100.png +0 -0
  230. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_100_f9f9f9_1x100.png +0 -0
  231. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_100_fafaf4_1x100.png +0 -0
  232. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_15_459e00_1x100.png +0 -0
  233. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_15_888888_1x100.png +0 -0
  234. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_20_0972a5_1x100.png +0 -0
  235. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_30_285c00_1x100.png +0 -0
  236. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_40_aaaaaa_1x100.png +0 -0
  237. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_55_555555_1x100.png +0 -0
  238. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_65_fee4bd_1x100.png +0 -0
  239. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_70_000000_1x100.png +0 -0
  240. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_75_f5f5b5_1x100.png +0 -0
  241. data/app/assets/images/jquery-ui/ui-bg_highlight-hard_95_cccccc_1x100.png +0 -0
  242. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_100_dcd9de_1x100.png +0 -0
  243. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_100_deedf7_1x100.png +0 -0
  244. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_100_eae6ea_1x100.png +0 -0
  245. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  246. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_100_f6f6f6_1x100.png +0 -0
  247. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_100_f9f9f9_1x100.png +0 -0
  248. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_100_feeebd_1x100.png +0 -0
  249. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_15_cc0000_1x100.png +0 -0
  250. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_20_201913_1x100.png +0 -0
  251. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_20_619226_1x100.png +0 -0
  252. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_25_0073ea_1x100.png +0 -0
  253. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_25_30273a_1x100.png +0 -0
  254. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_25_67b021_1x100.png +0 -0
  255. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_25_ffef8f_1x100.png +0 -0
  256. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_33_003147_1x100.png +0 -0
  257. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_33_3a8104_1x100.png +0 -0
  258. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_35_222222_1x100.png +0 -0
  259. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_35_adadad_1x100.png +0 -0
  260. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_44_444444_1x100.png +0 -0
  261. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_45_5f5964_1x100.png +0 -0
  262. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_50_4eb305_1x100.png +0 -0
  263. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_50_aaaaaa_1x100.png +0 -0
  264. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_50_dddddd_1x100.png +0 -0
  265. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_60_4ca20b_1x100.png +0 -0
  266. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_60_dddddd_1x100.png +0 -0
  267. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  268. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  269. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_80_eeeeee_1x100.png +0 -0
  270. data/app/assets/images/jquery-ui/ui-bg_highlight-soft_95_ffedad_1x100.png +0 -0
  271. data/app/assets/images/jquery-ui/ui-bg_inset-hard_100_eeeeee_1x100.png +0 -0
  272. data/app/assets/images/jquery-ui/ui-bg_inset-hard_100_f5f8f9_1x100.png +0 -0
  273. data/app/assets/images/jquery-ui/ui-bg_inset-hard_100_fcfdfd_1x100.png +0 -0
  274. data/app/assets/images/jquery-ui/ui-bg_inset-hard_45_cd0a0a_1x100.png +0 -0
  275. data/app/assets/images/jquery-ui/ui-bg_inset-hard_55_ffeb80_1x100.png +0 -0
  276. data/app/assets/images/jquery-ui/ui-bg_inset-hard_75_999999_1x100.png +0 -0
  277. data/app/assets/images/jquery-ui/ui-bg_inset-soft_100_f4f0ec_1x100.png +0 -0
  278. data/app/assets/images/jquery-ui/ui-bg_inset-soft_10_201913_1x100.png +0 -0
  279. data/app/assets/images/jquery-ui/ui-bg_inset-soft_10_285c00_1x100.png +0 -0
  280. data/app/assets/images/jquery-ui/ui-bg_inset-soft_15_121212_1x100.png +0 -0
  281. data/app/assets/images/jquery-ui/ui-bg_inset-soft_15_2b2922_1x100.png +0 -0
  282. data/app/assets/images/jquery-ui/ui-bg_inset-soft_25_000000_1x100.png +0 -0
  283. data/app/assets/images/jquery-ui/ui-bg_inset-soft_30_f58400_1x100.png +0 -0
  284. data/app/assets/images/jquery-ui/ui-bg_inset-soft_30_ffffff_1x100.png +0 -0
  285. data/app/assets/images/jquery-ui/ui-bg_inset-soft_50_c9c9c9_1x100.png +0 -0
  286. data/app/assets/images/jquery-ui/ui-bg_loop_25_000000_21x21.png +0 -0
  287. data/app/assets/images/jquery-ui/ui-bg_white-lines_85_f7f7ba_40x100.png +0 -0
  288. data/app/assets/images/jquery-ui/ui-icons_000000_256x240.png +0 -0
  289. data/app/assets/images/jquery-ui/ui-icons_004276_256x240.png +0 -0
  290. data/app/assets/images/jquery-ui/ui-icons_00498f_256x240.png +0 -0
  291. data/app/assets/images/jquery-ui/ui-icons_0073ea_256x240.png +0 -0
  292. data/app/assets/images/jquery-ui/ui-icons_0078ae_256x240.png +0 -0
  293. data/app/assets/images/jquery-ui/ui-icons_056b93_256x240.png +0 -0
  294. data/app/assets/images/jquery-ui/ui-icons_070603_256x240.png +0 -0
  295. data/app/assets/images/jquery-ui/ui-icons_0a82eb_256x240.png +0 -0
  296. data/app/assets/images/jquery-ui/ui-icons_0b54d5_256x240.png +0 -0
  297. data/app/assets/images/jquery-ui/ui-icons_1f1f1f_256x240.png +0 -0
  298. data/app/assets/images/jquery-ui/ui-icons_217bc0_256x240.png +0 -0
  299. data/app/assets/images/jquery-ui/ui-icons_222222_256x240.png +0 -0
  300. data/app/assets/images/jquery-ui/ui-icons_228ef1_256x240.png +0 -0
  301. data/app/assets/images/jquery-ui/ui-icons_2694e8_256x240.png +0 -0
  302. data/app/assets/images/jquery-ui/ui-icons_2e83ff_256x240.png +0 -0
  303. data/app/assets/images/jquery-ui/ui-icons_3383bb_256x240.png +0 -0
  304. data/app/assets/images/jquery-ui/ui-icons_3572ac_256x240.png +0 -0
  305. data/app/assets/images/jquery-ui/ui-icons_3d3d3d_256x240.png +0 -0
  306. data/app/assets/images/jquery-ui/ui-icons_3d80b3_256x240.png +0 -0
  307. data/app/assets/images/jquery-ui/ui-icons_454545_256x240.png +0 -0
  308. data/app/assets/images/jquery-ui/ui-icons_469bdd_256x240.png +0 -0
  309. data/app/assets/images/jquery-ui/ui-icons_4b8e0b_256x240.png +0 -0
  310. data/app/assets/images/jquery-ui/ui-icons_4ca300_256x240.png +0 -0
  311. data/app/assets/images/jquery-ui/ui-icons_4eb305_256x240.png +0 -0
  312. data/app/assets/images/jquery-ui/ui-icons_5fa5e3_256x240.png +0 -0
  313. data/app/assets/images/jquery-ui/ui-icons_666666_256x240.png +0 -0
  314. data/app/assets/images/jquery-ui/ui-icons_6da8d5_256x240.png +0 -0
  315. data/app/assets/images/jquery-ui/ui-icons_70b2e1_256x240.png +0 -0
  316. data/app/assets/images/jquery-ui/ui-icons_72a7cf_256x240.png +0 -0
  317. data/app/assets/images/jquery-ui/ui-icons_72b42d_256x240.png +0 -0
  318. data/app/assets/images/jquery-ui/ui-icons_734d99_256x240.png +0 -0
  319. data/app/assets/images/jquery-ui/ui-icons_808080_256x240.png +0 -0
  320. data/app/assets/images/jquery-ui/ui-icons_847e71_256x240.png +0 -0
  321. data/app/assets/images/jquery-ui/ui-icons_888888_256x240.png +0 -0
  322. data/app/assets/images/jquery-ui/ui-icons_88a206_256x240.png +0 -0
  323. data/app/assets/images/jquery-ui/ui-icons_8DC262_256x240.png +0 -0
  324. data/app/assets/images/jquery-ui/ui-icons_8c291d_256x240.png +0 -0
  325. data/app/assets/images/jquery-ui/ui-icons_8d78a5_256x240.png +0 -0
  326. data/app/assets/images/jquery-ui/ui-icons_98d2fb_256x240.png +0 -0
  327. data/app/assets/images/jquery-ui/ui-icons_999999_256x240.png +0 -0
  328. data/app/assets/images/jquery-ui/ui-icons_9bcc60_256x240.png +0 -0
  329. data/app/assets/images/jquery-ui/ui-icons_9ccdfc_256x240.png +0 -0
  330. data/app/assets/images/jquery-ui/ui-icons_9fda58_256x240.png +0 -0
  331. data/app/assets/images/jquery-ui/ui-icons_a83300_256x240.png +0 -0
  332. data/app/assets/images/jquery-ui/ui-icons_a8a3ae_256x240.png +0 -0
  333. data/app/assets/images/jquery-ui/ui-icons_aaaaaa_256x240.png +0 -0
  334. data/app/assets/images/jquery-ui/ui-icons_add978_256x240.png +0 -0
  335. data/app/assets/images/jquery-ui/ui-icons_b83400_256x240.png +0 -0
  336. data/app/assets/images/jquery-ui/ui-icons_b8ec79_256x240.png +0 -0
  337. data/app/assets/images/jquery-ui/ui-icons_bbbbbb_256x240.png +0 -0
  338. data/app/assets/images/jquery-ui/ui-icons_bd7b00_256x240.png +0 -0
  339. data/app/assets/images/jquery-ui/ui-icons_c02669_256x240.png +0 -0
  340. data/app/assets/images/jquery-ui/ui-icons_c47a23_256x240.png +0 -0
  341. data/app/assets/images/jquery-ui/ui-icons_c98000_256x240.png +0 -0
  342. data/app/assets/images/jquery-ui/ui-icons_cb672b_256x240.png +0 -0
  343. data/app/assets/images/jquery-ui/ui-icons_cc0000_256x240.png +0 -0
  344. data/app/assets/images/jquery-ui/ui-icons_cccccc_256x240.png +0 -0
  345. data/app/assets/images/jquery-ui/ui-icons_cd0a0a_256x240.png +0 -0
  346. data/app/assets/images/jquery-ui/ui-icons_d19405_256x240.png +0 -0
  347. data/app/assets/images/jquery-ui/ui-icons_d8e7f3_256x240.png +0 -0
  348. data/app/assets/images/jquery-ui/ui-icons_e0fdff_256x240.png +0 -0
  349. data/app/assets/images/jquery-ui/ui-icons_e1e463_256x240.png +0 -0
  350. data/app/assets/images/jquery-ui/ui-icons_e3ddc9_256x240.png +0 -0
  351. data/app/assets/images/jquery-ui/ui-icons_e8e2b5_256x240.png +0 -0
  352. data/app/assets/images/jquery-ui/ui-icons_e9cd86_256x240.png +0 -0
  353. data/app/assets/images/jquery-ui/ui-icons_eb990f_256x240.png +0 -0
  354. data/app/assets/images/jquery-ui/ui-icons_ebccce_256x240.png +0 -0
  355. data/app/assets/images/jquery-ui/ui-icons_ed9f26_256x240.png +0 -0
  356. data/app/assets/images/jquery-ui/ui-icons_ededed_256x240.png +0 -0
  357. data/app/assets/images/jquery-ui/ui-icons_eeeeee_256x240.png +0 -0
  358. data/app/assets/images/jquery-ui/ui-icons_ef8c08_256x240.png +0 -0
  359. data/app/assets/images/jquery-ui/ui-icons_efec9f_256x240.png +0 -0
  360. data/app/assets/images/jquery-ui/ui-icons_f08000_256x240.png +0 -0
  361. data/app/assets/images/jquery-ui/ui-icons_f1fd86_256x240.png +0 -0
  362. data/app/assets/images/jquery-ui/ui-icons_f29a00_256x240.png +0 -0
  363. data/app/assets/images/jquery-ui/ui-icons_f2ec64_256x240.png +0 -0
  364. data/app/assets/images/jquery-ui/ui-icons_f35f07_256x240.png +0 -0
  365. data/app/assets/images/jquery-ui/ui-icons_f5e175_256x240.png +0 -0
  366. data/app/assets/images/jquery-ui/ui-icons_f7a50d_256x240.png +0 -0
  367. data/app/assets/images/jquery-ui/ui-icons_f9bd01_256x240.png +0 -0
  368. data/app/assets/images/jquery-ui/ui-icons_f9f2bd_256x240.png +0 -0
  369. data/app/assets/images/jquery-ui/ui-icons_fadc7a_256x240.png +0 -0
  370. data/app/assets/images/jquery-ui/ui-icons_fbc856_256x240.png +0 -0
  371. data/app/assets/images/jquery-ui/ui-icons_fbdb93_256x240.png +0 -0
  372. data/app/assets/images/jquery-ui/ui-icons_fcd113_256x240.png +0 -0
  373. data/app/assets/images/jquery-ui/ui-icons_fcdd4a_256x240.png +0 -0
  374. data/app/assets/images/jquery-ui/ui-icons_ff0084_256x240.png +0 -0
  375. data/app/assets/images/jquery-ui/ui-icons_ff7519_256x240.png +0 -0
  376. data/app/assets/images/jquery-ui/ui-icons_ffcf29_256x240.png +0 -0
  377. data/app/assets/images/jquery-ui/ui-icons_ffd27a_256x240.png +0 -0
  378. data/app/assets/images/jquery-ui/ui-icons_ffe180_256x240.png +0 -0
  379. data/app/assets/images/jquery-ui/ui-icons_ffeb33_256x240.png +0 -0
  380. data/app/assets/images/jquery-ui/ui-icons_ffffff_256x240.png +0 -0
  381. data/app/assets/stylesheets/jquery_ui/jquery.ui.accordion.css.scss +0 -39
  382. data/app/assets/stylesheets/jquery_ui/jquery.ui.all.css.scss +0 -14
  383. data/app/assets/stylesheets/jquery_ui/jquery.ui.autocomplete.css.scss +0 -17
  384. data/app/assets/stylesheets/jquery_ui/jquery.ui.base.css.scss +0 -27
  385. data/app/assets/stylesheets/jquery_ui/jquery.ui.button.css.scss +0 -115
  386. data/app/assets/stylesheets/jquery_ui/jquery.ui.core.css.scss +0 -92
  387. data/app/assets/stylesheets/jquery_ui/jquery.ui.datepicker.css.scss +0 -179
  388. data/app/assets/stylesheets/jquery_ui/jquery.ui.dialog.css.scss +0 -70
  389. data/app/assets/stylesheets/jquery_ui/jquery.ui.menu.css.scss +0 -80
  390. data/app/assets/stylesheets/jquery_ui/jquery.ui.progressbar.css.scss +0 -29
  391. data/app/assets/stylesheets/jquery_ui/jquery.ui.resizable.css.scss +0 -79
  392. data/app/assets/stylesheets/jquery_ui/jquery.ui.selectable.css.scss +0 -16
  393. data/app/assets/stylesheets/jquery_ui/jquery.ui.slider.css.scss +0 -74
  394. data/app/assets/stylesheets/jquery_ui/jquery.ui.spinner.css.scss +0 -66
  395. data/app/assets/stylesheets/jquery_ui/jquery.ui.tabs.css.scss +0 -53
  396. data/app/assets/stylesheets/jquery_ui/jquery.ui.theme.css.scss +0 -408
  397. data/app/assets/stylesheets/jquery_ui/jquery.ui.tooltip.css.scss +0 -20
  398. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.base.css.scss +0 -78
  399. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.black-tie.css.scss +0 -80
  400. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.blitzer.css.scss +0 -80
  401. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.cupertino.css.scss +0 -80
  402. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.dark-hive.css.scss +0 -80
  403. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.dot-luv.css.scss +0 -80
  404. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.eggplant.css.scss +0 -80
  405. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.excite-bike.css.scss +0 -80
  406. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.flick.css.scss +0 -80
  407. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.hot-sneaks.css.scss +0 -80
  408. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.humanity.css.scss +0 -80
  409. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.le-frog.css.scss +0 -80
  410. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.mint-choc.css.scss +0 -80
  411. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.overcast.css.scss +0 -80
  412. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.pepper-grinder.css.scss +0 -80
  413. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.redmond.css.scss +0 -80
  414. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.smoothness.css.scss +0 -80
  415. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.south-street.css.scss +0 -80
  416. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.start.css.scss +0 -80
  417. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.sunny.css.scss +0 -80
  418. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.swanky-purse.css.scss +0 -80
  419. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.trontastic.css.scss +0 -80
  420. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.ui-darkness.css.scss +0 -80
  421. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.ui-lightness.css.scss +0 -80
  422. data/app/assets/stylesheets/jquery_ui/themes/_jquery.ui.vader.css.scss +0 -80
  423. /data/{lib → archived/form_elements/dns_records/lib}/inline_forms/form_elements/dns_records_helper.rb +0 -0
  424. /data/{lib → archived/form_elements/question_list/lib}/inline_forms/form_elements/question_list_helper.rb +0 -0
@@ -5,6 +5,7 @@ require "tmpdir"
5
5
  require "fileutils"
6
6
  require "logger"
7
7
  require "rails"
8
+ require "active_record"
8
9
  require "rails/generators"
9
10
  require "inline_forms"
10
11
  require_relative "../lib/generators/inline_forms_addto_generator"
@@ -56,6 +57,45 @@ class InlineFormsAddtoGeneratorTest < Minitest::Test
56
57
  end
57
58
  RUBY
58
59
 
60
+ # A model whose list ends with a trailing metadata block (a :header above
61
+ # :info / timestamps rows). New rows must land above the header, not after
62
+ # the info rows.
63
+ METADATA_MODEL = <<~RUBY
64
+ class Profile < ApplicationRecord
65
+
66
+ def inline_forms_attribute_list
67
+ @inline_forms_attribute_list ||= [
68
+ [ :name, :text_field ],
69
+ [ :bio, :text_area ],
70
+ [ :header_meta, :header ],
71
+ [ :encrypted_password, :info ],
72
+ [ :created_at, :info ],
73
+ [ :updated_at, :info ],
74
+ ]
75
+ end
76
+
77
+ end
78
+ RUBY
79
+
80
+ # List closed with `].freeze` and a method defined *after* the list. The old
81
+ # greedy `]\\n end` regex could mismatch here; the bracket-depth scan must not.
82
+ FROZEN_LIST_MODEL = <<~RUBY
83
+ class Gadget < ApplicationRecord
84
+
85
+ def inline_forms_attribute_list
86
+ @inline_forms_attribute_list ||= [
87
+ [ :name, :text_field ],
88
+ [ :size, :integer_field ],
89
+ ].freeze
90
+ end
91
+
92
+ def _presentation
93
+ "\#{name}"
94
+ end
95
+
96
+ end
97
+ RUBY
98
+
59
99
  def setup
60
100
  @destination_root = Dir.mktmpdir("inline_forms_addto_generator_test")
61
101
  mkdir_p("app/models")
@@ -110,6 +150,27 @@ class InlineFormsAddtoGeneratorTest < Minitest::Test
110
150
  refute_includes(migration, "create_table")
111
151
  end
112
152
 
153
+ def test_header_adds_a_row_but_no_migration_column
154
+ write_model("widget.rb", GENERATOR_SHAPED_MODEL)
155
+
156
+ capture_io { run_generator("Widget", "section:header") }
157
+
158
+ model = read("app/models/widget.rb")
159
+ assert_includes(model, "[ :section, :header ]")
160
+ # A header needs no column, so no migration should be written at all.
161
+ refute_addto_migration_for("widgets")
162
+ end
163
+
164
+ def test_header_alongside_scalar_migrates_only_the_scalar
165
+ write_model("widget.rb", GENERATOR_SHAPED_MODEL)
166
+
167
+ capture_io { run_generator("Widget", "section:header", "note:string") }
168
+
169
+ migration = read_single_addto_migration_for("widgets")
170
+ assert_includes(migration, "add_column :widgets, :note, :string")
171
+ refute_includes(migration, ":section")
172
+ end
173
+
113
174
  def test_idempotent_rerun_does_not_duplicate_lines
114
175
  write_model("widget.rb", GENERATOR_SHAPED_MODEL)
115
176
 
@@ -220,12 +281,171 @@ class InlineFormsAddtoGeneratorTest < Minitest::Test
220
281
  refute_includes(model, "self.name <=> other.name")
221
282
  end
222
283
 
284
+ def test_smart_placement_lands_above_trailing_metadata_block
285
+ write_model("profile.rb", METADATA_MODEL)
286
+
287
+ capture_io { run_generator("Profile", "occupation:string") }
288
+
289
+ model = read("app/models/profile.rb")
290
+ assert_includes(model, "[ :occupation, :text_field ]")
291
+ # Above the metadata header, and therefore above every :info row.
292
+ assert(model.index("[ :occupation") < model.index("[ :header_meta"),
293
+ "occupation should be inserted above the metadata header")
294
+ assert(model.index("[ :bio") < model.index("[ :occupation"),
295
+ "occupation should stay below the existing content rows")
296
+ end
297
+
298
+ def test_frozen_list_and_trailing_method_do_not_break_insertion
299
+ write_model("gadget.rb", FROZEN_LIST_MODEL)
300
+
301
+ capture_io { run_generator("Gadget", "color:string") }
302
+
303
+ model = read("app/models/gadget.rb")
304
+ assert_includes(model, "[ :color, :text_field ]")
305
+ # Still a single, valid, frozen list; the trailing method is untouched.
306
+ assert_equal(1, model.scan("].freeze").size)
307
+ assert_includes(model, "def _presentation")
308
+ assert(model.index("[ :size") < model.index("[ :color"),
309
+ "color should be appended after the last existing row")
310
+ assert(model.index("[ :color") < model.index("].freeze"),
311
+ "color should be inside the frozen array")
312
+ refute_includes(model, "no inline_forms_attribute_list found")
313
+ end
314
+
315
+ def test_after_option_places_row_after_named_attribute
316
+ write_model("widget.rb", GENERATOR_SHAPED_MODEL)
317
+
318
+ capture_io { run_generator("Widget", "occupation:string", "--after=name") }
319
+
320
+ model = read("app/models/widget.rb")
321
+ assert(model.index("[ :name") < model.index("[ :occupation"))
322
+ assert(model.index("[ :occupation") < model.index("[ :category"))
323
+ end
324
+
325
+ def test_before_option_places_row_before_named_attribute
326
+ write_model("widget.rb", GENERATOR_SHAPED_MODEL)
327
+
328
+ capture_io { run_generator("Widget", "occupation:string", "--before=category") }
329
+
330
+ model = read("app/models/widget.rb")
331
+ assert(model.index("[ :occupation") < model.index("[ :category"))
332
+ end
333
+
334
+ def test_missing_anchor_warns_and_falls_back_to_default
335
+ write_model("widget.rb", GENERATOR_SHAPED_MODEL)
336
+
337
+ out, _err = capture_io do
338
+ run_generator("Widget", "occupation:string", "--after=nope")
339
+ end
340
+
341
+ assert_includes(out, "--after nope not found")
342
+ model = read("app/models/widget.rb")
343
+ assert_includes(model, "[ :occupation, :text_field ]")
344
+ end
345
+
346
+ def test_value_bearing_element_gets_placeholder_hash_and_warns
347
+ write_model("widget.rb", GENERATOR_SHAPED_MODEL)
348
+
349
+ out, _err = capture_io do
350
+ run_generator("Widget", "status:dropdown_with_values")
351
+ end
352
+
353
+ model = read("app/models/widget.rb")
354
+ assert_includes(model, "[ :status, :dropdown_with_values, { 1 => 'one', 2 => 'two' } ]")
355
+ assert_includes(out, "values hash")
356
+ end
357
+
358
+ def test_back_to_back_runs_produce_distinct_migration_filenames
359
+ write_model("widget.rb", GENERATOR_SHAPED_MODEL)
360
+
361
+ # No sleep between runs: unique_time_stamp must still disambiguate.
362
+ capture_io { run_generator("Widget", "alpha:string") }
363
+ capture_io { run_generator("Widget", "beta:string") }
364
+
365
+ files = Dir.glob(File.join(@destination_root, "db/migrate/*_inline_forms_add_to_widgets_*.rb"))
366
+ assert_equal(2, files.size, "expected two distinct addto migrations, got #{files.inspect}")
367
+ prefixes = files.map { |f| File.basename(f)[/\A(\d{14})/, 1] }
368
+ assert_equal(prefixes.uniq.size, prefixes.size, "migration timestamps collided: #{prefixes.inspect}")
369
+ end
370
+
371
+ def test_generated_migration_applies_cleanly_to_sqlite
372
+ write_model("widget.rb", GENERATOR_SHAPED_MODEL)
373
+
374
+ capture_io { run_generator("Widget", "occupation:string", "organization:belongs_to") }
375
+
376
+ migration_file = Dir.glob(
377
+ File.join(@destination_root, "db/migrate/*_inline_forms_add_to_widgets_*.rb")
378
+ ).first
379
+ refute_nil(migration_file, "expected an addto migration to be generated")
380
+
381
+ conn = apply_migration_to_memory_sqlite(migration_file, tables: %i[organizations widgets])
382
+
383
+ assert(conn.column_exists?(:widgets, :occupation), "occupation column should exist after migrate")
384
+ assert(conn.column_exists?(:widgets, :organization_id), "organization_id column should exist after migrate")
385
+ ensure
386
+ teardown_sqlite
387
+ end
388
+
223
389
  private
224
390
 
225
391
  def run_generator(*args)
226
392
  InlineForms::InlineFormsAddtoGenerator.start(args, destination_root: @destination_root)
227
393
  end
228
394
 
395
+ # Applies a generated migration file against a fresh in-memory sqlite DB on a
396
+ # dedicated connection (ScratchMigrationBase), pre-creating the referenced
397
+ # tables. Proves the emitted migration DSL is syntactically and semantically
398
+ # valid without a full Rails app — and without disturbing ActiveRecord::Base's
399
+ # connection (the engine integration tests share this process). Returns the
400
+ # scratch connection for assertions.
401
+ def apply_migration_to_memory_sqlite(path, tables:)
402
+ scratch_migration_base.establish_connection(adapter: "sqlite3", database: ":memory:")
403
+ conn = scratch_migration_base.connection
404
+ ActiveRecord::Migration.verbose = false
405
+ tables.each do |t|
406
+ conn.create_table(t, force: true) { |tbl| tbl.string :name }
407
+ end
408
+
409
+ src = File.read(path)
410
+ klass_name = src[/class\s+(\w+)\s*</, 1]
411
+ Object.send(:remove_const, klass_name.to_sym) if Object.const_defined?(klass_name.to_sym)
412
+ Object.class_eval(src)
413
+ klass = Object.const_get(klass_name)
414
+ @sqlite_migration_const = klass_name
415
+ # exec_migration(conn, dir) runs the migration against `conn` specifically,
416
+ # unlike migrate(:up) which uses ActiveRecord::Base.connection.
417
+ ActiveRecord::Migration.suppress_messages { klass.new.exec_migration(conn, :up) }
418
+ conn
419
+ end
420
+
421
+ def teardown_sqlite
422
+ if @sqlite_migration_const && Object.const_defined?(@sqlite_migration_const.to_sym)
423
+ Object.send(:remove_const, @sqlite_migration_const.to_sym)
424
+ end
425
+ @sqlite_migration_const = nil
426
+ @@scratch_migration_base&.remove_connection
427
+ rescue StandardError
428
+ nil
429
+ end
430
+
431
+ # A dedicated abstract ActiveRecord::Base subclass whose connection is the
432
+ # scratch in-memory sqlite DB. Created lazily at *runtime* (not file-load
433
+ # time): subclassing ActiveRecord::Base before the dummy app boots trips the
434
+ # railtie into resolving the wrong Rails env. Never touches Base's own
435
+ # connection, so the engine integration tests sharing this process keep the
436
+ # dummy app's in-memory schema.
437
+ @@scratch_migration_base = nil
438
+
439
+ def scratch_migration_base
440
+ @@scratch_migration_base ||= begin
441
+ unless Object.const_defined?(:ScratchAddtoMigrationBase)
442
+ Object.const_set(:ScratchAddtoMigrationBase,
443
+ Class.new(ActiveRecord::Base) { self.abstract_class = true })
444
+ end
445
+ Object.const_get(:ScratchAddtoMigrationBase)
446
+ end
447
+ end
448
+
229
449
  def write_model(file, content)
230
450
  File.write(File.join(@destination_root, "app/models", file), content)
231
451
  end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration_test_helper"
4
+
5
+ # End-to-end (controller + views + Turbo frames) CRUD through the engine's
6
+ # InlineFormsController, running against the test/dummy host app.
7
+ class InlineFormsCrudTest < InlineFormsIntegrationTestCase
8
+ setup do
9
+ @widget = Widget.create!(name: "Alpha", priority: 2)
10
+ end
11
+
12
+ test "index lists rows inside the list turbo-frame" do
13
+ get widgets_path(update: "widgets_list"), headers: frame_headers("widgets_list")
14
+
15
+ assert_response :success
16
+ assert_includes response.body, %(<turbo-frame id="widgets_list" class="list_container">)
17
+ assert_includes response.body, %(<turbo-frame id="widget_#{@widget.id}">)
18
+ assert_includes response.body, "Alpha"
19
+ end
20
+
21
+ test "index applies inline_forms_search when ?search= is passed" do
22
+ Widget.create!(name: "Beta")
23
+
24
+ get widgets_path(update: "widgets_list", search: "Alp"),
25
+ headers: frame_headers("widgets_list")
26
+
27
+ assert_response :success
28
+ assert_includes response.body, "Alpha"
29
+ refute_includes response.body, "Beta"
30
+ end
31
+
32
+ test "row show renders attribute frames" do
33
+ frame = "widget_#{@widget.id}"
34
+ get widget_path(@widget, update: frame), headers: frame_headers(frame)
35
+
36
+ assert_response :success
37
+ assert_includes response.body, %(<turbo-frame id="#{frame}">)
38
+ assert_includes response.body, %(<turbo-frame id="widget_#{@widget.id}_name">)
39
+ end
40
+
41
+ test "attribute edit renders the input in its frame" do
42
+ frame = "widget_#{@widget.id}_name"
43
+ get edit_widget_path(@widget, attribute: "name", form_element: "text_field",
44
+ update: frame),
45
+ headers: frame_headers(frame)
46
+
47
+ assert_response :success
48
+ assert_includes response.body, %(<turbo-frame id="#{frame}">)
49
+ assert_includes response.body, %(name="name")
50
+ assert_includes response.body, %(value="Alpha")
51
+ end
52
+
53
+ test "attribute update persists and renders the show state" do
54
+ frame = "widget_#{@widget.id}_name"
55
+ put widget_path(@widget, attribute: "name", form_element: "text_field",
56
+ update: frame),
57
+ params: { name: "Renamed" }, headers: frame_headers(frame)
58
+
59
+ assert_response :success
60
+ assert_equal "Renamed", @widget.reload.name
61
+ assert_includes response.body, "Renamed"
62
+ end
63
+
64
+ test "failed save re-renders the edit state, not a fake show (8.1.19)" do
65
+ frame = "widget_#{@widget.id}_name"
66
+ put widget_path(@widget, attribute: "name", form_element: "text_field",
67
+ update: frame),
68
+ params: { name: "" }, headers: frame_headers(frame)
69
+
70
+ assert_response :success
71
+ assert_equal "Alpha", @widget.reload.name, "blank name must not persist"
72
+ assert_includes response.body, %(name="name"),
73
+ "expected the edit input to be re-rendered after a failed save"
74
+ end
75
+
76
+ test "destroy responds with the destroyed-row state and an undo pointing at the destroy version" do
77
+ frame = "widget_#{@widget.id}"
78
+ assert_difference -> { Widget.count }, -1 do
79
+ delete widget_path(@widget, update: frame), headers: frame_headers(frame)
80
+ end
81
+ assert_response :success
82
+
83
+ destroy_version = PaperTrail::Version.where(item_type: "Widget",
84
+ item_id: @widget.id,
85
+ event: "destroy").last
86
+ assert destroy_version, "expected a PaperTrail destroy version"
87
+ assert_includes response.body, "/widgets/#{destroy_version.id}/revert",
88
+ "undo link must target the destroy version (8.1.13)"
89
+ end
90
+
91
+ # revert responds with turbo_stream (replaces row + versions panel at once,
92
+ # see render_revert_response); the undo/Restore links request that format.
93
+ def turbo_stream_headers(frame_id)
94
+ frame_headers(frame_id).merge("Accept" => "text/vnd.turbo-stream.html")
95
+ end
96
+
97
+ test "revert of a destroy version restores the row (undo)" do
98
+ frame = "widget_#{@widget.id}"
99
+ delete widget_path(@widget, update: frame), headers: frame_headers(frame)
100
+ destroy_version = PaperTrail::Version.where(item_type: "Widget",
101
+ item_id: @widget.id,
102
+ event: "destroy").last
103
+
104
+ assert_difference -> { Widget.count }, +1 do
105
+ post revert_widget_path(destroy_version.id, update: "widgets_list"),
106
+ headers: turbo_stream_headers("widgets_list")
107
+ end
108
+ assert_response :success
109
+ assert Widget.exists?(@widget.id), "undo must restore the destroyed row"
110
+ assert_equal "Alpha", Widget.find(@widget.id).name
111
+ end
112
+
113
+ test "revert of a destroy version is idempotent when the row already exists (8.1.17)" do
114
+ frame = "widget_#{@widget.id}"
115
+ delete widget_path(@widget, update: frame), headers: frame_headers(frame)
116
+ destroy_version = PaperTrail::Version.where(item_type: "Widget",
117
+ item_id: @widget.id,
118
+ event: "destroy").last
119
+
120
+ post revert_widget_path(destroy_version.id, update: "widgets_list"),
121
+ headers: turbo_stream_headers("widgets_list")
122
+ assert Widget.exists?(@widget.id)
123
+
124
+ # Replaying the same restore must not raise RecordNotUnique.
125
+ post revert_widget_path(destroy_version.id, update: "widgets_list"),
126
+ headers: turbo_stream_headers("widgets_list")
127
+ assert_response :success
128
+ assert_equal 1, Widget.where(id: @widget.id).count
129
+ end
130
+
131
+ test "versions panel lists an update event" do
132
+ @widget.update!(name: "Renamed once")
133
+ frame = "widget_#{@widget.id}"
134
+
135
+ get list_versions_widget_path(@widget, update: frame),
136
+ headers: frame_headers(frame)
137
+
138
+ assert_response :success
139
+ assert_includes response.body, "Renamed once"
140
+ end
141
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration_test_helper"
4
+
5
+ # First-ever render coverage for two legacy display elements that no example
6
+ # app exercises (see stuff/improvement-plan.md §4): simple_file_field (raw
7
+ # filename column + host-provided download route) and pdf_link (host-provided
8
+ # /:attribute/:id report route).
9
+ class LegacyElementsTest < InlineFormsIntegrationTestCase
10
+ setup do
11
+ @widget = Widget.create!(name: "Legacy")
12
+ end
13
+
14
+ test "simple_file_field blank state renders the inline-edit plus link" do
15
+ frame = "widget_#{@widget.id}"
16
+ get widget_path(@widget, update: frame), headers: frame_headers(frame)
17
+
18
+ assert_response :success
19
+ assert_includes response.body, %(<turbo-frame id="widget_#{@widget.id}_manual">)
20
+ assert_includes response.body, "fi-plus"
21
+ end
22
+
23
+ test "simple_file_field edit renders a file input" do
24
+ frame = "widget_#{@widget.id}_manual"
25
+ get edit_widget_path(@widget, attribute: "manual",
26
+ form_element: "simple_file_field", update: frame),
27
+ headers: frame_headers(frame)
28
+
29
+ assert_response :success
30
+ assert_includes response.body, %(type="file")
31
+ assert_includes response.body, %(name="manual")
32
+ end
33
+
34
+ test "pdf_link show renders preview and pdf links for the attribute route" do
35
+ frame = "widget_#{@widget.id}"
36
+ get widget_path(@widget, update: frame), headers: frame_headers(frame)
37
+
38
+ assert_response :success
39
+ assert_includes response.body, %(href="/report/#{@widget.id}")
40
+ assert_includes response.body, %(href="/report/#{@widget.id}.pdf")
41
+ end
42
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration_test_helper"
4
+
5
+ # Engine-level render coverage for the native-input form elements introduced
6
+ # in 8.1.25/8.1.26 (previously only covered by the example-app gate).
7
+ class NativeInputsTest < InlineFormsIntegrationTestCase
8
+ setup do
9
+ @widget = Widget.create!(
10
+ name: "Native",
11
+ released_on: Date.new(2026, 5, 17),
12
+ meeting_at: Time.utc(2000, 1, 1, 9, 15),
13
+ start_month: Date.new(2026, 5, 1),
14
+ priority: 2,
15
+ rating: 3
16
+ )
17
+ end
18
+
19
+ def edit_attribute(attribute, form_element)
20
+ frame = "widget_#{@widget.id}_#{attribute}"
21
+ get edit_widget_path(@widget, attribute: attribute, form_element: form_element,
22
+ update: frame),
23
+ headers: frame_headers(frame)
24
+ assert_response :success
25
+ response.body
26
+ end
27
+
28
+ test "date_select renders a native date input with ISO value" do
29
+ body = edit_attribute("released_on", "date_select")
30
+ assert_includes body, %(type="date")
31
+ assert_includes body, %(value="2026-05-17")
32
+ refute_includes body, "<script"
33
+ end
34
+
35
+ test "time_select renders a native time input with HH:MM value" do
36
+ body = edit_attribute("meeting_at", "time_select")
37
+ assert_includes body, %(type="time")
38
+ assert_includes body, %(value="09:15")
39
+ end
40
+
41
+ test "month_year_picker renders a native month input with YYYY-MM value" do
42
+ body = edit_attribute("start_month", "month_year_picker")
43
+ assert_includes body, %(type="month")
44
+ assert_includes body, %(value="2026-05")
45
+ end
46
+
47
+ test "month_year_picker update accepts native and legacy formats" do
48
+ frame = "widget_#{@widget.id}_start_month"
49
+
50
+ put widget_path(@widget, attribute: "start_month",
51
+ form_element: "month_year_picker", update: frame),
52
+ params: { start_month: "2026-09" }, headers: frame_headers(frame)
53
+ assert_equal Date.new(2026, 9, 1), @widget.reload.start_month
54
+
55
+ put widget_path(@widget, attribute: "start_month",
56
+ form_element: "month_year_picker", update: frame),
57
+ params: { start_month: "November 2027" }, headers: frame_headers(frame)
58
+ assert_equal Date.new(2027, 11, 1), @widget.reload.start_month
59
+ end
60
+
61
+ test "dropdown_with_other renders a datalist-backed combobox without inline scripts" do
62
+ Kind.create!(name: "Gadget")
63
+ Kind.create!(name: "Gizmo")
64
+
65
+ body = edit_attribute("kind", "dropdown_with_other")
66
+ assert_includes body, "<datalist"
67
+ assert_includes body, %(<option value="Gadget">)
68
+ assert_includes body, %(<option value="Gizmo">)
69
+ assert_includes body, %(name="_widget[kind_other]")
70
+ refute_includes body, "<script"
71
+ end
72
+
73
+ test "dropdown_with_other update matches an existing record by name" do
74
+ kind = Kind.create!(name: "Gadget")
75
+ frame = "widget_#{@widget.id}_kind"
76
+
77
+ put widget_path(@widget, attribute: "kind",
78
+ form_element: "dropdown_with_other", update: frame),
79
+ params: { _widget: { kind_other: "Gadget" } }, headers: frame_headers(frame)
80
+
81
+ assert_response :success
82
+ @widget.reload
83
+ assert_equal kind.id, @widget.kind_id
84
+ assert_nil @widget.kind_other
85
+ end
86
+
87
+ test "dropdown_with_other update stores unmatched text as the other value" do
88
+ Kind.create!(name: "Gadget")
89
+ frame = "widget_#{@widget.id}_kind"
90
+
91
+ put widget_path(@widget, attribute: "kind",
92
+ form_element: "dropdown_with_other", update: frame),
93
+ params: { _widget: { kind_other: "Completely new thing" } },
94
+ headers: frame_headers(frame)
95
+
96
+ assert_response :success
97
+ @widget.reload
98
+ assert_equal 0, @widget.kind_id
99
+ assert_equal "Completely new thing", @widget.kind_other
100
+ end
101
+
102
+ test "slider_with_values renders a native range input with label data" do
103
+ body = edit_attribute("rating", "slider_with_values")
104
+ assert_includes body, %(type="range")
105
+ assert_includes body, "slider_with_values"
106
+ assert_includes body, "data-slider-values"
107
+ assert_includes body, %(<output)
108
+ refute_includes body, "<script"
109
+ end
110
+
111
+ test "slider_with_values show renders a progress bar (clickable, non-interactive)" do
112
+ frame = "widget_#{@widget.id}"
113
+ get widget_path(@widget, update: frame), headers: frame_headers(frame)
114
+
115
+ assert_response :success
116
+ assert_includes response.body, "<progress"
117
+ end
118
+
119
+ test "color_field renders a native color input and round-trips a hex value" do
120
+ @widget.update!(accent: "#a3381e")
121
+ body = edit_attribute("accent", "color_field")
122
+ assert_includes body, %(type="color")
123
+ assert_includes body, %(value="#a3381e")
124
+
125
+ frame = "widget_#{@widget.id}_accent"
126
+ put widget_path(@widget, attribute: "accent", form_element: "color_field",
127
+ update: frame),
128
+ params: { accent: "#2563EB" }, headers: frame_headers(frame)
129
+ assert_response :success
130
+ assert_equal "#2563eb", @widget.reload.accent, "hex is normalized to lowercase"
131
+
132
+ # Junk input is rejected server-side (stored as nil), not persisted raw.
133
+ put widget_path(@widget, attribute: "accent", form_element: "color_field",
134
+ update: frame),
135
+ params: { accent: "red; background:url(x)" }, headers: frame_headers(frame)
136
+ assert_nil @widget.reload.accent
137
+ end
138
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration_test_helper"
4
+
5
+ # "Open after create" (stuff/2026-07-10-nested-creation-options.md, Option A):
6
+ # a successful top-level create of a class with an :associated (has_many)
7
+ # panel responds with Turbo Streams that refresh the list and open the new
8
+ # record's row, so the nested panel is immediately usable. Everything else
9
+ # (classes without :associated, nested creates, failed saves, non-stream
10
+ # clients) keeps the plain frame responses.
11
+ class OpenAfterCreateTest < InlineFormsIntegrationTestCase
12
+ # Turbo form submissions advertise turbo-stream before text/html.
13
+ def stream_headers(frame_id)
14
+ { "Turbo-Frame" => frame_id,
15
+ "Accept" => "text/vnd.turbo-stream.html, text/html" }
16
+ end
17
+
18
+ test "top-level create with an associated panel streams the list and the opened row" do
19
+ assert_difference -> { Machine.count }, +1 do
20
+ post machines_path(update: "machines_list"),
21
+ params: { name: "Lathe" },
22
+ headers: stream_headers("machines_list")
23
+ end
24
+ assert_response :success
25
+ assert_equal "text/vnd.turbo-stream.html", response.media_type
26
+
27
+ machine = Machine.find_by!(name: "Lathe")
28
+ assert_includes response.body,
29
+ %(<turbo-stream action="replace" target="machines_list">)
30
+ assert_includes response.body,
31
+ %(<turbo-stream action="replace" target="machine_#{machine.id}">)
32
+
33
+ # The opened row exposes the parts panel with its "new part" link,
34
+ # carrying the freshly created parent's id.
35
+ assert_includes response.body, %(id="machine_#{machine.id}_parts_list_auto_header")
36
+ assert_match(
37
+ %r{/parts/new\?[^"]*parent_id=#{machine.id}},
38
+ response.body,
39
+ "expected the associated panel's new-record link for the created parent"
40
+ )
41
+ end
42
+
43
+ test "top-level create without turbo-stream accept keeps the list frame response" do
44
+ post machines_path(update: "machines_list"),
45
+ params: { name: "Mill" },
46
+ headers: frame_headers("machines_list")
47
+ assert_response :success
48
+ assert_equal "text/html", response.media_type
49
+ assert_includes response.body, %(<turbo-frame id="machines_list">)
50
+ end
51
+
52
+ test "create of a class without an associated panel is unchanged" do
53
+ post parts_path(update: "parts_list"),
54
+ params: { name: "Plain" },
55
+ headers: stream_headers("parts_list")
56
+ assert_response :success
57
+ assert_equal "text/html", response.media_type
58
+ assert_includes response.body, %(<turbo-frame id="parts_list">)
59
+ end
60
+
61
+ test "nested create under a parent keeps the frame response" do
62
+ machine = Machine.create!(name: "Host")
63
+ frame = "machine_#{machine.id}_parts"
64
+
65
+ assert_difference -> { Part.count }, +1 do
66
+ post parts_path(update: frame, parent_class: "Machine", parent_id: machine.id),
67
+ params: { name: "Bolt" },
68
+ headers: stream_headers(frame)
69
+ end
70
+ assert_response :success
71
+ assert_equal "text/html", response.media_type
72
+ assert_equal machine.id, Part.find_by!(name: "Bolt").machine_id
73
+ end
74
+
75
+ test "failed top-level create re-renders the new form, not streams" do
76
+ assert_no_difference -> { Machine.count } do
77
+ post machines_path(update: "machines_list"),
78
+ params: { name: "" },
79
+ headers: stream_headers("machines_list")
80
+ end
81
+ assert_response :success
82
+ assert_equal "text/html", response.media_type
83
+ assert_includes response.body, 'class="edit_form"'
84
+ end
85
+ end