fix(grid): add result search navigation controls
This commit is contained in:
1 parent
f468eb8f47
commit
8d7acf074d
5 files changed
+146
-10
No files matched your search
@@ -1,7 +1,18 @@
|
||||
import { nextTick, ref } from "vue";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { useDataGridSearch } from "@/composables/useDataGridSearch";
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
async function flushSearchDebounce() {
|
||||
await nextTick();
|
||||
vi.runOnlyPendingTimers();
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
describe("useDataGridSearch", () => {
|
||||
it("debounces matching across columns and cells", async () => {
|
||||
vi.useFakeTimers();
|
||||
@@ -15,7 +26,6 @@ describe("useDataGridSearch", () => {
|
||||
expect(search.matches.value).toEqual([{ kind: "cell", displayRow: 0, col: 1 }]);
|
||||
// matchSet 用数值 key:(displayRow+1)*65536+col
|
||||
expect(search.matchSet.value.has((0 + 1) * 65536 + 1)).toBe(true);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("keys column-name matches with displayRow -1", async () => {
|
||||
@@ -27,7 +37,6 @@ describe("useDataGridSearch", () => {
|
||||
await nextTick();
|
||||
expect(search.matches.value).toEqual([{ kind: "column", displayRow: -1, col: 1 }]);
|
||||
expect(search.matchSet.value.has((-1 + 1) * 65536 + 1)).toBe(true);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("suggests columns and replaces only the active token", async () => {
|
||||
@@ -39,4 +48,63 @@ describe("useDataGridSearch", () => {
|
||||
expect(search.acceptSuggestion()).toBe(true);
|
||||
expect(search.searchText.value).toBe("status = customer_id");
|
||||
});
|
||||
|
||||
it("navigates forward and backward with first/last wrapping", async () => {
|
||||
vi.useFakeTimers();
|
||||
const onNavigate = vi.fn();
|
||||
const search = useDataGridSearch({
|
||||
columns: ["left", "right"],
|
||||
rows: [
|
||||
["hit", "hit"],
|
||||
["none", "hit"],
|
||||
],
|
||||
getCellSearchText: (row, column) => row[column],
|
||||
onNavigate,
|
||||
});
|
||||
search.searchText.value = "hit";
|
||||
await flushSearchDebounce();
|
||||
|
||||
expect(search.currentMatchIndex.value).toBe(0);
|
||||
search.navigateMatch(1);
|
||||
expect(search.currentMatchIndex.value).toBe(1);
|
||||
expect(onNavigate).toHaveBeenLastCalledWith({ kind: "cell", displayRow: 0, col: 1 });
|
||||
|
||||
search.navigateMatch(-1);
|
||||
expect(search.currentMatchIndex.value).toBe(0);
|
||||
search.navigateMatch(-1);
|
||||
expect(search.currentMatchIndex.value).toBe(2);
|
||||
expect(onNavigate).toHaveBeenLastCalledWith({ kind: "cell", displayRow: 1, col: 1 });
|
||||
|
||||
search.navigateMatch(1);
|
||||
expect(search.currentMatchIndex.value).toBe(0);
|
||||
});
|
||||
|
||||
it("resets navigation when results change or the query is cleared", async () => {
|
||||
vi.useFakeTimers();
|
||||
const rows = ref([["hit"], ["hit"]]);
|
||||
const onNavigate = vi.fn();
|
||||
const search = useDataGridSearch({ columns: ["value"], rows, getCellSearchText: (row, column) => row[column], onNavigate });
|
||||
search.searchText.value = "hit";
|
||||
await flushSearchDebounce();
|
||||
search.navigateMatch(1);
|
||||
expect(search.currentMatchIndex.value).toBe(1);
|
||||
|
||||
rows.value = [["hit"]];
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
expect(search.currentMatchIndex.value).toBe(0);
|
||||
expect(search.currentMatch.value).toEqual({ kind: "cell", displayRow: 0, col: 0 });
|
||||
|
||||
rows.value = [];
|
||||
await nextTick();
|
||||
expect(search.currentMatchIndex.value).toBe(-1);
|
||||
expect(search.currentMatch.value).toBeNull();
|
||||
|
||||
rows.value = [["hit"]];
|
||||
await nextTick();
|
||||
search.searchText.value = "";
|
||||
await nextTick();
|
||||
expect(search.matches.value).toEqual([]);
|
||||
expect(search.currentMatchIndex.value).toBe(-1);
|
||||
});
|
||||
});
|
||||
@@ -91,8 +91,11 @@ export function useDataGridSearch<Row>(options: UseDataGridSearchOptions<Row>) {
|
||||
}
|
||||
|
||||
function navigateMatch(delta: number) {
|
||||
if (!matches.value.length) return;
|
||||
currentMatchIndex.value = (currentMatchIndex.value + delta + matches.value.length) % matches.value.length;
|
||||
const matchCount = matches.value.length;
|
||||
if (!matchCount) return;
|
||||
// Results may change between input and navigation; recover from a stale index in the requested direction.
|
||||
const currentIndex = currentMatchIndex.value >= 0 && currentMatchIndex.value < matchCount ? currentMatchIndex.value : delta < 0 ? 0 : -1;
|
||||
currentMatchIndex.value = (((currentIndex + delta) % matchCount) + matchCount) % matchCount;
|
||||
const match = currentMatch.value;
|
||||
if (match) options.onNavigate?.(match);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user