Files
openobserve/web/src/composables/useSelectAutocomplete.spec.ts
T
2026-07-24 07:11:39 +00:00

781 lines
26 KiB
TypeScript

// Copyright 2026 OpenObserve Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { ref, nextTick } from "vue";
import { useSelectAutoComplete } from "./useSelectAutocomplete";
describe("useSelectAutocomplete.ts Comprehensive Coverage", () => {
let mockUpdate: any;
beforeEach(() => {
vi.clearAllMocks();
mockUpdate = vi.fn((callback) => callback());
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("Composable Initialization", () => {
it("should initialize composable with string array options", () => {
const options = ref(["apple", "banana", "cherry"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toEqual(["apple", "banana", "cherry"]);
expect(typeof filterFn).toBe("function");
});
it("should initialize composable with object array options", () => {
const options = ref([
{ name: "Apple", value: "apple" },
{ name: "Banana", value: "banana" },
{ name: "Cherry", value: "cherry" },
]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toEqual([
{ name: "Apple", value: "apple" },
{ name: "Banana", value: "banana" },
{ name: "Cherry", value: "cherry" },
]);
expect(typeof filterFn).toBe("function");
});
it("should initialize composable with empty array options", () => {
const options = ref([]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toEqual([]);
expect(typeof filterFn).toBe("function");
});
it("should initialize composable with null options", () => {
const options = ref(null);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toBeNull();
expect(typeof filterFn).toBe("function");
});
it("should initialize composable with undefined options", () => {
const options = ref(undefined);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toBeUndefined();
expect(typeof filterFn).toBe("function");
});
it("should initialize composable with different searchKey values", () => {
const options = ref([{ title: "Test", description: "Test desc" }]);
const searchKey = "title";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toEqual([{ title: "Test", description: "Test desc" }]);
expect(typeof filterFn).toBe("function");
});
it("should initialize composable with empty searchKey", () => {
const options = ref(["test"]);
const searchKey = "";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toEqual(["test"]);
expect(typeof filterFn).toBe("function");
});
it("should handle mixed data types in options array", () => {
const options = ref(["string", 123, { name: "object" }, null, undefined]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toEqual(["string", 123, { name: "object" }, null, undefined]);
expect(typeof filterFn).toBe("function");
});
});
describe("Options Watcher Functionality", () => {
it("should update filteredOptions when options change", async () => {
const options = ref(["apple", "banana"]);
const searchKey = "name";
const { filteredOptions } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toEqual(["apple", "banana"]);
options.value = ["cherry", "date"];
await nextTick();
expect(filteredOptions.value).toEqual(["cherry", "date"]);
});
it("should update filteredOptions when options change from empty to filled", async () => {
const options = ref([]);
const searchKey = "name";
const { filteredOptions } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toEqual([]);
options.value = ["apple", "banana"];
await nextTick();
expect(filteredOptions.value).toEqual(["apple", "banana"]);
});
it("should update filteredOptions when options change from filled to empty", async () => {
const options = ref(["apple", "banana"]);
const searchKey = "name";
const { filteredOptions } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toEqual(["apple", "banana"]);
options.value = [];
await nextTick();
expect(filteredOptions.value).toEqual([]);
});
it("should update filteredOptions when options change from null to array", async () => {
const options = ref(null);
const searchKey = "name";
const { filteredOptions } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toBeNull();
options.value = ["apple", "banana"];
await nextTick();
expect(filteredOptions.value).toEqual(["apple", "banana"]);
});
it("should update filteredOptions when options change from array to null", async () => {
const options = ref(["apple", "banana"]);
const searchKey = "name";
const { filteredOptions } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toEqual(["apple", "banana"]);
options.value = null;
await nextTick();
expect(filteredOptions.value).toBeNull();
});
it("should update filteredOptions with object array changes", async () => {
const options = ref([{ name: "Apple" }]);
const searchKey = "name";
const { filteredOptions } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions.value).toEqual([{ name: "Apple" }]);
options.value = [{ name: "Banana" }, { name: "Cherry" }];
await nextTick();
expect(filteredOptions.value).toEqual([{ name: "Banana" }, { name: "Cherry" }]);
});
});
describe("FilterFn - Empty String Handling", () => {
it("should reset filteredOptions to original options when filter value is empty string", () => {
const options = ref(["apple", "banana", "cherry"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
// First filter to reduce options
filterFn("app", mockUpdate);
// Then reset with empty string
filterFn("", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(2);
expect(filteredOptions.value).toEqual(["apple", "banana", "cherry"]);
});
it("should handle empty string filter with object array", () => {
const options = ref([{ name: "Apple" }, { name: "Banana" }]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual([{ name: "Apple" }, { name: "Banana" }]);
});
it("should handle empty string filter with empty options array", () => {
const options = ref([]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual([]);
});
it("should handle empty string filter with null options", () => {
const options = ref(null);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toBeNull();
});
});
describe("FilterFn - String Array Filtering", () => {
it("should filter string array with case-insensitive matching", () => {
const options = ref(["Apple", "Banana", "Cherry", "Apricot"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("app", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["Apple"]);
});
it("should filter string array with exact case matching (case insensitive)", () => {
const options = ref(["APPLE", "banana", "Cherry"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("APPLE", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["APPLE"]);
});
it("should filter string array with partial matching", () => {
const options = ref(["JavaScript", "Java", "Python", "TypeScript"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("script", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["JavaScript", "TypeScript"]);
});
it("should check actual apricot filtering behavior", () => {
const options = ref(["Apple", "Apricot", "Banana"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("app", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
// Check what the actual behavior is - should only match items containing 'app'
expect(filteredOptions.value.length).toBeGreaterThan(0);
expect(filteredOptions.value).toContain("Apple");
});
it("should return empty array when no matches found in string array", () => {
const options = ref(["Apple", "Banana", "Cherry"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("xyz", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual([]);
});
it("should handle numeric strings in array", () => {
const options = ref(["1", "10", "100", "2", "20"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("1", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["1", "10", "100"]);
});
it("should handle special characters in string filtering", () => {
const options = ref(["test@email.com", "user@domain.org", "admin@site.net"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("@", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual([
"test@email.com",
"user@domain.org",
"admin@site.net",
]);
});
it("should handle unicode characters in string filtering", () => {
const options = ref(["café", "naïve", "résumé", "test"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("é", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["café", "résumé"]);
});
});
describe("FilterFn - Object Array Filtering", () => {
it("should filter object array by searchKey property", () => {
const options = ref([
{ name: "Apple", category: "fruit" },
{ name: "Banana", category: "fruit" },
{ name: "Carrot", category: "vegetable" },
]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("app", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual([{ name: "Apple", category: "fruit" }]);
});
it("should filter object array by different searchKey property", () => {
const options = ref([
{ name: "Apple", category: "fruit" },
{ name: "Banana", category: "fruit" },
{ name: "Carrot", category: "vegetable" },
]);
const searchKey = "category";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("veg", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual([{ name: "Carrot", category: "vegetable" }]);
});
it("should handle objects with missing searchKey property", () => {
const options = ref([
{ name: "Apple", category: "fruit" },
{ category: "fruit" }, // missing name property - will cause error
{ name: "Carrot", category: "vegetable" },
]);
const searchKey = "name";
const { filterFn } = useSelectAutoComplete(options, searchKey);
// The current implementation will throw an error when accessing undefined property
expect(() => {
filterFn("app", mockUpdate);
}).toThrow();
// Update is called but the callback inside throws an error
expect(mockUpdate).toHaveBeenCalledTimes(1);
});
it("should handle objects with null searchKey values", () => {
const options = ref([
{ name: "Apple", category: "fruit" },
{ name: "Carrot", category: "vegetable" },
]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("app", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual([{ name: "Apple", category: "fruit" }]);
});
it("should handle nested object properties (searchKey as simple property)", () => {
const options = ref([
{ details: { name: "Apple" }, category: "fruit" },
{ details: { name: "Banana" }, category: "fruit" },
]);
const searchKey = "category";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("fruit", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toHaveLength(2);
});
it("should filter objects with numeric properties", () => {
const options = ref([
{ name: "Item1", price: "100" },
{ name: "Item2", price: "200" },
{ name: "Item3", price: "150" },
]);
const searchKey = "price";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("1", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual([
{ name: "Item1", price: "100" },
{ name: "Item3", price: "150" },
]);
});
it("should handle objects with boolean properties", () => {
const options = ref([
{ name: "Active Item", active: "true" },
{ name: "Inactive Item", active: "false" },
]);
const searchKey = "active";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("true", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual([{ name: "Active Item", active: "true" }]);
});
});
describe("FilterFn - Mixed Data Types", () => {
it("should handle mixed primitive and object types", () => {
const options = ref([
"string item",
{ name: "Object Item", type: "object" },
123,
{ name: "Another Object", type: "test" },
]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("object", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual([
{ name: "Object Item", type: "object" },
{ name: "Another Object", type: "test" },
]);
});
it("should handle arrays with null and undefined values", () => {
const options = ref(["valid string", { name: "Valid Object" }, ""]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("valid", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["valid string", { name: "Valid Object" }]);
});
it("should handle numbers in mixed array", () => {
const options = ref(["item1", "123", "456", { name: "123", type: "number" }]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("123", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["123", { name: "123", type: "number" }]);
});
});
describe("FilterFn - Edge Cases", () => {
it("should handle null options array during filtering", () => {
const options = ref(null);
const searchKey = "name";
const { filterFn } = useSelectAutoComplete(options, searchKey);
expect(() => {
filterFn("test", mockUpdate);
}).not.toThrow();
expect(mockUpdate).toHaveBeenCalledTimes(1);
});
it("should handle undefined options array during filtering", () => {
const options = ref(undefined);
const searchKey = "name";
const { filterFn } = useSelectAutoComplete(options, searchKey);
expect(() => {
filterFn("test", mockUpdate);
}).not.toThrow();
expect(mockUpdate).toHaveBeenCalledTimes(1);
});
it("should handle very long search strings", () => {
const options = ref(["short", "a very long string that contains multiple words"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
const longSearchString = "a very long string that contains multiple words";
filterFn(longSearchString, mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["a very long string that contains multiple words"]);
});
it("should handle special regex characters in search", () => {
const options = ref(["test.*regex", "normal string", "another.test"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn(".*", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["test.*regex"]);
});
it("should handle whitespace in search strings", () => {
const options = ref(["hello world", "helloworld", "hello", "world"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn(" ", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["hello world"]);
});
it("should handle leading and trailing whitespace in search", () => {
const options = ref(["test", "testing", "test item"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("test", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["test", "testing", "test item"]);
});
});
describe("FilterFn - Case Sensitivity", () => {
it("should handle mixed case search terms", () => {
const options = ref(["Apple", "BANANA", "cherry", "DaTe"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("A", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["Apple", "BANANA", "DaTe"]);
});
it("should ensure double toLowerCase call does not break filtering", () => {
const options = ref(["Test", "TEST", "test"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("TEST", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["Test", "TEST", "test"]);
});
it("should handle non-ASCII characters case conversion", () => {
const options = ref(["CAFÉ", "café", "NAÏVE", "naïve"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("café", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toEqual(["CAFÉ", "café"]);
});
});
describe("FilterFn - Update Callback Integration", () => {
it("should call update callback exactly once for non-empty search", () => {
const options = ref(["test"]);
const searchKey = "name";
const { filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("test", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(typeof mockUpdate.mock.calls[0][0]).toBe("function");
});
it("should call update callback exactly once for empty search", () => {
const options = ref(["test"]);
const searchKey = "name";
const { filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(typeof mockUpdate.mock.calls[0][0]).toBe("function");
});
it("should pass correct callback function to update", () => {
const options = ref(["test"]);
const searchKey = "name";
const { filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("test", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
const callback = mockUpdate.mock.calls[0][0];
expect(() => callback()).not.toThrow();
});
});
describe("Return Value Structure", () => {
it("should return object with filteredOptions and filterFn properties", () => {
const options = ref(["test"]);
const searchKey = "name";
const result = useSelectAutoComplete(options, searchKey);
expect(result).toHaveProperty("filteredOptions");
expect(result).toHaveProperty("filterFn");
expect(Object.keys(result)).toHaveLength(2);
});
it("should return filteredOptions as a reactive ref", () => {
const options = ref(["test"]);
const searchKey = "name";
const { filteredOptions } = useSelectAutoComplete(options, searchKey);
expect(filteredOptions).toHaveProperty("value");
expect(typeof filteredOptions.value).not.toBe("undefined");
});
it("should return filterFn as a function", () => {
const options = ref(["test"]);
const searchKey = "name";
const { filterFn } = useSelectAutoComplete(options, searchKey);
expect(typeof filterFn).toBe("function");
expect(filterFn.length).toBe(2); // Should accept 2 parameters
});
});
describe("Integration and Complex Scenarios", () => {
it("should handle rapid successive filter calls", () => {
const options = ref(["apple", "apricot", "banana"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("a", mockUpdate);
filterFn("ap", mockUpdate);
filterFn("app", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(3);
expect(filteredOptions.value).toEqual(["apple"]);
});
it("should maintain state consistency across multiple operations", async () => {
const options = ref(["apple", "banana"]);
const searchKey = "name";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
// Initial state
expect(filteredOptions.value).toEqual(["apple", "banana"]);
// Filter
filterFn("app", mockUpdate);
expect(filteredOptions.value).toEqual(["apple"]);
// Reset
filterFn("", mockUpdate);
expect(filteredOptions.value).toEqual(["apple", "banana"]);
// Change original options
options.value = ["cherry", "date"];
await nextTick();
expect(filteredOptions.value).toEqual(["cherry", "date"]);
});
it("should handle complex object structures with deep filtering", () => {
const options = ref([
{
user: { name: "John Doe", email: "john@example.com" },
role: "admin",
displayName: "John Admin",
},
{
user: { name: "Jane Smith", email: "jane@example.com" },
role: "user",
displayName: "Jane User",
},
]);
const searchKey = "displayName";
const { filteredOptions, filterFn } = useSelectAutoComplete(options, searchKey);
filterFn("admin", mockUpdate);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(filteredOptions.value).toHaveLength(1);
expect(filteredOptions.value[0].displayName).toBe("John Admin");
});
});
});