admin_suite 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +134 -7
- data/app/assets/vendor/chart.umd.min.js +14 -0
- data/app/controllers/admin_suite/resources_controller.rb +130 -5
- data/app/helpers/admin_suite/base_helper.rb +187 -10
- data/app/javascript/admin_suite_application.js +3 -0
- data/app/javascript/controllers/admin_suite/chart_controller.js +173 -0
- data/app/views/admin_suite/panels/_chart.html.erb +158 -18
- data/app/views/admin_suite/resources/index.html.erb +24 -5
- data/app/views/admin_suite/shared/_sidebar.html.erb +14 -8
- data/app/views/layouts/admin_suite/application.html.erb +6 -0
- data/config/routes.rb +3 -0
- data/lib/admin/base/filter_builder.rb +48 -5
- data/lib/admin/base/resource.rb +43 -7
- data/lib/admin_suite/configuration.rb +10 -6
- data/lib/admin_suite/engine.rb +1 -1
- data/lib/admin_suite/legacy_custom_renderer_procs.rb +1 -1
- data/lib/admin_suite/renderer_registry.rb +11 -0
- data/lib/admin_suite/renderers/legacy_gleania.rb +7 -5
- data/lib/admin_suite/ui/dashboard_definition.rb +6 -0
- data/lib/admin_suite/version.rb +1 -1
- data/lib/admin_suite.rb +13 -2
- data/test/integration/association_linking_test.rb +292 -0
- data/test/integration/chart_panel_test.rb +491 -0
- data/test/integration/dashboard_test.rb +9 -2
- data/test/integration/index_table_test.rb +289 -0
- data/test/integration/navigation_sections_test.rb +21 -0
- data/test/integration/searchable_select_search_test.rb +368 -0
- data/test/integration/show_hide_blank_test.rb +195 -0
- data/test/integration/toggle_test.rb +106 -0
- data/test/lib/definition_loader_test.rb +12 -0
- data/test/lib/form_field_renderer_test.rb +83 -11
- data/test/lib/format_table_cell_test.rb +49 -0
- data/test/lib/index_includes_test.rb +223 -0
- data/test/lib/legacy_renderer_deprecation_test.rb +8 -1
- data/test/lib/renderer_test.rb +23 -7
- data/test/lib/resource_exportable_deprecation_test.rb +10 -2
- data/test/test_helper.rb +47 -8
- metadata +35 -5
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
# Exercises `hide_blank:` on `panel`/`section` `fields:` rows. Mirrors the
|
|
6
|
+
# `ReadOnlyResourceFixtures::Widget` shape (test/integration/read_only_resource_test.rb)
|
|
7
|
+
# so the show route resolves through the ordinary numeric-id `klass.find` path,
|
|
8
|
+
# not the slug/uuid/token fallback.
|
|
9
|
+
module HideBlankFixtures
|
|
10
|
+
class Widget
|
|
11
|
+
extend ActiveModel::Naming
|
|
12
|
+
|
|
13
|
+
attr_reader :id
|
|
14
|
+
|
|
15
|
+
def initialize(id: 1)
|
|
16
|
+
@id = id
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.all
|
|
20
|
+
[ new ]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.column_names
|
|
24
|
+
%w[id]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.primary_key
|
|
28
|
+
"id"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.columns_hash
|
|
32
|
+
{ "id" => Struct.new(:type).new(:integer) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.find(id)
|
|
36
|
+
raise ActiveRecord::RecordNotFound unless id.to_s == "1"
|
|
37
|
+
|
|
38
|
+
new
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_param
|
|
42
|
+
id.to_s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def attributes
|
|
46
|
+
{ "id" => id }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Non-blank control value -- always kept, hide_blank or not.
|
|
50
|
+
def title
|
|
51
|
+
"Widget One"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def blank_string
|
|
55
|
+
""
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def whitespace_string
|
|
59
|
+
" "
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def nil_field
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def empty_array
|
|
67
|
+
[]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def empty_hash
|
|
71
|
+
{}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def false_flag
|
|
75
|
+
false
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def zero_count
|
|
79
|
+
0
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def zero_float
|
|
83
|
+
0.0
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
module Admin
|
|
89
|
+
module Resources
|
|
90
|
+
class HideBlankWidgetResource < Admin::Base::Resource
|
|
91
|
+
model HideBlankFixtures::Widget
|
|
92
|
+
portal :ops
|
|
93
|
+
section :observability
|
|
94
|
+
|
|
95
|
+
FIELDS = %i[
|
|
96
|
+
title blank_string whitespace_string nil_field
|
|
97
|
+
empty_array empty_hash false_flag zero_count zero_float
|
|
98
|
+
].freeze
|
|
99
|
+
|
|
100
|
+
show do
|
|
101
|
+
sidebar do
|
|
102
|
+
panel :sidebar_hidden, title: "Sidebar hidden", fields: FIELDS, hide_blank: true
|
|
103
|
+
panel :sidebar_default, title: "Sidebar default", fields: FIELDS
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
section :main_hidden, title: "Main hidden", fields: FIELDS, hide_blank: true
|
|
107
|
+
section :main_default, title: "Main default", fields: FIELDS
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
module AdminSuite
|
|
114
|
+
class ShowHideBlankTest < ActionDispatch::IntegrationTest
|
|
115
|
+
PATH = "/internal/admin_suite/ops/hide_blank_widgets/1"
|
|
116
|
+
|
|
117
|
+
test "hide_blank: true hides nil, empty string, empty array, and empty hash" do
|
|
118
|
+
get PATH
|
|
119
|
+
assert_response :success
|
|
120
|
+
|
|
121
|
+
hidden_section = response.body[/Main hidden.*?(?=Main default)/m]
|
|
122
|
+
refute_nil hidden_section, "expected to find the 'Main hidden' panel before 'Main default' in the body"
|
|
123
|
+
|
|
124
|
+
refute_includes hidden_section, "Nil field"
|
|
125
|
+
refute_includes hidden_section, "Blank string"
|
|
126
|
+
refute_includes hidden_section, "Empty array"
|
|
127
|
+
refute_includes hidden_section, "Empty hash"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
test "hide_blank: true keeps false, 0, 0.0, and whitespace-only strings" do
|
|
131
|
+
get PATH
|
|
132
|
+
assert_response :success
|
|
133
|
+
|
|
134
|
+
hidden_section = response.body[/Main hidden.*?(?=Main default)/m]
|
|
135
|
+
refute_nil hidden_section
|
|
136
|
+
|
|
137
|
+
assert_includes hidden_section, "False flag"
|
|
138
|
+
assert_includes hidden_section, "Zero count"
|
|
139
|
+
assert_includes hidden_section, "Zero float"
|
|
140
|
+
assert_includes hidden_section, "Whitespace string"
|
|
141
|
+
# And the boolean must still render as the meaningful "No" state, not
|
|
142
|
+
# silently vanish.
|
|
143
|
+
assert_includes hidden_section, "No"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
test "hide_blank: true keeps genuinely non-blank fields" do
|
|
147
|
+
get PATH
|
|
148
|
+
assert_response :success
|
|
149
|
+
|
|
150
|
+
hidden_section = response.body[/Main hidden.*?(?=Main default)/m]
|
|
151
|
+
assert_includes hidden_section, "Title"
|
|
152
|
+
assert_includes hidden_section, "Widget One"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
test "default (no hide_blank option) hides nothing -- every label row still renders" do
|
|
156
|
+
get PATH
|
|
157
|
+
assert_response :success
|
|
158
|
+
|
|
159
|
+
default_section = response.body[/Main default.*?(?=<\/html>)/m]
|
|
160
|
+
refute_nil default_section
|
|
161
|
+
|
|
162
|
+
%w[Title Blank\ string Whitespace\ string Nil\ field Empty\ array Empty\ hash False\ flag Zero\ count Zero\ float].each do |label|
|
|
163
|
+
assert_includes default_section, label, "expected label '#{label}' to render in the default (no hide_blank) panel"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
test "hide_blank: true on a sidebar panel hides the same blank fields as main" do
|
|
168
|
+
get PATH
|
|
169
|
+
assert_response :success
|
|
170
|
+
|
|
171
|
+
sidebar_hidden = response.body[/Sidebar hidden.*?(?=Sidebar default)/m]
|
|
172
|
+
refute_nil sidebar_hidden
|
|
173
|
+
|
|
174
|
+
refute_includes sidebar_hidden, "Nil field"
|
|
175
|
+
refute_includes sidebar_hidden, "Blank string"
|
|
176
|
+
refute_includes sidebar_hidden, "Empty array"
|
|
177
|
+
refute_includes sidebar_hidden, "Empty hash"
|
|
178
|
+
assert_includes sidebar_hidden, "False flag"
|
|
179
|
+
assert_includes sidebar_hidden, "Zero count"
|
|
180
|
+
assert_includes sidebar_hidden, "Zero float"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
test "default sidebar panel (no hide_blank) hides nothing" do
|
|
184
|
+
get PATH
|
|
185
|
+
assert_response :success
|
|
186
|
+
|
|
187
|
+
sidebar_default = response.body[/Sidebar default.*?(?=<div class="bg-white)/m] || response.body[/Sidebar default.*\z/m]
|
|
188
|
+
refute_nil sidebar_default
|
|
189
|
+
|
|
190
|
+
%w[Nil\ field Blank\ string Empty\ array Empty\ hash].each do |label|
|
|
191
|
+
assert_includes sidebar_default, label
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
# Final review Finding 3: the gem hard-depends on turbo-rails (`turbo_stream`
|
|
6
|
+
# in `ResourcesController#toggle`, `turbo_frame_tag` throughout the resource
|
|
7
|
+
# views) but never declared it in the gemspec. Before that dependency is
|
|
8
|
+
# declared, this test exercises the exact path the review flagged as broken
|
|
9
|
+
# in a turbo-less host: `format.turbo_stream` inside `respond_to` raises the
|
|
10
|
+
# moment it's evaluated, because the `:turbo_stream` MIME type is unregistered
|
|
11
|
+
# without turbo-rails loaded. See `test/test_helper.rb`'s (now removed)
|
|
12
|
+
# `TurboFrameTestHelper`, which only ever patched `turbo_frame_tag` -- it
|
|
13
|
+
# never made `turbo_stream`/the MIME type real, so this branch was untestable
|
|
14
|
+
# until the real dependency was declared.
|
|
15
|
+
module ToggleFixtures
|
|
16
|
+
class Widget
|
|
17
|
+
extend ActiveModel::Naming
|
|
18
|
+
|
|
19
|
+
attr_reader :id
|
|
20
|
+
attr_accessor :active
|
|
21
|
+
|
|
22
|
+
def initialize(id: 1, active: false)
|
|
23
|
+
@id = id
|
|
24
|
+
@active = active
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.all
|
|
28
|
+
ReadOnlyResourceFixtures::Relation.new([ new ])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.column_names
|
|
32
|
+
%w[id active]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.primary_key
|
|
36
|
+
"id"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.columns_hash
|
|
40
|
+
{ "id" => Struct.new(:type).new(:integer) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.find(id)
|
|
44
|
+
return new(id: 1, active: false) if id.to_s == "1"
|
|
45
|
+
|
|
46
|
+
raise ActiveRecord::RecordNotFound
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def to_param
|
|
50
|
+
id.to_s
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# `dom_id` (via `ActionView::RecordIdentifier#record_key_for_dom_id`)
|
|
54
|
+
# calls `to_key` on the record -- `extend ActiveModel::Naming` alone
|
|
55
|
+
# only supplies `model_name`, not `to_key`.
|
|
56
|
+
def to_key
|
|
57
|
+
[ id ]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def update!(attrs)
|
|
61
|
+
attrs.each { |key, value| public_send("#{key}=", value) }
|
|
62
|
+
true
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
module Admin
|
|
68
|
+
module Resources
|
|
69
|
+
class ToggleWidgetResource < Admin::Base::Resource
|
|
70
|
+
model ToggleFixtures::Widget
|
|
71
|
+
portal :ops
|
|
72
|
+
section :observability
|
|
73
|
+
|
|
74
|
+
index do
|
|
75
|
+
columns do
|
|
76
|
+
column :active, type: :toggle
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module AdminSuite
|
|
84
|
+
class ToggleTest < ActionDispatch::IntegrationTest
|
|
85
|
+
include ActionView::RecordIdentifier
|
|
86
|
+
|
|
87
|
+
BASE_PATH = "/internal/admin_suite/ops/toggle_widgets"
|
|
88
|
+
|
|
89
|
+
test "toggle's turbo_stream branch replaces the toggle cell with the flipped state" do
|
|
90
|
+
post "#{BASE_PATH}/1/toggle",
|
|
91
|
+
params: { field: "active" },
|
|
92
|
+
headers: { "Accept" => "text/vnd.turbo-stream.html" }
|
|
93
|
+
|
|
94
|
+
assert_response :success
|
|
95
|
+
assert_equal "text/vnd.turbo-stream.html", response.media_type
|
|
96
|
+
|
|
97
|
+
target = dom_id(ToggleFixtures::Widget.new(id: 1), "active_toggle")
|
|
98
|
+
assert_includes response.body, "<turbo-stream"
|
|
99
|
+
assert_includes response.body, %(action="replace")
|
|
100
|
+
assert_includes response.body, %(target="#{target}")
|
|
101
|
+
# The record started with `active: false`; the toggle flips it to
|
|
102
|
+
# `true` before rendering, so the replaced cell must reflect "on".
|
|
103
|
+
assert_includes response.body, "is-on"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -41,6 +41,18 @@ module AdminSuite
|
|
|
41
41
|
before.each_value { |definition| AdminSuite::PortalRegistry.register(definition) }
|
|
42
42
|
end
|
|
43
43
|
else
|
|
44
|
+
# :dashboards/:actions have no accumulating registry to snapshot --
|
|
45
|
+
# unlike :resources/:portals (which pick up real fixtures registered
|
|
46
|
+
# by *other* test files at load time), reset! for these two kinds
|
|
47
|
+
# only clears a single flag/definition object
|
|
48
|
+
# (`root_dashboard_loaded`/`root_dashboard_definition`,
|
|
49
|
+
# `handlers_loaded`) that is entirely local to whichever test set it,
|
|
50
|
+
# via its own `with_globs`/`with_dashboard`-style ensure block. So
|
|
51
|
+
# there is nothing cross-test left to preserve, and "restore" here
|
|
52
|
+
# deliberately degrades to a second `reset!` call rather than a real
|
|
53
|
+
# snapshot+restore -- do not read this branch as returning a
|
|
54
|
+
# value-preserving restore the way the :resources/:portals branches
|
|
55
|
+
# above do.
|
|
44
56
|
-> { AdminSuite::DefinitionLoader.reset!(kind) }
|
|
45
57
|
end
|
|
46
58
|
end
|
|
@@ -6,31 +6,52 @@ module AdminSuite
|
|
|
6
6
|
class FormFieldRendererTest < ActionView::TestCase
|
|
7
7
|
include AdminSuite::BaseHelper
|
|
8
8
|
|
|
9
|
-
Record = Struct.new(:name, :body, :enabled) do
|
|
9
|
+
Record = Struct.new(:name, :body, :enabled, :error_messages) do
|
|
10
|
+
# Both a class-level and an instance-level `model_name` are needed:
|
|
11
|
+
# `render_toggle_field`/`render_searchable_select`/`render_multi_select`
|
|
12
|
+
# all call `resource.class.model_name.param_key` (class-level), while
|
|
13
|
+
# `form_with` itself wants the instance-level accessor. (`extend
|
|
14
|
+
# ActiveModel::Naming` would give both via its own delegation, but
|
|
15
|
+
# then redefining `model_name` below to force the short "Record" name
|
|
16
|
+
# -- rather than the fully-qualified nested constant name -- clashes
|
|
17
|
+
# with that delegation and triggers a "method redefined" warning.
|
|
18
|
+
# Defining both explicitly avoids it.) The pre-existing instance-level
|
|
19
|
+
# `model_name` never covered the class-level call site because no test
|
|
20
|
+
# here exercised `:toggle`/`:searchable_select`/`:multi_select` before.
|
|
21
|
+
def self.model_name
|
|
22
|
+
@model_name ||= ActiveModel::Name.new(self, nil, "Record")
|
|
23
|
+
end
|
|
24
|
+
|
|
10
25
|
def to_model = self
|
|
11
|
-
def model_name =
|
|
26
|
+
def model_name = self.class.model_name
|
|
12
27
|
def persisted? = false
|
|
13
28
|
def to_key = nil
|
|
14
29
|
|
|
15
30
|
# render_form_field unconditionally calls `resource.errors[field.name]`
|
|
16
31
|
# for every field type (to add the error border class / message), so
|
|
17
32
|
# the double needs an `errors` object supporting `[]` -> Array-like
|
|
18
|
-
# (`.any?`, `.first`).
|
|
19
|
-
# error, so a Hash defaulting to `[]` is sufficient
|
|
20
|
-
|
|
33
|
+
# (`.any?`, `.first`). Most tests here don't exercise an actual
|
|
34
|
+
# validation error, so a Hash defaulting to `[]` is sufficient; tests
|
|
35
|
+
# that do pass `error_messages` (a plain Hash of field name -> Array of
|
|
36
|
+
# messages) to get real per-field errors without losing the default.
|
|
37
|
+
def errors
|
|
38
|
+
Hash.new([]).merge(error_messages || {})
|
|
39
|
+
end
|
|
21
40
|
end
|
|
22
41
|
|
|
23
|
-
def field(name, type)
|
|
42
|
+
def field(name, type, **overrides)
|
|
24
43
|
Admin::Base::Resource::FieldDefinition.new(
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
{
|
|
45
|
+
name: name, type: type, required: false, label: name.to_s.humanize,
|
|
46
|
+
readonly: false, multiple: false, creatable: false, preview: true
|
|
47
|
+
}.merge(overrides)
|
|
27
48
|
)
|
|
28
49
|
end
|
|
29
50
|
|
|
30
|
-
def render_field(type, name: :name)
|
|
31
|
-
record = Record.new("x", "y", true)
|
|
51
|
+
def render_field(type, name: :name, error_messages: nil, **field_overrides)
|
|
52
|
+
record = Record.new("x", "y", true, error_messages)
|
|
32
53
|
html = nil
|
|
33
|
-
form_with(model: record, url: "/", scope: :record) { |f| html = render_form_field(f, field(name, type), record) }
|
|
54
|
+
form_with(model: record, url: "/", scope: :record) { |f| html = render_form_field(f, field(name, type, **field_overrides), record) }
|
|
34
55
|
html.to_s
|
|
35
56
|
end
|
|
36
57
|
|
|
@@ -60,5 +81,56 @@ module AdminSuite
|
|
|
60
81
|
test "the label is rendered for every field" do
|
|
61
82
|
assert_includes render_field(:text), "Name"
|
|
62
83
|
end
|
|
84
|
+
|
|
85
|
+
test "a field with a validation error renders the red border and error message" do
|
|
86
|
+
html = render_field(:text, error_messages: { name: [ "is invalid" ] })
|
|
87
|
+
assert_includes html, "border-red-500"
|
|
88
|
+
assert_includes html, "is invalid"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The full set of field types `FieldRendererRegistry` is expected to
|
|
92
|
+
# have registered (see `lib/admin_suite/ui/field_renderer_registry.rb`).
|
|
93
|
+
# Pinned explicitly here, NOT derived from the registry itself: an
|
|
94
|
+
# earlier version of this test iterated `handlers.keys` directly, which
|
|
95
|
+
# cannot detect a deletion -- a deleted key simply stops appearing in
|
|
96
|
+
# the iteration, so the loop body never runs for it and the test still
|
|
97
|
+
# passes with one fewer assertion (verified by deleting the `:toggle`
|
|
98
|
+
# registration: 42 assertions became 41, 0 failures). The
|
|
99
|
+
# size-and-membership assertion in the test below is what actually
|
|
100
|
+
# catches that; a self-referential "iterate the thing you're testing"
|
|
101
|
+
# loop cannot.
|
|
102
|
+
EXPECTED_FIELD_TYPES = %i[
|
|
103
|
+
textarea url email number toggle label select searchable_select
|
|
104
|
+
dependent_select multi_select tags image attachment trix rich_text
|
|
105
|
+
markdown file datetime date time json code text string
|
|
106
|
+
].freeze
|
|
107
|
+
|
|
108
|
+
# :trix/:rich_text both call `f.rich_text_area`, which only exists once
|
|
109
|
+
# ActionText's FormBuilder extension is loaded -- and per the plan's
|
|
110
|
+
# Test-harness fact #2, this dummy app is deliberately database-free and
|
|
111
|
+
# never requires `action_text/engine`. Confirmed by running the
|
|
112
|
+
# rendering test with them included: `NoMethodError: undefined method
|
|
113
|
+
# 'rich_text_area'`, not a bug in FieldRendererRegistry. There is no way
|
|
114
|
+
# to pin these two types in this harness without pulling in ActionText
|
|
115
|
+
# (and, transitively, ActiveRecord) — out of scope here. Named
|
|
116
|
+
# explicitly (not an implicit skip) so `EXPECTED_FIELD_TYPES.size` and
|
|
117
|
+
# the number of types actually exercised below both stay visible and
|
|
118
|
+
# add up: 24 expected, 2 excluded, 22 rendered.
|
|
119
|
+
UNTESTABLE_WITHOUT_ACTION_TEXT = %i[trix rich_text].freeze
|
|
120
|
+
|
|
121
|
+
test "the registry has exactly the expected set of field types, no more, no fewer" do
|
|
122
|
+
assert_equal EXPECTED_FIELD_TYPES.sort, AdminSuite::UI::FieldRendererRegistry.handlers.keys.sort
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
test "every expected field type except the ActionText-only ones renders non-empty markup" do
|
|
126
|
+
sample_collection = [ [ "One", 1 ], [ "Two", 2 ] ]
|
|
127
|
+
needs_collection = %i[select searchable_select dependent_select multi_select tags]
|
|
128
|
+
|
|
129
|
+
(EXPECTED_FIELD_TYPES - UNTESTABLE_WITHOUT_ACTION_TEXT).each do |type|
|
|
130
|
+
overrides = needs_collection.include?(type) ? { collection: sample_collection } : {}
|
|
131
|
+
html = render_field(type, **overrides)
|
|
132
|
+
assert html.present?, "expected #{type.inspect} to render non-empty markup"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
63
135
|
end
|
|
64
136
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
module AdminSuite
|
|
6
|
+
# `format_table_cell` had no direct unit test anywhere in the suite before
|
|
7
|
+
# this file -- it was only exercised indirectly through
|
|
8
|
+
# `association_linking_test.rb`'s AR-focused fixtures. Covers the branches
|
|
9
|
+
# not already pinned there, and specifically the Integer/Float/BigDecimal
|
|
10
|
+
# delimiting added to match `format_show_value` (see
|
|
11
|
+
# `ShowValueFormatterTest#"integers and floats render delimited"`).
|
|
12
|
+
class FormatTableCellTest < ActionView::TestCase
|
|
13
|
+
include AdminSuite::BaseHelper
|
|
14
|
+
|
|
15
|
+
test "nil renders the em dash placeholder" do
|
|
16
|
+
assert_equal "—", format_table_cell(nil)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
test "booleans render Yes/No" do
|
|
20
|
+
assert_equal "Yes", format_table_cell(true)
|
|
21
|
+
assert_equal "No", format_table_cell(false)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
test "Time and DateTime render a short date-time" do
|
|
25
|
+
assert_equal "Jan 02, 03:04", format_table_cell(Time.utc(2026, 1, 2, 3, 4))
|
|
26
|
+
assert_equal "Jan 02, 03:04", format_table_cell(DateTime.new(2026, 1, 2, 3, 4))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
test "Date renders a short date" do
|
|
30
|
+
assert_equal "Jan 02, 2026", format_table_cell(Date.new(2026, 1, 2))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
test "integers and floats render delimited, matching format_show_value" do
|
|
34
|
+
assert_equal "1,234,567", format_table_cell(1_234_567)
|
|
35
|
+
assert_equal "1,234.5", format_table_cell(1234.5)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
test "BigDecimal renders delimited rather than in exponential notation" do
|
|
39
|
+
result = format_table_cell(BigDecimal("9876.5"))
|
|
40
|
+
assert_includes result, "9,876"
|
|
41
|
+
refute_includes result, "0.98765e4"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
test "plain strings are truncated at 50 characters" do
|
|
45
|
+
assert_equal "hello", format_table_cell("hello")
|
|
46
|
+
assert_equal ("x" * 47) + "...", format_table_cell("x" * 60)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|