fix rank tracking unranked filters (#18)

Co-authored-by: Granata005 <granata005@gmail.com>
This commit is contained in:
Ben Senescu 2026-05-06 10:46:33 -04:00 committed by GitHub
parent 6b7d0464f7
commit 6f50850b5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 118 additions and 17 deletions

View File

@ -0,0 +1,86 @@
import { describe, expect, it } from "vitest";
import type { RankTrackingRow } from "@/types/schemas/rank-tracking";
import {
applyFilters,
EMPTY_FILTERS,
matchesPositionFilter,
type Filters,
} from "./RankTrackingFilters";
function makeRow(
keyword: string,
desktopPosition: number | null,
mobilePosition: number | null,
): RankTrackingRow {
return {
trackingKeywordId: keyword,
keyword,
searchVolume: null,
keywordDifficulty: null,
cpc: null,
desktop: {
position: desktopPosition,
previousPosition: null,
rankingUrl: null,
serpFeatures: [],
},
mobile: {
position: mobilePosition,
previousPosition: null,
rankingUrl: null,
serpFeatures: [],
},
};
}
function withFilters(overrides: Partial<Filters>): Filters {
return { ...EMPTY_FILTERS, ...overrides };
}
describe("matchesPositionFilter", () => {
it("matches only unranked positions when max is zero", () => {
expect(matchesPositionFilter(null, "", "0")).toBe(true);
expect(matchesPositionFilter(1, "", "0")).toBe(false);
expect(matchesPositionFilter(20, "10", "0")).toBe(false);
});
it("keeps regular rank ranges unchanged", () => {
expect(matchesPositionFilter(4, "1", "10")).toBe(true);
expect(matchesPositionFilter(11, "1", "10")).toBe(false);
expect(matchesPositionFilter(null, "1", "10")).toBe(false);
});
});
describe("applyFilters", () => {
const rows = [
makeRow("ranked both", 3, 6),
makeRow("desktop unranked", null, 5),
makeRow("mobile unranked", 7, null),
makeRow("unranked both", null, null),
];
it("filters desktop unranked rows with desktop max zero", () => {
expect(
applyFilters(rows, withFilters({ maxDesktopPos: "0" })).map(
(row) => row.keyword,
),
).toEqual(["desktop unranked", "unranked both"]);
});
it("filters mobile unranked rows with mobile max zero", () => {
expect(
applyFilters(rows, withFilters({ maxMobilePos: "0" })).map(
(row) => row.keyword,
),
).toEqual(["mobile unranked", "unranked both"]);
});
it("requires both devices to be unranked when both max values are zero", () => {
expect(
applyFilters(
rows,
withFilters({ maxDesktopPos: "0", maxMobilePos: "0" }),
).map((row) => row.keyword),
).toEqual(["unranked both"]);
});
});

View File

@ -162,29 +162,44 @@ export function applyFilters(
if (excludeTerms.some((t) => kw.includes(t))) return false;
if (filters.minDesktopPos || filters.maxDesktopPos) {
const min = filters.minDesktopPos ? Number(filters.minDesktopPos) : 0;
const max = filters.maxDesktopPos
? Number(filters.maxDesktopPos)
: Infinity;
if (row.desktop.position === null) return false;
if (row.desktop.position < min || row.desktop.position > max)
return false;
}
if (
!matchesPositionFilter(
row.desktop.position,
filters.minDesktopPos,
filters.maxDesktopPos,
)
)
return false;
if (filters.minMobilePos || filters.maxMobilePos) {
const min = filters.minMobilePos ? Number(filters.minMobilePos) : 0;
const max = filters.maxMobilePos
? Number(filters.maxMobilePos)
: Infinity;
if (row.mobile.position === null) return false;
if (row.mobile.position < min || row.mobile.position > max) return false;
}
if (
!matchesPositionFilter(
row.mobile.position,
filters.minMobilePos,
filters.maxMobilePos,
)
)
return false;
return true;
});
}
export function matchesPositionFilter(
position: number | null,
minValue: string,
maxValue: string,
): boolean {
if (!minValue && !maxValue) return true;
const max = maxValue === "" ? Infinity : Number(maxValue);
if (max === 0) return position === null;
if (position === null) return false;
const min = minValue === "" ? 0 : Number(minValue);
return position >= min && position <= max;
}
export function countActiveFilters(filters: Filters): number {
let count = 0;
if (filters.include) count++;