fix(search-tabs): keep tabs without explicit location across reloads (#108)
This commit is contained in:
parent
3edbd0c11e
commit
d3c62aa5a2
103
src/client/features/search-tabs/useSearchTabs.test.ts
Normal file
103
src/client/features/search-tabs/useSearchTabs.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -44,18 +44,33 @@ function parseTabInput(value: unknown): SearchTabInput | null {
|
|||||||
if (value.type === "domain") {
|
if (value.type === "domain") {
|
||||||
if (typeof value.domain !== "string" || value.domain === "") return null;
|
if (typeof value.domain !== "string" || value.domain === "") return null;
|
||||||
if (typeof value.subdomains !== "boolean") 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 {
|
return {
|
||||||
type: "domain",
|
type: "domain",
|
||||||
domain: value.domain,
|
domain: value.domain,
|
||||||
subdomains: value.subdomains,
|
subdomains: value.subdomains,
|
||||||
locationCode: value.locationCode,
|
locationCode:
|
||||||
|
typeof value.locationCode === "number" ? value.locationCode : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.type === "keyword") {
|
if (value.type === "keyword") {
|
||||||
if (typeof value.keyword !== "string" || value.keyword === "") return null;
|
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 (
|
if (
|
||||||
value.resultLimit !== 150 &&
|
value.resultLimit !== 150 &&
|
||||||
value.resultLimit !== 300 &&
|
value.resultLimit !== 300 &&
|
||||||
@ -74,7 +89,8 @@ function parseTabInput(value: unknown): SearchTabInput | null {
|
|||||||
return {
|
return {
|
||||||
type: "keyword",
|
type: "keyword",
|
||||||
keyword: value.keyword,
|
keyword: value.keyword,
|
||||||
locationCode: value.locationCode,
|
locationCode:
|
||||||
|
typeof value.locationCode === "number" ? value.locationCode : undefined,
|
||||||
resultLimit: value.resultLimit,
|
resultLimit: value.resultLimit,
|
||||||
mode: value.mode,
|
mode: value.mode,
|
||||||
// Tabs persisted before the clickstream toggle existed default to off.
|
// Tabs persisted before the clickstream toggle existed default to off.
|
||||||
@ -93,7 +109,8 @@ function tabInputKey(value: unknown): string {
|
|||||||
return JSON.stringify(value);
|
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 (!isRecord(value)) return EMPTY_STATE;
|
||||||
if (!Array.isArray(value.tabs)) return EMPTY_STATE;
|
if (!Array.isArray(value.tabs)) return EMPTY_STATE;
|
||||||
const tabs = value.tabs
|
const tabs = value.tabs
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user