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 (excludeTerms.some((t) => kw.includes(t))) return false;
|
||||||
|
|
||||||
if (filters.minDesktopPos || filters.maxDesktopPos) {
|
if (
|
||||||
const min = filters.minDesktopPos ? Number(filters.minDesktopPos) : 0;
|
!matchesPositionFilter(
|
||||||
const max = filters.maxDesktopPos
|
row.desktop.position,
|
||||||
? Number(filters.maxDesktopPos)
|
filters.minDesktopPos,
|
||||||
: Infinity;
|
filters.maxDesktopPos,
|
||||||
if (row.desktop.position === null) return false;
|
)
|
||||||
if (row.desktop.position < min || row.desktop.position > max)
|
)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.minMobilePos || filters.maxMobilePos) {
|
if (
|
||||||
const min = filters.minMobilePos ? Number(filters.minMobilePos) : 0;
|
!matchesPositionFilter(
|
||||||
const max = filters.maxMobilePos
|
row.mobile.position,
|
||||||
? Number(filters.maxMobilePos)
|
filters.minMobilePos,
|
||||||
: Infinity;
|
filters.maxMobilePos,
|
||||||
if (row.mobile.position === null) return false;
|
)
|
||||||
if (row.mobile.position < min || row.mobile.position > max) return false;
|
)
|
||||||
}
|
return false;
|
||||||
|
|
||||||
return true;
|
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 {
|
export function countActiveFilters(filters: Filters): number {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
if (filters.include) count++;
|
if (filters.include) count++;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user