diff --git a/spec/lib/tiny_admin/configure_spec.rb b/spec/lib/tiny_admin/configure_spec.rb new file mode 100644 index 0000000..0cd07f1 --- /dev/null +++ b/spec/lib/tiny_admin/configure_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "dummy_rails_app" +require "rails_helper" + +RSpec.describe "TinyAdmin.configure" do # rubocop:disable RSpec/DescribeClass + let(:settings) { TinyAdmin::Settings.instance } + + around do |example| + saved = settings.instance_variable_get(:@options)&.deep_dup + saved_store = settings.instance_variable_get(:@store) + saved_loaded = settings.instance_variable_get(:@loaded) + example.run + ensure + settings.instance_variable_set(:@options, saved) + settings.instance_variable_set(:@store, saved_store) + settings.instance_variable_set(:@loaded, saved_loaded) + end + + it "yields settings to the block" do + yielded = nil + TinyAdmin.configure { |s| yielded = s } + expect(yielded).to eq(settings) + end + + it "allows setting options via the block" do + TinyAdmin.configure { |s| s.root_path = "/custom" } + expect(settings.root_path).to eq("/custom") + end + + it "returns settings when no block is given" do + result = TinyAdmin.configure + expect(result).to eq(settings) + end +end diff --git a/spec/lib/tiny_admin/utils_prepare_page_spec.rb b/spec/lib/tiny_admin/utils_prepare_page_spec.rb new file mode 100644 index 0000000..8b37ac7 --- /dev/null +++ b/spec/lib/tiny_admin/utils_prepare_page_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "dummy_rails_app" +require "rails_helper" + +RSpec.describe TinyAdmin::Utils, "#prepare_page" do + let(:utils_instance) { Class.new { include TinyAdmin::Utils }.new } + let(:settings) { TinyAdmin::Settings.instance } + + around do |example| + saved = settings.instance_variable_get(:@options)&.deep_dup + saved_store = settings.instance_variable_get(:@store) + saved_loaded = settings.instance_variable_get(:@loaded) + settings.load_settings + example.run + ensure + settings.instance_variable_set(:@options, saved) + settings.instance_variable_set(:@store, saved_store) + settings.instance_variable_set(:@loaded, saved_loaded) + end + + it "returns an instance of the given page class" do + page = utils_instance.prepare_page(TinyAdmin::Views::Pages::Root) + expect(page).to be_a(TinyAdmin::Views::Pages::Root) + end + + it "assigns head, flash, and navbar components", :aggregate_failures do + page = utils_instance.prepare_page(TinyAdmin::Views::Pages::Root) + expect(page.head_component).to be_a(TinyAdmin::Views::Components::Head) + expect(page.flash_component).to be_a(TinyAdmin::Views::Components::Flash) + expect(page.navbar_component).to be_a(TinyAdmin::Views::Components::Navbar) + end + + it "sets the page title from attributes" do + page = utils_instance.prepare_page(TinyAdmin::Views::Pages::Root, attributes: { title: "My Title" }) + expect(page.title).to eq("My Title") + end + + it "sets params on the page" do + page = utils_instance.prepare_page(TinyAdmin::Views::Pages::Root, params: { "page" => "2" }) + expect(page.params).to eq({ "page" => "2" }) + end + + it "passes no_menu option to hide navbar items" do + page = utils_instance.prepare_page(TinyAdmin::Views::Pages::Root, options: [:no_menu]) + expect(page.navbar_component.items).to eq([]) + end + + it "yields the page to the block when given" do + yielded = nil + utils_instance.prepare_page(TinyAdmin::Views::Pages::Root) { |p| yielded = p } + expect(yielded).to be_a(TinyAdmin::Views::Pages::Root) + end + + it "resolves widget class names from strings" do + widget_class = Class.new(Phlex::HTML) do + def view_template + plain "test" + end + end + stub_const("TestWidget", widget_class) + page = utils_instance.prepare_page(TinyAdmin::Views::Pages::Root, attributes: { widgets: ["TestWidget"] }) + expect(page.widgets).to eq([TestWidget]) + end +end diff --git a/spec/lib/tiny_admin/views/components/field_value_spec.rb b/spec/lib/tiny_admin/views/components/field_value_spec.rb new file mode 100644 index 0000000..ada0cd3 --- /dev/null +++ b/spec/lib/tiny_admin/views/components/field_value_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "dummy_rails_app" +require "rails_helper" + +RSpec.describe TinyAdmin::Views::Components::FieldValue do + let(:settings) { TinyAdmin::Settings.instance } + + before { settings.load_settings } + + describe "with a basic field" do + let(:field) { TinyAdmin::Field.new(name: "title", type: :string, title: "Title", options: {}) } + let(:record) { double("record", id: 1) } # rubocop:disable RSpec/VerifiedDoubles + + it "renders the translated value in a span", :aggregate_failures do + html = described_class.new(field, "Hello", record: record).call + expect(html).to include("Hello") + expect(html).to include(" { filter: { type: :boolean }, value: "1" } } } + + it "renders a select with true/false options", :aggregate_failures do + html = component.call + expect(html).to include("form-select") + expect(html).to include("true") + expect(html).to include("false") + end + end + + describe "with a date filter" do + let(:field) { TinyAdmin::Field.new(name: "created_at", type: :date, title: "Created At", options: {}) } + let(:filters) { { field => { filter: { type: :date }, value: "2024-01-01" } } } + + it "renders a date input", :aggregate_failures do + html = component.call + expect(html).to include('type="date"') + expect(html).to include("2024-01-01") + end + end + + describe "with a datetime filter" do + let(:field) { TinyAdmin::Field.new(name: "updated_at", type: :datetime, title: "Updated At", options: {}) } + let(:filters) { { field => { filter: { type: :datetime }, value: "2024-01-01T12:00" } } } + + it "renders a datetime-local input" do + html = component.call + expect(html).to include('type="datetime-local"') + end + end + + describe "with an integer filter" do + let(:field) { TinyAdmin::Field.new(name: "age", type: :integer, title: "Age", options: {}) } + let(:filters) { { field => { filter: { type: :integer }, value: "25" } } } + + it "renders a number input" do + html = component.call + expect(html).to include('type="number"') + end + end + + describe "with a select filter" do + let(:field) { TinyAdmin::Field.new(name: "state", type: :select, title: "State", options: {}) } + let(:filters) { { field => { filter: { type: :select, values: %w[available unavailable] }, value: "available" } } } + + it "renders a select with the provided values", :aggregate_failures do + html = component.call + expect(html).to include("form-select") + expect(html).to include("available") + expect(html).to include("unavailable") + end + end + + describe "with a text filter (default)" do + let(:field) { TinyAdmin::Field.new(name: "title", type: :string, title: "Title", options: {}) } + let(:filters) { { field => { filter: {}, value: "hello" } } } + + it "renders a text input", :aggregate_failures do + html = component.call + expect(html).to include('type="text"') + expect(html).to include("hello") + end + end + + describe "action buttons" do + let(:field) { TinyAdmin::Field.new(name: "title", type: :string, title: "Title", options: {}) } + let(:filters) { { field => { filter: {}, value: "" } } } + + it "renders Clear and Filter buttons", :aggregate_failures do + html = component.call + expect(html).to include("Clear") + expect(html).to include("Filter") + end + end +end diff --git a/spec/lib/tiny_admin/views/components/widgets_spec.rb b/spec/lib/tiny_admin/views/components/widgets_spec.rb new file mode 100644 index 0000000..dff1168 --- /dev/null +++ b/spec/lib/tiny_admin/views/components/widgets_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "dummy_rails_app" +require "rails_helper" + +RSpec.describe TinyAdmin::Views::Components::Widgets do + describe "with nil widgets" do + it "renders nothing" do + html = described_class.new(nil).call + expect(html).to eq("") + end + end + + describe "with empty widgets" do + it "renders nothing" do + html = described_class.new([]).call + expect(html).to eq("") + end + end + + describe "with valid widget classes" do + let(:widget_class) do + Class.new(Phlex::HTML) do + def view_template + plain "Widget content" + end + end + end + + it "renders each widget in a card", :aggregate_failures do + html = described_class.new([widget_class]).call + expect(html).to include("Widget content") + expect(html).to include("card-body") + end + end + + describe "with non-Phlex widget" do + it "skips non-Phlex classes" do + html = described_class.new([String]).call + expect(html).not_to include("card-body") + end + end +end