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,491 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
|
|
6
|
+
module AdminSuite
|
|
7
|
+
class ChartPanelTest < ActionDispatch::IntegrationTest
|
|
8
|
+
# Renders a root dashboard from a temp file. Mirrors the recipe in
|
|
9
|
+
# dashboard_test.rb, which is the only reliable way to exercise panels.
|
|
10
|
+
def with_dashboard(body)
|
|
11
|
+
Dir.mktmpdir do |dir|
|
|
12
|
+
File.write(File.join(dir, "dashboard.rb"), body)
|
|
13
|
+
saved = AdminSuite.config.dashboard_globs
|
|
14
|
+
AdminSuite.config.dashboard_globs = [ File.join(dir, "*.rb") ]
|
|
15
|
+
AdminSuite.reset_root_dashboard!
|
|
16
|
+
AdminSuite::DefinitionLoader.reset!(:dashboards)
|
|
17
|
+
yield
|
|
18
|
+
ensure
|
|
19
|
+
AdminSuite.config.dashboard_globs = saved
|
|
20
|
+
AdminSuite.reset_root_dashboard!
|
|
21
|
+
AdminSuite::DefinitionLoader.reset!(:dashboards)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
test "a chart panel emits the chart controller with serialized series" do
|
|
26
|
+
with_dashboard(<<~RUBY) do
|
|
27
|
+
AdminSuite.root_dashboard do
|
|
28
|
+
row do
|
|
29
|
+
chart_panel "Daily Cost", data: -> { [ { label: "Mon", value: 3 }, { label: "Tue", value: 7 } ] }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
RUBY
|
|
33
|
+
get "/internal/admin_suite"
|
|
34
|
+
assert_response :success
|
|
35
|
+
assert_includes response.body, "admin-suite--chart"
|
|
36
|
+
assert_includes response.body, "Mon"
|
|
37
|
+
assert_includes response.body, "Tue"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
test "string-keyed chart data renders values, not blank bars" do
|
|
42
|
+
with_dashboard(<<~RUBY) do
|
|
43
|
+
AdminSuite.root_dashboard do
|
|
44
|
+
row do
|
|
45
|
+
chart_panel "From JSONB", data: -> { [ { "label" => "Alpha", "value" => 5 } ] }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
RUBY
|
|
49
|
+
get "/internal/admin_suite"
|
|
50
|
+
assert_response :success
|
|
51
|
+
assert_includes response.body, "Alpha"
|
|
52
|
+
document = Nokogiri::HTML(response.body)
|
|
53
|
+
# Scoped to the chart's own card: the layout's topbar partial also has
|
|
54
|
+
# h-16/h-full classes (unrelated to the chart), so an unscoped
|
|
55
|
+
# "[data-admin-suite--chart-target='bars'] > .h-full > div" selector
|
|
56
|
+
# is deliberately anchored under the chart's own card rather than
|
|
57
|
+
# matching against the whole page.
|
|
58
|
+
chart = document.at_xpath("//h3[normalize-space()='From JSONB']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
59
|
+
assert chart, "expected the chart panel card"
|
|
60
|
+
bar = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div").first
|
|
61
|
+
assert bar, "expected a rendered bar"
|
|
62
|
+
assert_equal "height: 100%", bar["style"], "string-keyed value must normalize like a symbol-keyed one"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
test "a raising data proc degrades to the empty state instead of a 500" do
|
|
67
|
+
with_dashboard(<<~RUBY) do
|
|
68
|
+
AdminSuite.root_dashboard do
|
|
69
|
+
row do
|
|
70
|
+
chart_panel "Broken", data: -> { raise "boom" }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
RUBY
|
|
74
|
+
get "/internal/admin_suite"
|
|
75
|
+
assert_response :success
|
|
76
|
+
assert_includes response.body, "Broken"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
test "a malformed row in an otherwise-good data array degrades instead of a 500" do
|
|
81
|
+
with_dashboard(<<~RUBY) do
|
|
82
|
+
AdminSuite.root_dashboard do
|
|
83
|
+
row do
|
|
84
|
+
chart_panel "Mixed", data: -> { [ { label: "ok", value: 3 }, 5, nil ] }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
RUBY
|
|
88
|
+
get "/internal/admin_suite"
|
|
89
|
+
assert_response :success
|
|
90
|
+
assert_includes response.body, "Mixed"
|
|
91
|
+
document = Nokogiri::HTML(response.body)
|
|
92
|
+
chart = document.at_xpath("//h3[normalize-space()='Mixed']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
93
|
+
assert chart, "expected the chart panel card"
|
|
94
|
+
# The good row still renders as a full-height bar; the junk rows
|
|
95
|
+
# (an Integer and nil) are silently dropped rather than raising.
|
|
96
|
+
assert_includes chart.text, "ok"
|
|
97
|
+
bars = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div")
|
|
98
|
+
assert_equal 1, bars.size
|
|
99
|
+
assert_equal "height: 100%", bars.first["style"]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
test "a Hash row with a non-numeric value degrades to a zero-height bar instead of a 500" do
|
|
104
|
+
with_dashboard(<<~RUBY) do
|
|
105
|
+
AdminSuite.root_dashboard do
|
|
106
|
+
row do
|
|
107
|
+
chart_panel "Junk Values", data: -> {
|
|
108
|
+
[
|
|
109
|
+
{ label: "bool", value: true },
|
|
110
|
+
{ label: "hash", value: {} },
|
|
111
|
+
{ label: "array", value: [ 1, 2 ] },
|
|
112
|
+
{ label: "good", value: 5 }
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
RUBY
|
|
118
|
+
get "/internal/admin_suite"
|
|
119
|
+
assert_response :success
|
|
120
|
+
assert_includes response.body, "Junk Values"
|
|
121
|
+
document = Nokogiri::HTML(response.body)
|
|
122
|
+
chart = document.at_xpath("//h3[normalize-space()='Junk Values']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
123
|
+
assert chart, "expected the chart panel card"
|
|
124
|
+
# Boolean/Hash/Array values used to raise via #to_f (e.g. `true.to_f`
|
|
125
|
+
# is a NoMethodError). They now coerce to 0 instead of 500ing, and
|
|
126
|
+
# the one well-formed row alongside them still renders at full height.
|
|
127
|
+
bars = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div")
|
|
128
|
+
assert_equal 4, bars.size
|
|
129
|
+
assert_equal [ "height: 0%", "height: 0%", "height: 0%", "height: 100%" ], bars.map { |bar| bar["style"] }
|
|
130
|
+
assert_includes chart.text, "good"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
test "a numeric-string value still displays without a trailing .0 in the tooltip" do
|
|
135
|
+
with_dashboard(<<~RUBY) do
|
|
136
|
+
AdminSuite.root_dashboard do
|
|
137
|
+
row do
|
|
138
|
+
chart_panel "String Value", data: -> { [ { label: "Str", value: "3" } ] }
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
RUBY
|
|
142
|
+
get "/internal/admin_suite"
|
|
143
|
+
assert_response :success
|
|
144
|
+
document = Nokogiri::HTML(response.body)
|
|
145
|
+
chart = document.at_xpath("//h3[normalize-space()='String Value']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
146
|
+
assert chart, "expected the chart panel card"
|
|
147
|
+
bar = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div").first
|
|
148
|
+
assert bar, "expected a rendered bar"
|
|
149
|
+
# Display keeps the original value ("3"), even though the height math
|
|
150
|
+
# underneath uses the coerced numeric (3.0) — no display regression
|
|
151
|
+
# from the total-coercion fix.
|
|
152
|
+
assert_equal "Str: 3", bar["title"]
|
|
153
|
+
assert_equal "height: 100%", bar["style"]
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
test "chart height defaults to a real chart size and is configurable" do
|
|
158
|
+
with_dashboard(<<~RUBY) do
|
|
159
|
+
AdminSuite.root_dashboard do
|
|
160
|
+
row do
|
|
161
|
+
chart_panel "Daily Cost", data: -> { [ { label: "Mon", value: 3 } ] }
|
|
162
|
+
chart_panel "Tall One", data: -> { [ { label: "Mon", value: 3 } ] }, height: 300
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
RUBY
|
|
166
|
+
get "/internal/admin_suite"
|
|
167
|
+
assert_response :success
|
|
168
|
+
document = Nokogiri::HTML(response.body)
|
|
169
|
+
|
|
170
|
+
default_chart = document.at_xpath("//h3[normalize-space()='Daily Cost']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
171
|
+
default_bars = default_chart.css("[data-admin-suite--chart-target='bars']").first
|
|
172
|
+
assert_equal "height: 192px;", default_bars["style"]
|
|
173
|
+
assert_includes default_chart.to_s, 'data-admin-suite--chart-height-value="192"'
|
|
174
|
+
|
|
175
|
+
tall_chart = document.at_xpath("//h3[normalize-space()='Tall One']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
176
|
+
tall_bars = tall_chart.css("[data-admin-suite--chart-target='bars']").first
|
|
177
|
+
assert_equal "height: 300px;", tall_bars["style"]
|
|
178
|
+
assert_includes tall_chart.to_s, 'data-admin-suite--chart-height-value="300"'
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Final review Finding 1: `chart_height.presence || 192` lets any value
|
|
183
|
+
# that survives `presence` (true, Symbols, non-empty Arrays/Hashes)
|
|
184
|
+
# through to `#to_i`, which none of those implement -- NoMethodError,
|
|
185
|
+
# 500ing the whole dashboard. Regression tests for the total-coercion fix
|
|
186
|
+
# (`Integer(Float(...))` rescuing ArgumentError/TypeError), mirroring the
|
|
187
|
+
# `type:` junk-value tests above.
|
|
188
|
+
#
|
|
189
|
+
# The Float::INFINITY/NAN cases come from the fix's own re-review: those
|
|
190
|
+
# parse as Floats but have no Integer form, so `Integer()` raises
|
|
191
|
+
# FloatDomainError -- a RangeError, which the first cut of the rescue did
|
|
192
|
+
# not name. They were the one junk value still 500ing the dashboard, and a
|
|
193
|
+
# host reaches them by computing a height from a ratio that divides by zero.
|
|
194
|
+
[ "true", ":tall", "[200]", "{ px: 200 }", '"abc"', "nil", "0", "-5",
|
|
195
|
+
"Float::INFINITY", "-Float::INFINITY", "Float::NAN" ].each do |literal|
|
|
196
|
+
test "height: #{literal} does not raise and falls back to the default 192px" do
|
|
197
|
+
with_dashboard(<<~RUBY) do
|
|
198
|
+
AdminSuite.root_dashboard do
|
|
199
|
+
row do
|
|
200
|
+
chart_panel "Junk Height", height: #{literal}, data: -> { [ { label: "Mon", value: 3 } ] }
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
RUBY
|
|
204
|
+
get "/internal/admin_suite"
|
|
205
|
+
assert_response :success
|
|
206
|
+
document = Nokogiri::HTML(response.body)
|
|
207
|
+
chart = document.at_xpath("//h3[normalize-space()='Junk Height']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
208
|
+
assert chart, "expected the chart panel card"
|
|
209
|
+
bars = chart.css("[data-admin-suite--chart-target='bars']").first
|
|
210
|
+
assert_equal "height: 192px;", bars["style"]
|
|
211
|
+
assert_includes chart.to_s, 'data-admin-suite--chart-height-value="192"'
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
[ [ '"240"', "240" ], [ "240.7", "240" ] ].each do |literal, expected|
|
|
217
|
+
test "height: #{literal} is coerced to a real pixel height instead of the default" do
|
|
218
|
+
with_dashboard(<<~RUBY) do
|
|
219
|
+
AdminSuite.root_dashboard do
|
|
220
|
+
row do
|
|
221
|
+
chart_panel "Parsed Height", height: #{literal}, data: -> { [ { label: "Mon", value: 3 } ] }
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
RUBY
|
|
225
|
+
get "/internal/admin_suite"
|
|
226
|
+
assert_response :success
|
|
227
|
+
document = Nokogiri::HTML(response.body)
|
|
228
|
+
chart = document.at_xpath("//h3[normalize-space()='Parsed Height']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
229
|
+
assert chart, "expected the chart panel card"
|
|
230
|
+
bars = chart.css("[data-admin-suite--chart-target='bars']").first
|
|
231
|
+
assert_equal "height: #{expected}px;", bars["style"]
|
|
232
|
+
assert_includes chart.to_s, %(data-admin-suite--chart-height-value="#{expected}")
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Final review Finding 2: `(panel.options[:color] || theme_primary).to_sym`
|
|
238
|
+
# raises `NoMethodError` for any `color:` value that doesn't implement
|
|
239
|
+
# `#to_sym` (e.g. an Integer or Boolean). Pre-existing since 0.4.0.
|
|
240
|
+
# Regression tests for the `.to_s.presence&.to_sym` fix -- unknown colors
|
|
241
|
+
# already fall through to indigo in both the ERB `case` and the JS
|
|
242
|
+
# `COLOR_HEX` map, so an unrecognized coerced value is expected to render
|
|
243
|
+
# the indigo bar, not raise.
|
|
244
|
+
test "a non-Symbol, non-String color: (an Integer) falls back to indigo instead of raising" do
|
|
245
|
+
with_dashboard(<<~RUBY) do
|
|
246
|
+
AdminSuite.root_dashboard do
|
|
247
|
+
row do
|
|
248
|
+
chart_panel "Integer Color", color: 42, data: -> { [ { label: "Mon", value: 3 } ] }
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
RUBY
|
|
252
|
+
get "/internal/admin_suite"
|
|
253
|
+
assert_response :success
|
|
254
|
+
document = Nokogiri::HTML(response.body)
|
|
255
|
+
chart = document.at_xpath("//h3[normalize-space()='Integer Color']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
256
|
+
assert chart, "expected the chart panel card"
|
|
257
|
+
assert_includes chart.to_s, 'data-admin-suite--chart-color-value="42"'
|
|
258
|
+
bar = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div").first
|
|
259
|
+
assert_includes bar["class"], "bg-indigo-500"
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
test "a Boolean color: (true) falls back to indigo instead of raising" do
|
|
264
|
+
with_dashboard(<<~RUBY) do
|
|
265
|
+
AdminSuite.root_dashboard do
|
|
266
|
+
row do
|
|
267
|
+
chart_panel "Boolean Color", color: true, data: -> { [ { label: "Mon", value: 3 } ] }
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
RUBY
|
|
271
|
+
get "/internal/admin_suite"
|
|
272
|
+
assert_response :success
|
|
273
|
+
document = Nokogiri::HTML(response.body)
|
|
274
|
+
chart = document.at_xpath("//h3[normalize-space()='Boolean Color']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
275
|
+
assert chart, "expected the chart panel card"
|
|
276
|
+
assert_includes chart.to_s, 'data-admin-suite--chart-color-value="true"'
|
|
277
|
+
bar = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div").first
|
|
278
|
+
assert_includes bar["class"], "bg-indigo-500"
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
test "a known color: as a Symbol still renders its matching bar color" do
|
|
283
|
+
with_dashboard(<<~RUBY) do
|
|
284
|
+
AdminSuite.root_dashboard do
|
|
285
|
+
row do
|
|
286
|
+
chart_panel "Amber Color", color: :amber, data: -> { [ { label: "Mon", value: 3 } ] }
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
RUBY
|
|
290
|
+
get "/internal/admin_suite"
|
|
291
|
+
assert_response :success
|
|
292
|
+
document = Nokogiri::HTML(response.body)
|
|
293
|
+
chart = document.at_xpath("//h3[normalize-space()='Amber Color']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
294
|
+
assert chart, "expected the chart panel card"
|
|
295
|
+
assert_includes chart.to_s, 'data-admin-suite--chart-color-value="amber"'
|
|
296
|
+
bar = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div").first
|
|
297
|
+
assert_includes bar["class"], "bg-amber-500"
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
test "chart assets load only on pages that render a chart" do
|
|
302
|
+
# Self-contained via `with_dashboard` (defined above in this file)
|
|
303
|
+
# rather than hitting a fixture route registered by a *different* test
|
|
304
|
+
# file (`/ops/read_only_widgets`, from read_only_resource_test.rb):
|
|
305
|
+
# that made this test pass in the full suite but 404 under
|
|
306
|
+
# `TEST=test/integration/chart_panel_test.rb` isolation, since that
|
|
307
|
+
# other file's resource/route never gets registered. A `stat_panel`
|
|
308
|
+
# renders no chart at all, so this is guaranteed chart-free regardless
|
|
309
|
+
# of what else has (or hasn't) been loaded.
|
|
310
|
+
with_dashboard(<<~RUBY) do
|
|
311
|
+
AdminSuite.root_dashboard do
|
|
312
|
+
row do
|
|
313
|
+
stat_panel "Active Users", 42
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
RUBY
|
|
317
|
+
get "/internal/admin_suite"
|
|
318
|
+
assert_response :success
|
|
319
|
+
refute_includes response.body, "vendor/chart"
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
test "chart_panel with no type: still renders exactly as a bar chart" do
|
|
324
|
+
with_dashboard(<<~RUBY) do
|
|
325
|
+
AdminSuite.root_dashboard do
|
|
326
|
+
row do
|
|
327
|
+
chart_panel "Bar Default", data: -> { [ { label: "Mon", value: 3 } ] }
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
RUBY
|
|
331
|
+
get "/internal/admin_suite"
|
|
332
|
+
assert_response :success
|
|
333
|
+
assert_includes response.body, 'data-admin-suite--chart-type-value="bar"'
|
|
334
|
+
document = Nokogiri::HTML(response.body)
|
|
335
|
+
chart = document.at_xpath("//h3[normalize-space()='Bar Default']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
336
|
+
assert chart, "expected the chart panel card"
|
|
337
|
+
bars = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div")
|
|
338
|
+
assert_equal 1, bars.size
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
test "chart_panel type: :line serializes to the DOM and degrades to CSS bars" do
|
|
343
|
+
with_dashboard(<<~RUBY) do
|
|
344
|
+
AdminSuite.root_dashboard do
|
|
345
|
+
row do
|
|
346
|
+
chart_panel "Line Chart", type: :line, data: -> { [ { label: "Mon", value: 3 } ] }
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
RUBY
|
|
350
|
+
get "/internal/admin_suite"
|
|
351
|
+
assert_response :success
|
|
352
|
+
assert_includes response.body, 'data-admin-suite--chart-type-value="line"'
|
|
353
|
+
document = Nokogiri::HTML(response.body)
|
|
354
|
+
chart = document.at_xpath("//h3[normalize-space()='Line Chart']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
355
|
+
assert chart, "expected the chart panel card"
|
|
356
|
+
# line's degraded (no-JS) rendering is the same CSS bars as bar/area —
|
|
357
|
+
# a line chart with one data point has no sensible line-only degrade.
|
|
358
|
+
bars = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div")
|
|
359
|
+
assert_equal 1, bars.size
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
test "chart_panel type: :area serializes to the DOM and degrades to CSS bars" do
|
|
364
|
+
with_dashboard(<<~RUBY) do
|
|
365
|
+
AdminSuite.root_dashboard do
|
|
366
|
+
row do
|
|
367
|
+
chart_panel "Area Chart", type: :area, data: -> { [ { label: "Mon", value: 3 } ] }
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
RUBY
|
|
371
|
+
get "/internal/admin_suite"
|
|
372
|
+
assert_response :success
|
|
373
|
+
assert_includes response.body, 'data-admin-suite--chart-type-value="area"'
|
|
374
|
+
document = Nokogiri::HTML(response.body)
|
|
375
|
+
chart = document.at_xpath("//h3[normalize-space()='Area Chart']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
376
|
+
assert chart, "expected the chart panel card"
|
|
377
|
+
bars = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div")
|
|
378
|
+
assert_equal 1, bars.size
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
test "chart_panel type: :doughnut serializes to the DOM and degrades to a labelled value list, not bars" do
|
|
383
|
+
with_dashboard(<<~RUBY) do
|
|
384
|
+
AdminSuite.root_dashboard do
|
|
385
|
+
row do
|
|
386
|
+
chart_panel "Doughnut Chart", type: :doughnut, data: -> {
|
|
387
|
+
[ { label: "Mon", value: 3 }, { label: "Tue", value: 7 } ]
|
|
388
|
+
}
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
RUBY
|
|
392
|
+
get "/internal/admin_suite"
|
|
393
|
+
assert_response :success
|
|
394
|
+
assert_includes response.body, 'data-admin-suite--chart-type-value="doughnut"'
|
|
395
|
+
document = Nokogiri::HTML(response.body)
|
|
396
|
+
chart = document.at_xpath("//h3[normalize-space()='Doughnut Chart']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
397
|
+
assert chart, "expected the chart panel card"
|
|
398
|
+
# A stacked bar makes no sense for a doughnut's degraded state — assert
|
|
399
|
+
# there are no percentage-height bars, and instead a plain label/value
|
|
400
|
+
# list carries both rows' data.
|
|
401
|
+
assert_empty chart.css("[data-admin-suite--chart-target='bars']")
|
|
402
|
+
assert_includes chart.text, "Mon"
|
|
403
|
+
assert_includes chart.text, "3"
|
|
404
|
+
assert_includes chart.text, "Tue"
|
|
405
|
+
assert_includes chart.text, "7"
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
test "an unknown chart type falls back to :bar and does not raise" do
|
|
410
|
+
with_dashboard(<<~RUBY) do
|
|
411
|
+
AdminSuite.root_dashboard do
|
|
412
|
+
row do
|
|
413
|
+
chart_panel "Weird Chart", type: :bogus, data: -> { [ { label: "Mon", value: 3 } ] }
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
RUBY
|
|
417
|
+
get "/internal/admin_suite"
|
|
418
|
+
assert_response :success
|
|
419
|
+
assert_includes response.body, 'data-admin-suite--chart-type-value="bar"'
|
|
420
|
+
refute_includes response.body, 'data-admin-suite--chart-type-value="bogus"'
|
|
421
|
+
document = Nokogiri::HTML(response.body)
|
|
422
|
+
chart = document.at_xpath("//h3[normalize-space()='Weird Chart']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
423
|
+
assert chart, "expected the chart panel card"
|
|
424
|
+
bars = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div")
|
|
425
|
+
assert_equal 1, bars.size
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
test "a non-Symbol, non-String type: (an Integer) falls back to :bar instead of raising" do
|
|
430
|
+
# `type: 42` used to reach `.presence&.to_sym` directly -- `presence`
|
|
431
|
+
# passes an Integer through unchanged, and Integer has no `#to_sym`,
|
|
432
|
+
# so this 500ed the whole dashboard rather than degrading. Regression
|
|
433
|
+
# test for that total-coercion fix.
|
|
434
|
+
with_dashboard(<<~RUBY) do
|
|
435
|
+
AdminSuite.root_dashboard do
|
|
436
|
+
row do
|
|
437
|
+
chart_panel "Integer Type", type: 42, data: -> { [ { label: "Mon", value: 3 } ] }
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
RUBY
|
|
441
|
+
get "/internal/admin_suite"
|
|
442
|
+
assert_response :success
|
|
443
|
+
assert_includes response.body, 'data-admin-suite--chart-type-value="bar"'
|
|
444
|
+
document = Nokogiri::HTML(response.body)
|
|
445
|
+
chart = document.at_xpath("//h3[normalize-space()='Integer Type']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
446
|
+
assert chart, "expected the chart panel card"
|
|
447
|
+
bars = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div")
|
|
448
|
+
assert_equal 1, bars.size
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
test "a Boolean type: (true) falls back to :bar instead of raising" do
|
|
453
|
+
with_dashboard(<<~RUBY) do
|
|
454
|
+
AdminSuite.root_dashboard do
|
|
455
|
+
row do
|
|
456
|
+
chart_panel "Boolean Type", type: true, data: -> { [ { label: "Mon", value: 3 } ] }
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
RUBY
|
|
460
|
+
get "/internal/admin_suite"
|
|
461
|
+
assert_response :success
|
|
462
|
+
assert_includes response.body, 'data-admin-suite--chart-type-value="bar"'
|
|
463
|
+
document = Nokogiri::HTML(response.body)
|
|
464
|
+
chart = document.at_xpath("//h3[normalize-space()='Boolean Type']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
465
|
+
assert chart, "expected the chart panel card"
|
|
466
|
+
bars = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div")
|
|
467
|
+
assert_equal 1, bars.size
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
test "a valid type: given as a String (not a Symbol) is honored, not just tolerated" do
|
|
472
|
+
with_dashboard(<<~RUBY) do
|
|
473
|
+
AdminSuite.root_dashboard do
|
|
474
|
+
row do
|
|
475
|
+
chart_panel "String Type", type: "line", data: -> { [ { label: "Mon", value: 3 } ] }
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
RUBY
|
|
479
|
+
get "/internal/admin_suite"
|
|
480
|
+
assert_response :success
|
|
481
|
+
assert_includes response.body, 'data-admin-suite--chart-type-value="line"'
|
|
482
|
+
document = Nokogiri::HTML(response.body)
|
|
483
|
+
chart = document.at_xpath("//h3[normalize-space()='String Type']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
484
|
+
assert chart, "expected the chart panel card"
|
|
485
|
+
# "line" still degrades to CSS bars, same as the Symbol form.
|
|
486
|
+
bars = chart.css("[data-admin-suite--chart-target='bars'] > .h-full > div")
|
|
487
|
+
assert_equal 1, bars.size
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
end
|
|
@@ -96,8 +96,15 @@ module AdminSuite
|
|
|
96
96
|
document = Nokogiri::HTML(response.body)
|
|
97
97
|
chart = document.at_xpath("//h3[normalize-space()='Daily Cost']/ancestor::div[contains(@class, 'rounded-xl')]")
|
|
98
98
|
assert chart
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
# The bars container carries an explicit inline height (not a
|
|
100
|
+
# Tailwind class) so the degraded CSS bars and the upgraded canvas
|
|
101
|
+
# share an identical, non-content-derived height — see
|
|
102
|
+
# app/views/admin_suite/panels/_chart.html.erb.
|
|
103
|
+
bars = chart.css("[data-admin-suite--chart-target='bars']").first
|
|
104
|
+
assert bars, "expected the bars wrapper"
|
|
105
|
+
assert_equal "height: 192px;", bars["style"], "bars wrapper must carry the default definite chart height"
|
|
106
|
+
assert_equal 2, bars.css(".h-full").size
|
|
107
|
+
assert_equal ["height: 2%", "height: 100%"], bars.css(".h-full > div").map { |bar| bar["style"] }
|
|
101
108
|
assert_includes chart.text, "Jul 30"
|
|
102
109
|
assert_includes chart.text, "Thursday"
|
|
103
110
|
end
|