740f101242
Co-authored-by: Yaowei Zheng <hiyouga@buaa.edu.cn>
173 lines
5.1 KiB
TypeScript
173 lines
5.1 KiB
TypeScript
/**
|
|
* Unit tests for the Evaluation center's Score-only chart helpers: Score extraction,
|
|
* dynamic y-axis range, gap segmentation across runtime series, and runtime grouping.
|
|
*/
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
lineSegments,
|
|
modelSeries,
|
|
scoreScale,
|
|
scoreValues,
|
|
seriesValues,
|
|
} from "../src/features/benchmark/benchmark-metrics";
|
|
|
|
const evaluations = [{ score: 60 }, { score: 75.25 }, { score: 85.5 }];
|
|
|
|
describe("scoreValues", () => {
|
|
it("extracts stored Scores and treats non-finite input as a gap", () => {
|
|
expect(scoreValues(evaluations)).toEqual([60, 75.25, 85.5]);
|
|
expect(scoreValues([{ score: Number.NaN }, { score: Infinity }])).toEqual([null, null]);
|
|
});
|
|
});
|
|
|
|
describe("scoreScale (dynamic padded Score axis)", () => {
|
|
it("pads observed scores, clamps to 0..100, and rounds outward to friendly ticks", () => {
|
|
expect(scoreScale([71, 83.67, 88.33])).toEqual({
|
|
min: 60,
|
|
max: 100,
|
|
ticks: [60, 70, 80, 90, 100],
|
|
});
|
|
});
|
|
|
|
it("keeps a dynamic range for a single or repeated score", () => {
|
|
expect(scoreScale([88])).toEqual({
|
|
min: 75,
|
|
max: 100,
|
|
ticks: [75, 80, 85, 90, 95, 100],
|
|
});
|
|
expect(scoreScale([50, 50])).toEqual({
|
|
min: 40,
|
|
max: 60,
|
|
ticks: [40, 45, 50, 55, 60],
|
|
});
|
|
});
|
|
|
|
it("clamps boundary scores and falls back safely when every value is missing", () => {
|
|
expect(scoreScale([100])).toEqual({
|
|
min: 90,
|
|
max: 100,
|
|
ticks: [90, 92, 94, 96, 98, 100],
|
|
});
|
|
expect(scoreScale([0])).toEqual({
|
|
min: 0,
|
|
max: 10,
|
|
ticks: [0, 2, 4, 6, 8, 10],
|
|
});
|
|
expect(scoreScale([null, null])).toEqual({
|
|
min: 0,
|
|
max: 100,
|
|
ticks: [0, 20, 40, 60, 80, 100],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("lineSegments (gap segmentation)", () => {
|
|
it("no gaps: one segment with everything (consecutive indexes)", () => {
|
|
expect(lineSegments([60, 75.25, 85.5])).toEqual([
|
|
[
|
|
{ index: 0, value: 60 },
|
|
{ index: 1, value: 75.25 },
|
|
{ index: 2, value: 85.5 },
|
|
],
|
|
]);
|
|
});
|
|
|
|
it("a middle gap breaks into two segments (a lone point still forms a segment: point drawn, no line)", () => {
|
|
expect(lineSegments([0.12, null, 0.2])).toEqual([
|
|
[{ index: 0, value: 0.12 }],
|
|
[{ index: 2, value: 0.2 }],
|
|
]);
|
|
expect(lineSegments([null, 1, 2, null, 3])).toEqual([
|
|
[
|
|
{ index: 1, value: 1 },
|
|
{ index: 2, value: 2 },
|
|
],
|
|
[{ index: 4, value: 3 }],
|
|
]);
|
|
});
|
|
|
|
it("all missing / empty list: no segments", () => {
|
|
expect(lineSegments([null, null])).toEqual([]);
|
|
expect(lineSegments([])).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("modelSeries / seriesValues (curves split by model ID and thinking level)", () => {
|
|
const mixed = [
|
|
{
|
|
score: 6,
|
|
provider: "deepseek",
|
|
modelId: "deepseek-v4-flash",
|
|
thinkingLevel: "medium",
|
|
},
|
|
{ score: 7 }, // Defensive untagged input -> trailing gray series.
|
|
{
|
|
score: 7.5,
|
|
provider: "deepseek",
|
|
modelId: "deepseek-v4-pro",
|
|
thinkingLevel: "xhigh",
|
|
},
|
|
{
|
|
score: 8.5,
|
|
provider: "deepseek",
|
|
modelId: "deepseek-v4-pro",
|
|
thinkingLevel: "xhigh",
|
|
},
|
|
];
|
|
|
|
it("groups by (modelId, thinkingLevel) in first-appearance order; untagged records go to a trailing unnamed series", () => {
|
|
const series = modelSeries(mixed);
|
|
expect(series.map((s) => s.modelId)).toEqual([
|
|
"deepseek-v4-flash",
|
|
"deepseek-v4-pro",
|
|
undefined,
|
|
]);
|
|
expect(series.map((s) => s.thinkingLevel)).toEqual(["medium", "xhigh", undefined]);
|
|
expect(series.map((s) => s.indices)).toEqual([[0], [2, 3], [1]]);
|
|
expect(series[2]!.key).toBe("");
|
|
});
|
|
|
|
it("the same model ID and thinking level across providers stays in one series", () => {
|
|
const sameRuntime = [
|
|
{
|
|
score: 1,
|
|
provider: "moonshot",
|
|
modelId: "kimi-k2.6",
|
|
thinkingLevel: "medium",
|
|
},
|
|
{
|
|
score: 2,
|
|
provider: "siliconflow",
|
|
modelId: "kimi-k2.6",
|
|
thinkingLevel: "medium",
|
|
},
|
|
];
|
|
const series = modelSeries(sameRuntime);
|
|
expect(series).toHaveLength(1);
|
|
expect(series[0]!.indices).toEqual([0, 1]);
|
|
});
|
|
|
|
it("the same model ID at different thinking levels forms separate series", () => {
|
|
const levels = [
|
|
{ score: 1, modelId: "deepseek-v4-pro", thinkingLevel: "medium" },
|
|
{ score: 2, modelId: "deepseek-v4-pro", thinkingLevel: "xhigh" },
|
|
];
|
|
const series = modelSeries(levels);
|
|
expect(series).toHaveLength(2);
|
|
expect(series.map((s) => s.thinkingLevel)).toEqual(["medium", "xhigh"]);
|
|
});
|
|
|
|
it("seriesValues: indexes outside the series are null (skipped points), keeping the global time axis", () => {
|
|
const series = modelSeries(mixed);
|
|
expect(seriesValues(mixed, series[1]!)).toEqual([null, null, 7.5, 8.5]);
|
|
expect(seriesValues(mixed, series[2]!)).toEqual([null, 7, null, null]);
|
|
});
|
|
|
|
it("all untagged defensive input forms one unnamed series", () => {
|
|
const series = modelSeries([{}, {}]);
|
|
expect(series).toHaveLength(1);
|
|
expect(series[0]!.key).toBe("");
|
|
expect(series[0]!.indices).toEqual([0, 1]);
|
|
});
|
|
});
|