Skip to content

Commit 008b2dc

Browse files
committed
test: add specs for configure, prepare_page, idempotent settings, and view components
Add 31 new specs bringing line coverage to 100% and branch coverage from 80.97% to 83.4%. Covers TinyAdmin.configure block form, Utils#prepare_page, Settings#load_settings idempotency, and unit specs for FieldValue, Widgets, FiltersForm (all filter types), and ActionsButtons components.
1 parent 347b53b commit 008b2dc

7 files changed

Lines changed: 369 additions & 0 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# frozen_string_literal: true
2+
3+
require "dummy_rails_app"
4+
require "rails_helper"
5+
6+
RSpec.describe "TinyAdmin.configure" do
7+
let(:settings) { TinyAdmin::Settings.instance }
8+
9+
around do |example|
10+
saved = settings.instance_variable_get(:@options)&.deep_dup
11+
saved_store = settings.instance_variable_get(:@store)
12+
saved_loaded = settings.instance_variable_get(:@loaded)
13+
example.run
14+
ensure
15+
settings.instance_variable_set(:@options, saved)
16+
settings.instance_variable_set(:@store, saved_store)
17+
settings.instance_variable_set(:@loaded, saved_loaded)
18+
end
19+
20+
it "yields settings to the block" do
21+
yielded = nil
22+
TinyAdmin.configure { |s| yielded = s }
23+
expect(yielded).to eq(settings)
24+
end
25+
26+
it "allows setting options via the block" do
27+
TinyAdmin.configure { |s| s.root_path = "/custom" }
28+
expect(settings.root_path).to eq("/custom")
29+
end
30+
31+
it "returns settings when no block is given" do
32+
result = TinyAdmin.configure
33+
expect(result).to eq(settings)
34+
end
35+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
require "dummy_rails_app"
4+
require "rails_helper"
5+
6+
RSpec.describe TinyAdmin::Settings, "#load_settings idempotency" do
7+
let(:settings) { described_class.instance }
8+
9+
around do |example|
10+
saved = settings.instance_variable_get(:@options)&.deep_dup
11+
saved_store = settings.instance_variable_get(:@store)
12+
saved_loaded = settings.instance_variable_get(:@loaded)
13+
example.run
14+
ensure
15+
settings.instance_variable_set(:@options, saved)
16+
settings.instance_variable_set(:@store, saved_store)
17+
settings.instance_variable_set(:@loaded, saved_loaded)
18+
end
19+
20+
it "does not re-run on subsequent calls" do
21+
settings.reset!
22+
settings.load_settings
23+
store = settings.store
24+
settings.load_settings
25+
expect(settings.store).to equal(store)
26+
end
27+
28+
it "runs again after reset!" do
29+
settings.reset!
30+
settings.load_settings
31+
store = settings.store
32+
33+
settings.reset!
34+
settings.load_settings
35+
expect(settings.store).not_to equal(store)
36+
end
37+
end
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# frozen_string_literal: true
2+
3+
require "dummy_rails_app"
4+
require "rails_helper"
5+
6+
RSpec.describe TinyAdmin::Utils, "#prepare_page" do
7+
let(:utils_instance) { Class.new { include TinyAdmin::Utils }.new }
8+
let(:settings) { TinyAdmin::Settings.instance }
9+
10+
around do |example|
11+
saved = settings.instance_variable_get(:@options)&.deep_dup
12+
saved_store = settings.instance_variable_get(:@store)
13+
saved_loaded = settings.instance_variable_get(:@loaded)
14+
settings.load_settings
15+
example.run
16+
ensure
17+
settings.instance_variable_set(:@options, saved)
18+
settings.instance_variable_set(:@store, saved_store)
19+
settings.instance_variable_set(:@loaded, saved_loaded)
20+
end
21+
22+
it "returns an instance of the given page class" do
23+
page = utils_instance.prepare_page(TinyAdmin::Views::Pages::Root)
24+
expect(page).to be_a(TinyAdmin::Views::Pages::Root)
25+
end
26+
27+
it "assigns head, flash, and navbar components" do
28+
page = utils_instance.prepare_page(TinyAdmin::Views::Pages::Root)
29+
expect(page.head_component).to be_a(TinyAdmin::Views::Components::Head)
30+
expect(page.flash_component).to be_a(TinyAdmin::Views::Components::Flash)
31+
expect(page.navbar_component).to be_a(TinyAdmin::Views::Components::Navbar)
32+
end
33+
34+
it "sets the page title from attributes" do
35+
page = utils_instance.prepare_page(TinyAdmin::Views::Pages::Root, attributes: {title: "My Title"})
36+
expect(page.title).to eq("My Title")
37+
end
38+
39+
it "sets params on the page" do
40+
page = utils_instance.prepare_page(TinyAdmin::Views::Pages::Root, params: {"page" => "2"})
41+
expect(page.params).to eq({"page" => "2"})
42+
end
43+
44+
it "passes no_menu option to hide navbar items" do
45+
page = utils_instance.prepare_page(TinyAdmin::Views::Pages::Root, options: [:no_menu])
46+
expect(page.navbar_component.items).to eq([])
47+
end
48+
49+
it "yields the page to the block when given" do
50+
yielded = nil
51+
utils_instance.prepare_page(TinyAdmin::Views::Pages::Root) { |p| yielded = p }
52+
expect(yielded).to be_a(TinyAdmin::Views::Pages::Root)
53+
end
54+
55+
it "resolves widget class names from strings" do
56+
widget_class = Class.new(Phlex::HTML) do
57+
def view_template
58+
plain "test"
59+
end
60+
end
61+
stub_const("TestWidget", widget_class)
62+
page = utils_instance.prepare_page(TinyAdmin::Views::Pages::Root, attributes: {widgets: ["TestWidget"]})
63+
expect(page.widgets).to eq([TestWidget])
64+
end
65+
end
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# frozen_string_literal: true
2+
3+
require "dummy_rails_app"
4+
require "rails_helper"
5+
6+
RSpec.describe TinyAdmin::Views::Components::ActionsButtons do
7+
let(:settings) { TinyAdmin::Settings.instance }
8+
9+
before { settings.load_settings }
10+
11+
describe "with no actions" do
12+
it "renders an empty nav list" do
13+
component = described_class.new
14+
component.update_attributes(actions: {}, slug: "posts")
15+
html = component.call
16+
expect(html).to include("nav")
17+
expect(html).not_to include("nav-item")
18+
end
19+
end
20+
21+
describe "with actions" do
22+
let(:action_class) do
23+
Class.new do
24+
def self.title
25+
"Export"
26+
end
27+
end
28+
end
29+
30+
it "renders action buttons with links" do
31+
component = described_class.new
32+
component.update_attributes(actions: {"export" => action_class}, slug: "posts")
33+
html = component.call
34+
expect(html).to include("Export")
35+
expect(html).to include("export")
36+
expect(html).to include("nav-item")
37+
end
38+
39+
it "includes the reference in the URL when provided" do
40+
component = described_class.new
41+
component.update_attributes(actions: {"export" => action_class}, slug: "posts", reference: "42")
42+
html = component.call
43+
expect(html).to include("42")
44+
end
45+
end
46+
47+
describe "with an action class that does not respond to title" do
48+
it "falls back to the action key as label" do
49+
action_class = Class.new
50+
component = described_class.new
51+
component.update_attributes(actions: {"download" => action_class}, slug: "posts")
52+
html = component.call
53+
expect(html).to include("download")
54+
end
55+
end
56+
end
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# frozen_string_literal: true
2+
3+
require "dummy_rails_app"
4+
require "rails_helper"
5+
6+
RSpec.describe TinyAdmin::Views::Components::FieldValue do
7+
let(:settings) { TinyAdmin::Settings.instance }
8+
9+
before { settings.load_settings }
10+
11+
describe "with a basic field" do
12+
let(:field) { TinyAdmin::Field.new(name: "title", type: :string, title: "Title", options: {}) }
13+
let(:record) { double("record", id: 1) }
14+
15+
it "renders the translated value in a span" do
16+
html = described_class.new(field, "Hello", record: record).call
17+
expect(html).to include("Hello")
18+
expect(html).to include("<span")
19+
end
20+
end
21+
22+
describe "with link_to option" do
23+
let(:field) { TinyAdmin::Field.new(name: "author_id", type: :integer, title: "Author", options: {link_to: "authors"}) }
24+
let(:record) { double("record", id: 1) }
25+
26+
it "wraps the value in a link" do
27+
html = described_class.new(field, "42", record: record).call
28+
expect(html).to include("<a")
29+
expect(html).to include("authors")
30+
expect(html).to include("42")
31+
end
32+
end
33+
34+
describe "with value_class option" do
35+
let(:field) { TinyAdmin::Field.new(name: "status", type: :string, title: "Status", options: {options: ["value_class"]}) }
36+
let(:record) { double("record", id: 1) }
37+
38+
it "adds value-based CSS class to the span" do
39+
html = described_class.new(field, "active", record: record).call
40+
expect(html).to include("value-active")
41+
end
42+
end
43+
end
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# frozen_string_literal: true
2+
3+
require "dummy_rails_app"
4+
require "rails_helper"
5+
6+
RSpec.describe TinyAdmin::Views::Components::FiltersForm do
7+
let(:component) { described_class.new }
8+
9+
before do
10+
TinyAdmin::Settings.instance.load_settings
11+
component.update_attributes(section_path: "/admin/posts", filters: filters)
12+
end
13+
14+
describe "with a boolean filter" do
15+
let(:field) { TinyAdmin::Field.new(name: "published", type: :boolean, title: "Published", options: {}) }
16+
let(:filters) { {field => {filter: {type: :boolean}, value: "1"}} }
17+
18+
it "renders a select with true/false options" do
19+
html = component.call
20+
expect(html).to include("form-select")
21+
expect(html).to include("true")
22+
expect(html).to include("false")
23+
end
24+
end
25+
26+
describe "with a date filter" do
27+
let(:field) { TinyAdmin::Field.new(name: "created_at", type: :date, title: "Created At", options: {}) }
28+
let(:filters) { {field => {filter: {type: :date}, value: "2024-01-01"}} }
29+
30+
it "renders a date input" do
31+
html = component.call
32+
expect(html).to include('type="date"')
33+
expect(html).to include("2024-01-01")
34+
end
35+
end
36+
37+
describe "with a datetime filter" do
38+
let(:field) { TinyAdmin::Field.new(name: "updated_at", type: :datetime, title: "Updated At", options: {}) }
39+
let(:filters) { {field => {filter: {type: :datetime}, value: "2024-01-01T12:00"}} }
40+
41+
it "renders a datetime-local input" do
42+
html = component.call
43+
expect(html).to include('type="datetime-local"')
44+
end
45+
end
46+
47+
describe "with an integer filter" do
48+
let(:field) { TinyAdmin::Field.new(name: "age", type: :integer, title: "Age", options: {}) }
49+
let(:filters) { {field => {filter: {type: :integer}, value: "25"}} }
50+
51+
it "renders a number input" do
52+
html = component.call
53+
expect(html).to include('type="number"')
54+
end
55+
end
56+
57+
describe "with a select filter" do
58+
let(:field) { TinyAdmin::Field.new(name: "state", type: :select, title: "State", options: {}) }
59+
let(:filters) { {field => {filter: {type: :select, values: %w[available unavailable]}, value: "available"}} }
60+
61+
it "renders a select with the provided values" do
62+
html = component.call
63+
expect(html).to include("form-select")
64+
expect(html).to include("available")
65+
expect(html).to include("unavailable")
66+
end
67+
end
68+
69+
describe "with a text filter (default)" do
70+
let(:field) { TinyAdmin::Field.new(name: "title", type: :string, title: "Title", options: {}) }
71+
let(:filters) { {field => {filter: {}, value: "hello"}} }
72+
73+
it "renders a text input" do
74+
html = component.call
75+
expect(html).to include('type="text"')
76+
expect(html).to include("hello")
77+
end
78+
end
79+
80+
describe "action buttons" do
81+
let(:field) { TinyAdmin::Field.new(name: "title", type: :string, title: "Title", options: {}) }
82+
let(:filters) { {field => {filter: {}, value: ""}} }
83+
84+
it "renders Clear and Filter buttons" do
85+
html = component.call
86+
expect(html).to include("Clear")
87+
expect(html).to include("Filter")
88+
end
89+
end
90+
end
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# frozen_string_literal: true
2+
3+
require "dummy_rails_app"
4+
require "rails_helper"
5+
6+
RSpec.describe TinyAdmin::Views::Components::Widgets do
7+
describe "with nil widgets" do
8+
it "renders nothing" do
9+
html = described_class.new(nil).call
10+
expect(html).to eq("")
11+
end
12+
end
13+
14+
describe "with empty widgets" do
15+
it "renders nothing" do
16+
html = described_class.new([]).call
17+
expect(html).to eq("")
18+
end
19+
end
20+
21+
describe "with valid widget classes" do
22+
let(:widget_class) do
23+
Class.new(Phlex::HTML) do
24+
def view_template
25+
plain "Widget content"
26+
end
27+
end
28+
end
29+
30+
it "renders each widget in a card" do
31+
html = described_class.new([widget_class]).call
32+
expect(html).to include("Widget content")
33+
expect(html).to include("card-body")
34+
end
35+
end
36+
37+
describe "with non-Phlex widget" do
38+
it "skips non-Phlex classes" do
39+
html = described_class.new([String]).call
40+
expect(html).not_to include("card-body")
41+
end
42+
end
43+
end

0 commit comments

Comments
 (0)