fix(search-tabs): keep tabs without explicit location across reloads (#108)

This commit is contained in:
Xiaonan Cui 2026-07-23 22:00:26 +08:00 committed by GitHub
parent 3edbd0c11e
commit d3c62aa5a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 125 additions and 5 deletions

View File

@ -0,0 +1,103 @@
import { describe, expect, it } from "vitest";
import { parseStoredState } from "./useSearchTabs";
function persistedTab(input: unknown) {
return {
id: "tab-1",
label: "example",
createdAt: 1,
viewedAt: null,
input,
};
}
describe("parseStoredState", () => {
it("keeps domain tabs persisted without a locationCode (default location)", () => {
const state = parseStoredState({
activeTabId: "tab-1",
tabs: [
persistedTab({
type: "domain",
domain: "example.com",
subdomains: true,
}),
],
});
expect(state.tabs).toHaveLength(1);
expect(state.activeTabId).toBe("tab-1");
expect(state.tabs[0].input).toEqual({
type: "domain",
domain: "example.com",
subdomains: true,
locationCode: undefined,
});
});
it("keeps keyword tabs persisted without a locationCode (default location)", () => {
const state = parseStoredState({
activeTabId: "tab-1",
tabs: [
persistedTab({
type: "keyword",
keyword: "seo tools",
resultLimit: 150,
mode: "auto",
clickstream: false,
}),
],
});
expect(state.tabs).toHaveLength(1);
expect(state.activeTabId).toBe("tab-1");
expect(state.tabs[0].input).toEqual({
type: "keyword",
keyword: "seo tools",
locationCode: undefined,
resultLimit: 150,
mode: "auto",
clickstream: false,
});
});
it("keeps tabs persisted with an explicit locationCode", () => {
const state = parseStoredState({
activeTabId: "tab-1",
tabs: [
persistedTab({
type: "domain",
domain: "example.com",
subdomains: false,
locationCode: 2840,
}),
],
});
expect(state.tabs).toHaveLength(1);
expect(state.tabs[0].input).toMatchObject({ locationCode: 2840 });
});
it("still rejects malformed tab inputs", () => {
const state = parseStoredState({
activeTabId: null,
tabs: [
persistedTab({ type: "domain", subdomains: true }),
persistedTab({
type: "keyword",
keyword: "seo tools",
resultLimit: 999,
mode: "auto",
}),
persistedTab({
type: "domain",
domain: "example.com",
subdomains: true,
locationCode: "us",
}),
persistedTab({ type: "unknown" }),
],
});
expect(state.tabs).toHaveLength(0);
});
});

View File

@ -44,18 +44,33 @@ function parseTabInput(value: unknown): SearchTabInput | null {
if (value.type === "domain") {
if (typeof value.domain !== "string" || value.domain === "") return null;
if (typeof value.subdomains !== "boolean") return null;
if (typeof value.locationCode !== "number") return null;
// locationCode is optional in DomainSearchTabInput: tabs opened at the
// default location persist no loc param, so accept a missing key.
if (
value.locationCode !== undefined &&
typeof value.locationCode !== "number"
) {
return null;
}
return {
type: "domain",
domain: value.domain,
subdomains: value.subdomains,
locationCode: value.locationCode,
locationCode:
typeof value.locationCode === "number" ? value.locationCode : undefined,
};
}
if (value.type === "keyword") {
if (typeof value.keyword !== "string" || value.keyword === "") return null;
if (typeof value.locationCode !== "number") return null;
// locationCode is optional in KeywordSearchTabInput: tabs opened at the
// default location persist no loc param, so accept a missing key.
if (
value.locationCode !== undefined &&
typeof value.locationCode !== "number"
) {
return null;
}
if (
value.resultLimit !== 150 &&
value.resultLimit !== 300 &&
@ -74,7 +89,8 @@ function parseTabInput(value: unknown): SearchTabInput | null {
return {
type: "keyword",
keyword: value.keyword,
locationCode: value.locationCode,
locationCode:
typeof value.locationCode === "number" ? value.locationCode : undefined,
resultLimit: value.resultLimit,
mode: value.mode,
// Tabs persisted before the clickstream toggle existed default to off.
@ -93,7 +109,8 @@ function tabInputKey(value: unknown): string {
return JSON.stringify(value);
}
function parseStoredState(value: unknown): TabsState {
// Exported for unit tests.
export function parseStoredState(value: unknown): TabsState {
if (!isRecord(value)) return EMPTY_STATE;
if (!Array.isArray(value.tabs)) return EMPTY_STATE;
const tabs = value.tabs