fix rank tracking unranked filters (#18)
Co-authored-by: Granata005 <granata005@gmail.com>
This commit is contained in:
parent
6b7d0464f7
commit
6f50850b5a
@ -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"]);
|
||||
});
|
||||
});
|
||||
@ -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)
|
||||
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++;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user