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
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration_test_helper"
4
+
5
+ # Covers the pending-migration gate: an attribute_list row whose column does
6
+ # not exist yet (the inline_forms_addto window between the model edit and
7
+ # `rails db:migrate`) must render a read-only placeholder and be skipped on
8
+ # write, instead of 500ing on the missing column. Gizmo's list references
9
+ # :pending_note, which has no column on the gizmos table (see
10
+ # test/integration_test_helper.rb and test/dummy/app/models/gizmo.rb).
11
+ class PendingMigrationGateTest < InlineFormsIntegrationTestCase
12
+ setup do
13
+ @gizmo = Gizmo.create!(name: "Gadget")
14
+ end
15
+
16
+ test "predicate flags a phantom column and only that" do
17
+ g = @gizmo
18
+ # phantom scalar column -> pending
19
+ assert InlineForms.attribute_pending_migration?(g, :pending_note, :text_field)
20
+ # real column -> not pending
21
+ refute InlineForms.attribute_pending_migration?(g, :name, :text_field)
22
+ # header is a label, never gated
23
+ refute InlineForms.attribute_pending_migration?(g, :section, :header)
24
+ # info bound to a real column
25
+ refute InlineForms.attribute_pending_migration?(g, :created_at, :info)
26
+ # relation dropdown backed by a missing foreign key -> pending
27
+ assert InlineForms.attribute_pending_migration?(g, :vendor, :dropdown)
28
+ # no-column elements are never gated
29
+ refute InlineForms.attribute_pending_migration?(g, :parts, :has_many)
30
+ refute InlineForms.attribute_pending_migration?(g, :body, :rich_text)
31
+ refute InlineForms.attribute_pending_migration?(g, :report, :pdf_link)
32
+ # exempt virtual-backed elements (column name != attribute)
33
+ refute InlineForms.attribute_pending_migration?(g, :amount, :money_field)
34
+ end
35
+
36
+ test "row show renders a pending placeholder and does not raise" do
37
+ frame = "gizmo_#{@gizmo.id}"
38
+ get gizmo_path(@gizmo, update: frame), headers: frame_headers(frame)
39
+
40
+ assert_response :success
41
+ assert_includes response.body, "pending migration"
42
+ # the real field still renders its own edit frame
43
+ assert_includes response.body, %(<turbo-frame id="gizmo_#{@gizmo.id}_name">)
44
+ # the phantom field is NOT rendered as an editable field frame
45
+ refute_includes response.body, %(<turbo-frame id="gizmo_#{@gizmo.id}_pending_note">)
46
+ end
47
+
48
+ test "new form renders a pending placeholder instead of an input" do
49
+ get new_gizmo_path(update: "gizmos_list"), headers: frame_headers("gizmos_list")
50
+
51
+ assert_response :success
52
+ assert_includes response.body, "pending migration"
53
+ refute_includes response.body, %(name="pending_note")
54
+ end
55
+
56
+ test "create skips the pending column and still saves" do
57
+ assert_difference -> { Gizmo.count }, +1 do
58
+ post gizmos_path(update: "gizmos_list"),
59
+ params: { name: "Fresh", pending_note: "ignored — no column yet" },
60
+ headers: frame_headers("gizmos_list")
61
+ end
62
+ assert_response :success
63
+ assert Gizmo.exists?(name: "Fresh")
64
+ end
65
+
66
+ test "forced update of a pending field is refused, not a 500" do
67
+ frame = "gizmo_#{@gizmo.id}_pending_note"
68
+ put gizmo_path(@gizmo, attribute: "pending_note", form_element: "text_field",
69
+ update: frame),
70
+ params: { pending_note: "x" }, headers: frame_headers(frame)
71
+
72
+ assert_response :unprocessable_entity
73
+ assert_includes response.body, "pending migration"
74
+ end
75
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration_test_helper"
4
+
5
+ # The batch pipeline (phases 1-3): drafting intents into a batch, freeze on
6
+ # submit, token-authenticated export + status callback, and import/replay
7
+ # via a recording executor (never mutates the dummy tree).
8
+ class SchemaBatchPipelineTest < InlineFormsIntegrationTestCase
9
+ def teardown
10
+ InlineFormsSchemaEdit.export_token = nil
11
+ super
12
+ end
13
+
14
+ def draft_widget_field!(attribute: "warehouse_note", label: "Warehouse note")
15
+ post inline_forms_schema_draft_path,
16
+ params: { model_name: "Widget", attribute: attribute,
17
+ form_element: "text_field", after: "name", label: label, locale: "en" }
18
+ end
19
+
20
+ # -- phase 1: drafting + freeze ------------------------------------------
21
+
22
+ test "drafting an intent creates a draft batch (the cart) and index shows it" do
23
+ draft_widget_field!
24
+ assert_response :redirect
25
+
26
+ batch = InlineForms::SchemaBatch.with_status(:draft).first
27
+ assert batch, "draft batch created"
28
+ assert_equal 1, batch.intents.count
29
+ intent = batch.intents.first
30
+ assert_equal %w[Widget warehouse_note text_field name], [
31
+ intent.target_model, intent.attr_name, intent.form_element, intent.after_attr
32
+ ]
33
+
34
+ get inline_forms_schema_index_path
35
+ assert_response :success
36
+ assert_includes response.body, "warehouse_note"
37
+ assert_includes response.body, "Submit batch"
38
+ end
39
+
40
+ test "invalid intents are rejected at drafting time" do
41
+ post inline_forms_schema_draft_path,
42
+ params: { model_name: "Widget", attribute: "name", form_element: "text_field" }
43
+ assert_response :success # renders :new with the error
44
+ assert_includes response.body, "already has a name column"
45
+ assert_equal 0, InlineForms::SchemaIntentRecord.count
46
+ end
47
+
48
+ test "submit freezes the batch: no more adds, edits or removals; digest sealed" do
49
+ draft_widget_field!
50
+ post inline_forms_schema_submit_batch_path
51
+ assert_response :redirect
52
+
53
+ batch = InlineForms::SchemaBatch.last
54
+ assert_equal "submitted", batch.status
55
+ assert batch.submitted_at
56
+ assert_match(/\Asha256:/, batch.content_digest)
57
+
58
+ intent = batch.intents.first
59
+ intent.label = "changed"
60
+ refute intent.save, "frozen intent must not save"
61
+ refute intent.destroy, "frozen intent must not be destroyed"
62
+
63
+ refute batch.intents.create(target_model: "Widget", attr_name: "late_one",
64
+ form_element: "text_field").persisted?,
65
+ "no new intents into a frozen batch"
66
+
67
+ batch.window_at = 2.days.from_now
68
+ refute batch.save, "frozen batch attributes must not change"
69
+
70
+ # A new draft goes into a NEW batch.
71
+ draft_widget_field!(attribute: "second_note")
72
+ assert_equal 2, InlineForms::SchemaBatch.count
73
+ end
74
+
75
+ test "submitting an empty batch is refused" do
76
+ post inline_forms_schema_submit_batch_path
77
+ assert_redirected_to inline_forms_schema_index_path
78
+ assert_equal 0, InlineForms::SchemaBatch.where(status: "submitted").count
79
+ end
80
+
81
+ test "status transitions follow the pipeline; illegal jumps raise" do
82
+ draft_widget_field!
83
+ batch = InlineForms::SchemaBatch.last.submit!
84
+
85
+ batch.transition!(:processing)
86
+ batch.transition!(:ready, git_sha: "abc123")
87
+ assert_equal "abc123", batch.git_sha
88
+ assert_raises(ArgumentError) { batch.transition!(:submitted) }
89
+ batch.transition!(:applied)
90
+ assert batch.applied_at
91
+ end
92
+
93
+ # -- phase 3: machine endpoints ------------------------------------------
94
+
95
+ test "export 404s without a configured token and 401s with a wrong one" do
96
+ draft_widget_field!
97
+ batch = InlineForms::SchemaBatch.last.submit!
98
+
99
+ get inline_forms_schema_export_path(batch)
100
+ assert_response :not_found
101
+
102
+ InlineFormsSchemaEdit.export_token = "sekret"
103
+ get inline_forms_schema_export_path(batch), headers: { "Authorization" => "Bearer wrong" }
104
+ assert_response :unauthorized
105
+ end
106
+
107
+ test "export returns the sealed payload; draft batches are refused" do
108
+ InlineFormsSchemaEdit.export_token = "sekret"
109
+ draft_widget_field!
110
+ batch = InlineForms::SchemaBatch.last
111
+
112
+ get inline_forms_schema_export_path(batch), headers: { "Authorization" => "Bearer sekret" }
113
+ assert_response :conflict
114
+
115
+ batch.submit!
116
+ get inline_forms_schema_export_path(batch), headers: { "Authorization" => "Bearer sekret" }
117
+ assert_response :success
118
+ payload = JSON.parse(response.body)
119
+ assert_equal 1, payload["format"]
120
+ assert_equal batch.content_digest, payload["digest"]
121
+ assert_equal "warehouse_note", payload["intents"].first["attribute"]
122
+ end
123
+
124
+ test "status callback drives the batch through the pipeline" do
125
+ InlineFormsSchemaEdit.export_token = "sekret"
126
+ draft_widget_field!
127
+ batch = InlineForms::SchemaBatch.last.submit!
128
+
129
+ post inline_forms_schema_batch_status_path(batch),
130
+ params: { status: "processing" }, headers: { "Authorization" => "Bearer sekret" }
131
+ assert_response :success
132
+ assert_equal "processing", batch.reload.status
133
+
134
+ post inline_forms_schema_batch_status_path(batch),
135
+ params: { status: "applied" }, headers: { "Authorization" => "Bearer sekret" }
136
+ assert_response :unprocessable_entity, "processing -> applied skips ready"
137
+
138
+ post inline_forms_schema_batch_status_path(batch),
139
+ params: { status: "failed", error: "test gate red" },
140
+ headers: { "Authorization" => "Bearer sekret" }
141
+ assert_equal "failed", batch.reload.status
142
+ assert_equal "test gate red", batch.error
143
+ end
144
+
145
+ # -- phase 2: export/import round trip -----------------------------------
146
+
147
+ test "import verifies the digest and replays intents through the executor" do
148
+ draft_widget_field!
149
+ batch = InlineForms::SchemaBatch.last.submit!
150
+
151
+ payload = InlineFormsSchemaEdit::BatchExport.payload(batch)
152
+
153
+ recorded = []
154
+ labels = []
155
+ import = InlineFormsSchemaEdit::BatchImport.new(
156
+ payload,
157
+ executor: ->(args, _root) { recorded << args },
158
+ label_writer: ->(**kwargs) { labels << kwargs; "/dev/null/labels.yml" }
159
+ )
160
+ result = import.apply!(destination_root: Dir.tmpdir)
161
+
162
+ assert_equal 1, result.applied
163
+ assert_equal [ "Widget", "warehouse_note:text_field", "--after=name" ], recorded.first
164
+ assert_equal "Warehouse note", labels.first[:label]
165
+ assert_includes result.plan.join("\n"), "inline_forms_addto Widget warehouse_note:text_field"
166
+ end
167
+
168
+ test "import rejects a tampered payload (digest mismatch) and stale intents" do
169
+ draft_widget_field!
170
+ batch = InlineForms::SchemaBatch.last.submit!
171
+ payload = InlineFormsSchemaEdit::BatchExport.payload(batch)
172
+
173
+ tampered = payload.deep_dup
174
+ tampered["intents"].first["attribute"] = "evil_column"
175
+ error = assert_raises(InlineFormsSchemaEdit::BatchImport::ImportError) do
176
+ InlineFormsSchemaEdit::BatchImport.new(tampered).verify!
177
+ end
178
+ assert_match(/digest mismatch/, error.message)
179
+
180
+ # A column that already exists in THIS checkout fails per-intent validation.
181
+ stale = InlineFormsSchemaEdit::BatchExport.payload(batch)
182
+ stale["intents"].first["attribute"] = "name"
183
+ stale["digest"] = "sha256:#{Digest::SHA256.hexdigest(JSON.generate(stale['intents']))}"
184
+ error = assert_raises(InlineFormsSchemaEdit::BatchImport::ImportError) do
185
+ InlineFormsSchemaEdit::BatchImport.new(stale).verify!
186
+ end
187
+ assert_match(/already has a name column/, error.message)
188
+ end
189
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration_test_helper"
4
+
5
+ # The dev-only schema-change GUI (InlineForms::SchemaController): new -> preview
6
+ # (no migration) -> apply (runs the addto generator, no db:migrate). Apply is
7
+ # exercised with a RECORDING executor so the suite never mutates the dummy tree.
8
+ class SchemaEditTest < InlineFormsIntegrationTestCase
9
+ def teardown
10
+ InlineForms::SchemaController.generator_executor = nil
11
+ InlineForms::SchemaController.label_writer = nil
12
+ super
13
+ end
14
+
15
+ test "new renders the form with supported elements" do
16
+ get inline_forms_schema_new_path
17
+ assert_response :success
18
+ assert_includes response.body, "Model"
19
+ assert_includes response.body, "Form element"
20
+ assert_includes response.body, "text_field"
21
+ assert_includes response.body, "Preview"
22
+ # data-turbo opt-out so the standalone utility navigates on POST
23
+ assert_includes response.body, %(data-turbo="false")
24
+ end
25
+
26
+ test "info is not offered and is rejected; header is not a scalar field" do
27
+ refute_includes InlineForms::SchemaPreview.supported_form_elements, :header
28
+ refute_includes InlineForms::SchemaPreview.supported_form_elements, :info
29
+
30
+ post inline_forms_schema_preview_path,
31
+ params: { model_name: "Widget", attribute: "meta", form_element: "info" }
32
+ assert_response :success
33
+ assert_includes response.body, "Unsupported form element"
34
+ end
35
+
36
+ test "header preview adds a no-column row (no migration, no widget)" do
37
+ post inline_forms_schema_preview_path,
38
+ params: { model_name: "Widget", attribute: "section_title",
39
+ form_element: "header", after: "name", label: "Section" }
40
+ assert_response :success
41
+ assert_includes response.body, "no column, no migration"
42
+ assert_includes response.body, "section_title (new)"
43
+ # header shows its label, not an input widget
44
+ assert_includes response.body, "Section"
45
+ refute_includes response.body, %(name="section_title")
46
+ refute_includes Widget.column_names, "section_title"
47
+ end
48
+
49
+ test "header apply invokes the generator without a column (no migration)" do
50
+ recorded = []
51
+ InlineForms::SchemaController.generator_executor = ->(args, _root) { recorded << args }
52
+
53
+ post inline_forms_schema_path,
54
+ params: { model_name: "Widget", attribute: "section_title", form_element: "header" }
55
+ assert_response :success
56
+ assert_includes response.body, "header"
57
+ assert_includes response.body, "no migration"
58
+ assert_equal [ "Widget", "section_title:header" ], recorded.first
59
+ end
60
+
61
+ test "a label is written through the injected label writer" do
62
+ recorded = []
63
+ InlineForms::SchemaController.generator_executor = ->(_args, _root) { }
64
+ InlineForms::SchemaController.label_writer = ->(**kw) { recorded << kw; "/tmp/inline_forms_labels.en.yml" }
65
+
66
+ post inline_forms_schema_path,
67
+ params: { model_name: "Widget", attribute: "section_title", form_element: "header",
68
+ label: "Section", locale: "en" }
69
+ assert_response :success
70
+ assert_includes response.body, "Wrote label"
71
+ assert_equal 1, recorded.size
72
+ assert_equal :section_title, recorded.first[:attribute]
73
+ assert_equal "Section", recorded.first[:label]
74
+ assert_equal "en", recorded.first[:locale]
75
+ end
76
+
77
+ test "preview shows the field at the right position with no migration" do
78
+ post inline_forms_schema_preview_path,
79
+ params: { model_name: "Widget", attribute: "internal_note",
80
+ form_element: "text_field", after: "name" }
81
+ assert_response :success
82
+ assert_includes response.body, "internal_note (new)"
83
+ # rendered live widget for the new field
84
+ assert_includes response.body, %(name="internal_note")
85
+ # placement: internal_note appears after name in the rendered order
86
+ assert response.body.index("name (new)").nil?
87
+ assert response.body.index(">name<") < response.body.index("internal_note (new)")
88
+
89
+ # No schema change happened.
90
+ refute_includes Widget.column_names, "internal_note"
91
+ assert_empty Dir.glob(Rails.root.join("db/migrate/*_inline_forms_add_to_*.rb"))
92
+ end
93
+
94
+ test "an unsupported (non-scalar) element is rejected with an error" do
95
+ post inline_forms_schema_preview_path,
96
+ params: { model_name: "Widget", attribute: "vendor", form_element: "dropdown" }
97
+ assert_response :success
98
+ assert_includes response.body, "Unsupported form element"
99
+ end
100
+
101
+ test "a value-bearing element previews with a placeholder values hash (no raise)" do
102
+ post inline_forms_schema_preview_path,
103
+ params: { model_name: "Widget", attribute: "rank",
104
+ form_element: "dropdown_with_values_with_stars", after: "name" }
105
+ assert_response :success
106
+ assert_includes response.body, "rank (new)"
107
+ assert_includes response.body, "placeholder values hash"
108
+ end
109
+
110
+ test "invalid input re-renders the form with an error" do
111
+ post inline_forms_schema_preview_path,
112
+ params: { model_name: "Widget", attribute: "Bad Name", form_element: "text_field" }
113
+ assert_response :success
114
+ assert_includes response.body, "lowercase letters"
115
+ end
116
+
117
+ test "apply invokes the generator (no db:migrate) via the injected executor" do
118
+ recorded = []
119
+ InlineForms::SchemaController.generator_executor = ->(args, _root) { recorded << args }
120
+
121
+ post inline_forms_schema_path,
122
+ params: { model_name: "Widget", attribute: "internal_note",
123
+ form_element: "text_field", after: "name" }
124
+ assert_response :success
125
+ assert_includes response.body, "Applied"
126
+ assert_includes response.body, "db:migrate" # the "you run this" instruction
127
+ assert_equal 1, recorded.size
128
+ assert_equal [ "Widget", "internal_note:text_field", "--after=name" ], recorded.first
129
+ # The generator never wrote a migration (recorder), and we never migrated.
130
+ refute_includes Widget.column_names, "internal_note"
131
+ end
132
+
133
+ test "reachable only outside production" do
134
+ original = Rails.env
135
+ Rails.env = ActiveSupport::StringInquirer.new("production")
136
+ get inline_forms_schema_new_path
137
+ assert_response :not_found
138
+ ensure
139
+ Rails.env = original
140
+ end
141
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration_test_helper"
4
+
5
+ # Cheap preview of a SchemaIntent against the dummy app: a virtual typed
6
+ # attribute on a throwaway subclass + an AttributeList with the proposed row
7
+ # spliced in — no migration, no column, real class/table untouched.
8
+ class SchemaPreviewTest < InlineFormsIntegrationTestCase
9
+ def scalar_intent(**overrides)
10
+ InlineForms::SchemaIntent.new(
11
+ **{ model_name: "Widget", attribute: :internal_note, form_element: :text_field }.merge(overrides)
12
+ )
13
+ end
14
+
15
+ test "supported? is true for scalar elements, false for relations/uploaders/virtuals" do
16
+ assert InlineForms::SchemaPreview.supported?(scalar_intent(form_element: :text_field))
17
+ assert InlineForms::SchemaPreview.supported?(scalar_intent(form_element: :integer_field))
18
+ assert InlineForms::SchemaPreview.supported?(scalar_intent(form_element: :date_select))
19
+ assert InlineForms::SchemaPreview.supported?(scalar_intent(form_element: :check_box))
20
+ refute InlineForms::SchemaPreview.supported?(scalar_intent(form_element: :dropdown))
21
+ refute InlineForms::SchemaPreview.supported?(scalar_intent(form_element: :rich_text))
22
+ refute InlineForms::SchemaPreview.supported?(scalar_intent(form_element: :image_field))
23
+ refute InlineForms::SchemaPreview.supported?(scalar_intent(form_element: :money_field))
24
+ end
25
+
26
+ test "preview object carries the proposed attribute as a virtual, no column" do
27
+ object, = InlineForms::SchemaPreview.build(scalar_intent)
28
+
29
+ assert object.respond_to?(:internal_note), "preview should expose the virtual attribute"
30
+ assert_nil object.internal_note, "virtual attribute defaults to nil with no column"
31
+ object.internal_note = "boiler serviced"
32
+ assert_equal "boiler serviced", object.internal_note, "virtual attribute is writable in memory"
33
+
34
+ # The real class/table are untouched.
35
+ refute_includes Widget.column_names, "internal_note"
36
+ assert_equal "Widget", object.class.name, "preview masquerades as the base class"
37
+ end
38
+
39
+ test "preview attribute_list splices the row at --after" do
40
+ _, list = InlineForms::SchemaPreview.build(scalar_intent(after: :name))
41
+ assert_kind_of InlineForms::AttributeList, list
42
+ assert_equal :internal_note, list.names[list.index_of(:name) + 1]
43
+ # original rows are preserved
44
+ assert list.include_attribute?(:priority)
45
+ end
46
+
47
+ test "preview attribute_list appends when no anchor given" do
48
+ _, list = InlineForms::SchemaPreview.build(scalar_intent)
49
+ assert_equal :internal_note, list.names.last
50
+ end
51
+
52
+ test "preview seeds existing attributes from a base record" do
53
+ widget = Widget.create!(name: "Base", priority: 2)
54
+ object, = InlineForms::SchemaPreview.build(scalar_intent, widget)
55
+ assert_equal "Base", object.name, "existing data is carried into the preview"
56
+ assert_nil object.internal_note
57
+ end
58
+
59
+ test "building a preview does not add a column to the real table" do
60
+ before = Widget.column_names.dup
61
+ InlineForms::SchemaPreview.build(scalar_intent(after: :name))
62
+ Widget.reset_column_information
63
+ assert_equal before, Widget.column_names
64
+ end
65
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Boots the test/dummy host app for the engine's integration tests. Fast path
4
+ # (seconds) next to the full release gate (build gems -> inline_forms create
5
+ # MyApp --example -> rails test); catches controller/view/Turbo regressions
6
+ # without an install cycle. See stuff/prompt/test-the-example-app.md for the
7
+ # release gate, which remains authoritative.
8
+ ENV["RAILS_ENV"] = "test"
9
+
10
+ require_relative "dummy/config/environment"
11
+ require "rails/test_help"
12
+ require "minitest/autorun"
13
+
14
+ # In-memory SQLite: define the schema on every boot (maintain_test_schema is
15
+ # off; there are no migration files to run).
16
+ ActiveRecord::Schema.verbose = false
17
+ ActiveRecord::Schema.define do
18
+ create_table :widgets, force: true do |t|
19
+ t.string :name
20
+ t.text :notes
21
+ t.date :released_on
22
+ t.time :meeting_at
23
+ t.date :start_month
24
+ t.integer :priority
25
+ t.boolean :active
26
+ t.integer :kind_id
27
+ t.string :kind_other
28
+ t.integer :rating
29
+ t.string :manual
30
+ t.string :accent
31
+ t.timestamps
32
+ end
33
+
34
+ create_table :kinds, force: true do |t|
35
+ t.string :name
36
+ t.timestamps
37
+ end
38
+
39
+ # Machine has_many :parts with an :associated panel (open-after-create).
40
+ create_table :machines, force: true do |t|
41
+ t.string :name
42
+ t.timestamps
43
+ end
44
+
45
+ create_table :parts, force: true do |t|
46
+ t.string :name
47
+ t.integer :machine_id
48
+ t.timestamps
49
+ end
50
+ add_index :parts, :machine_id
51
+
52
+ # Gizmo intentionally has NO `pending_note` column: its attribute_list
53
+ # references one, modeling the inline_forms_addto pre-migrate window that
54
+ # InlineForms.attribute_pending_migration? gates. See
55
+ # test/integration/pending_migration_gate_test.rb.
56
+ create_table :gizmos, force: true do |t|
57
+ t.string :name
58
+ t.timestamps
59
+ end
60
+
61
+ # Schema-GUI batch pipeline (inline_forms_schema_edit). Mirrors the gem's
62
+ # install-generator migration.
63
+ create_table :inline_forms_schema_batches, force: true do |t|
64
+ t.string :status, null: false, default: "draft"
65
+ t.string :requested_by
66
+ t.datetime :submitted_at
67
+ t.datetime :window_at
68
+ t.datetime :applied_at
69
+ t.string :git_sha
70
+ t.string :content_digest
71
+ t.text :error
72
+ t.timestamps
73
+ end
74
+ add_index :inline_forms_schema_batches, :status
75
+
76
+ create_table :inline_forms_schema_intents, force: true do |t|
77
+ t.references :batch, null: false, index: true
78
+ t.string :target_model, null: false
79
+ t.string :attr_name, null: false
80
+ t.string :form_element, null: false
81
+ t.string :after_attr
82
+ t.string :before_attr
83
+ t.string :label
84
+ t.string :locale
85
+ t.integer :position
86
+ t.string :migration_version
87
+ t.timestamps
88
+ end
89
+
90
+ # PaperTrail (paper_trail 17.x shape).
91
+ create_table :versions, force: true do |t|
92
+ t.string :item_type, null: false
93
+ t.bigint :item_id, null: false
94
+ t.string :event, null: false
95
+ t.string :whodunnit
96
+ t.text :object, limit: 1_073_741_823
97
+ t.text :object_changes, limit: 1_073_741_823
98
+ t.datetime :created_at
99
+ end
100
+ add_index :versions, [ :item_type, :item_id ]
101
+ end
102
+
103
+ class InlineFormsIntegrationTestCase < ActionDispatch::IntegrationTest
104
+ # The inline UI drives every request through a <turbo-frame>; the frame
105
+ # layout (turbo_rails/frame) also spares the dummy the full chrome (header
106
+ # needs current_user, assets, ...).
107
+ def frame_headers(frame_id)
108
+ { "Turbo-Frame" => frame_id, "Accept" => "text/html" }
109
+ end
110
+
111
+ setup do
112
+ Widget.delete_all
113
+ Kind.delete_all
114
+ Part.delete_all
115
+ Machine.delete_all
116
+ Gizmo.delete_all
117
+ PaperTrail::Version.delete_all
118
+ InlineForms::SchemaIntentRecord.delete_all
119
+ InlineForms::SchemaBatch.delete_all
120
+ end
121
+ end
@@ -20,7 +20,7 @@ class PlainTextConfigurationTest < Minitest::Test
20
20
 
21
21
  def inline_forms_attribute_list
22
22
  [
23
- [:description, :plain_text]
23
+ [ :description, :plain_text ]
24
24
  ]
25
25
  end
26
26
  end
@@ -40,7 +40,7 @@ class PlainTextConfigurationTest < Minitest::Test
40
40
 
41
41
  def inline_forms_attribute_list
42
42
  [
43
- [:description, :plain_text]
43
+ [ :description, :plain_text ]
44
44
  ]
45
45
  end
46
46
  end
@@ -56,7 +56,7 @@ class PlainTextConfigurationTest < Minitest::Test
56
56
 
57
57
  def inline_forms_attribute_list
58
58
  [
59
- [:description, :rich_text]
59
+ [ :description, :rich_text ]
60
60
  ]
61
61
  end
62
62
  end