diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb
index d0b6104d8c..2f6404cba6 100644
--- a/spec/controllers/admin/reports_controller_spec.rb
+++ b/spec/controllers/admin/reports_controller_spec.rb
@@ -370,4 +370,153 @@ RSpec.describe Admin::ReportsController do
end
end
end
+
+ context "AJAX Search" do
+ let(:enterprise_fee1) {
+ create(:enterprise_fee, name: "Delivery Fee", enterprise: distributor1)
+ }
+ let(:enterprise_fee2) { create(:enterprise_fee, name: "Admin Fee", enterprise: distributor2) }
+
+ before do
+ controller_login_as_admin
+ orderA1.adjustments.create!(
+ originator: enterprise_fee1,
+ label: "Delivery Fee",
+ amount: 5.0,
+ state: "finalized",
+ order: orderA1
+ )
+ orderB1.adjustments.create!(
+ originator: enterprise_fee2,
+ label: "Admin Fee",
+ amount: 3.0,
+ state: "finalized",
+ order: orderB1
+ )
+ end
+
+ describe "#search_enterprise_fees" do
+ it "returns paginated JSON with enterprise fees ordered by name" do
+ spree_get(
+ :search_enterprise_fees,
+ report_type: :enterprise_fee_summary,
+ report_subtype: :enterprise_fees_with_tax_report_by_order
+ )
+
+ expect(response).to have_http_status(:ok)
+ json_response = response.parsed_body
+
+ names = json_response["results"].pluck("text")
+ expect(names).to eq(['Admin Fee', 'Delivery Fee'])
+ end
+
+ it "caches and works with different report types" do
+ spree_get(
+ :search_enterprise_fees,
+ report_type: :enterprise_fee_summary,
+ report_subtype: :enterprise_fees_with_tax_report_by_order
+ )
+ first_response = response.parsed_body
+
+ spree_get(
+ :search_enterprise_fees,
+ report_type: :enterprise_fee_summary,
+ report_subtype: :enterprise_fees_with_tax_report_by_order
+ )
+ second_response = response.parsed_body
+
+ expect(first_response).to eq(second_response)
+ end
+ end
+
+ describe "#search_enterprise_fee_owners" do
+ it "returns paginated JSON with unique enterprise owners ordered by name" do
+ distributor1.update!(name: "Zebra Farm")
+ distributor2.update!(name: "Alpha Market")
+
+ spree_get(
+ :search_enterprise_fee_owners,
+ report_type: :enterprise_fee_summary,
+ report_subtype: :enterprise_fees_with_tax_report_by_order
+ )
+
+ expect(response).to have_http_status(:ok)
+ json_response = response.parsed_body
+
+ names = json_response["results"].pluck("text")
+ expect(names).to eq(['Alpha Market', 'Zebra Farm'])
+ end
+
+ it "caches results" do
+ spree_get(
+ :search_enterprise_fee_owners,
+ report_type: :enterprise_fee_summary,
+ report_subtype: :enterprise_fees_with_tax_report_by_order
+ )
+ first_response = response.parsed_body
+
+ spree_get(
+ :search_enterprise_fee_owners,
+ report_type: :enterprise_fee_summary,
+ report_subtype: :enterprise_fees_with_tax_report_by_order
+ )
+ second_response = response.parsed_body
+
+ expect(first_response).to eq(second_response)
+ end
+ end
+
+ describe "#search_order_customers" do
+ it "filters customers by email and returns paginated results" do
+ customer1 = create(:customer, email: "alice@example.com", enterprise: distributor1)
+ customer2 = create(:customer, email: "bob@example.com", enterprise: distributor1)
+ orderA1.update!(customer: customer1)
+ orderA2.update!(customer: customer2)
+
+ spree_get(
+ :search_order_customers,
+ report_type: :enterprise_fee_summary,
+ report_subtype: :enterprise_fees_with_tax_report_by_order,
+ q: "alice"
+ )
+
+ json_response = response.parsed_body
+ expect(json_response["results"].pluck("text")).to eq(["alice@example.com"])
+ end
+ end
+
+ describe "#search_order_cycles" do
+ it "filters order cycles by name and orders by close date" do
+ ocA.update!(name: "Winter Market")
+ ocB.update!(name: "Summer Market")
+
+ spree_get(
+ :search_order_cycles,
+ report_type: :enterprise_fee_summary,
+ report_subtype: :enterprise_fees_with_tax_report_by_order,
+ q: "Winter"
+ )
+
+ json_response = response.parsed_body
+ expect(json_response["results"].pluck("text")).to eq(["Winter Market"])
+ end
+ end
+
+ describe "#search_distributors" do
+ it "filters distributors by name" do
+ distributor1.update!(name: "Alpha Farm")
+ distributor2.update!(name: "Beta Market")
+
+ spree_get(
+ :search_distributors,
+ report_type: :enterprise_fee_summary,
+ report_subtype: :enterprise_fees_with_tax_report_by_order,
+ q: "Alpha"
+ )
+
+ json_response = response.parsed_body
+ expect(json_response["results"].pluck("text")).to eq(["Alpha Farm"])
+ end
+ end
+ end
end
diff --git a/spec/javascripts/stimulus/select2_ajax_controller_test.js b/spec/javascripts/stimulus/select2_ajax_controller_test.js
new file mode 100644
index 0000000000..3b1d449a7a
--- /dev/null
+++ b/spec/javascripts/stimulus/select2_ajax_controller_test.js
@@ -0,0 +1,156 @@
+/**
+ * @jest-environment jsdom
+ */
+
+import { Application } from "stimulus";
+import select2_ajax_controller from "../../../app/webpacker/controllers/select2_ajax_controller.js";
+
+describe("Select2AjaxController", () => {
+ let select2InitOptions = null;
+ let application;
+
+ beforeAll(() => {
+ application = Application.start();
+ application.register("select2-ajax", select2_ajax_controller);
+ });
+
+ beforeEach(() => {
+ select2InitOptions = null;
+
+ // Mock jQuery and select2
+ const mockVal = jest.fn(function (value) {
+ if (value !== undefined) {
+ this._value = value;
+ return this;
+ }
+ return this._value || "";
+ });
+
+ const mockOn = jest.fn().mockReturnThis();
+
+ const mockSelect2 = jest.fn(function (options) {
+ if (typeof options === "string" && options === "destroy") {
+ return this;
+ }
+ select2InitOptions = options;
+ return this;
+ });
+
+ const jQueryMock = jest.fn((selector) => {
+ let element;
+ if (typeof selector === "string" && selector.startsWith("
+
+
+ `;
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = "";
+ delete global.$;
+ });
+
+ describe("#connect", () => {
+ it("initializes select2 with correct AJAX URL", () => {
+ expect(select2InitOptions).not.toBeNull();
+ expect(select2InitOptions.ajax.url).toBe("/api/search");
+ });
+
+ it("configures select2 with correct options", () => {
+ expect(select2InitOptions.ajax.dataType).toBe("json");
+ expect(select2InitOptions.ajax.quietMillis).toBe(300);
+ expect(select2InitOptions.allowClear).toBe(true);
+ expect(select2InitOptions.minimumInputLength).toBe(0);
+ expect(select2InitOptions.width).toBe("100%");
+ });
+
+ it("configures AJAX data function", () => {
+ const dataFunc = select2InitOptions.ajax.data;
+ const result = dataFunc("search term", 2);
+
+ expect(result).toEqual({
+ q: "search term",
+ page: 2,
+ });
+ });
+
+ it("handles empty search term", () => {
+ const dataFunc = select2InitOptions.ajax.data;
+ const result = dataFunc(null, null);
+
+ expect(result).toEqual({
+ q: "",
+ page: 1,
+ });
+ });
+
+ it("configures results function with pagination", () => {
+ const resultsFunc = select2InitOptions.ajax.results;
+ const mockData = {
+ results: [{ id: 1, text: "Item 1" }],
+ pagination: { more: true },
+ };
+
+ const result = resultsFunc(mockData, 1);
+
+ expect(result).toEqual({
+ results: mockData.results,
+ more: true,
+ });
+ });
+
+ it("handles missing pagination", () => {
+ const resultsFunc = select2InitOptions.ajax.results;
+ const mockData = {
+ results: [{ id: 1, text: "Item 1" }],
+ };
+
+ const result = resultsFunc(mockData, 1);
+
+ expect(result).toEqual({
+ results: mockData.results,
+ more: false,
+ });
+ });
+
+ it("configures format functions", () => {
+ const item = { id: 1, text: "Test Item" };
+
+ expect(select2InitOptions.formatResult(item)).toBe("Test Item");
+ expect(select2InitOptions.formatSelection(item)).toBe("Test Item");
+ });
+ });
+});