diff --git a/web/libs/editor/src/components/TaskSummary/TaskSummary.test.tsx b/web/libs/editor/src/components/TaskSummary/TaskSummary.test.tsx
deleted file mode 100644
index fdd98c3a264a..000000000000
--- a/web/libs/editor/src/components/TaskSummary/TaskSummary.test.tsx
+++ /dev/null
@@ -1,428 +0,0 @@
-import { render, screen } from "@testing-library/react";
-import type { MSTAnnotation, MSTStore } from "../../stores/types";
-import TaskSummary from "./TaskSummary";
-
-// Mock external dependencies only
-jest.mock("@humansignal/ui", () => ({
- Tooltip: ({ title, children }: { title: string; children: React.ReactNode }) => (
-
{children}
- ),
- Userpic: ({ user, children, className }: { user: any; children?: React.ReactNode; className?: string }) => (
-
- {children || user?.displayName?.[0] || "U"}
-
- ),
-}));
-
-jest.mock("@humansignal/icons", () => ({
- IconInfoOutline: ({ size, style }: { size?: number; style?: any }) => (
-
- ℹ
-
- ),
- IconSparks: ({ style }: { style?: any }) => (
-
- ✨
-
- ),
-}));
-
-// Mock the labelings renderers module
-jest.mock("./labelings", () => ({
- renderers: {
- choices: (results: any[], control: any) => {
- const choices = results.flatMap((r) => r.value?.choices || []);
- return choices.length > 0 ? choices.join(", ") : "-";
- },
- textarea: (results: any[], control: any) => {
- const texts = results.map((r) => r.value?.text).filter(Boolean);
- return texts.length > 0 ? texts.join(", ") : "-";
- },
- },
-}));
-
-describe("TaskSummary", () => {
- const createMockAnnotation = (overrides: Partial = {}): MSTAnnotation =>
- ({
- id: "1",
- pk: 1,
- type: "annotation",
- user: { id: 1, displayName: "John Doe" },
- createdBy: "John Doe",
- versions: {
- result: [{ from_name: "label", to_name: "text", type: "choices", value: { choices: ["positive"] } }],
- },
- results: [],
- ...overrides,
- }) as MSTAnnotation;
-
- const createMockControlTag = (name: string, type = "choices") => [
- name,
- {
- isControlTag: true,
- type,
- toname: "text",
- perregion: false,
- children: [
- { value: "positive", background: "#ff0000" },
- { value: "negative", background: "#00ff00" },
- ],
- },
- ];
-
- const createMockObjectTag = (name: string, type = "text") => [
- name,
- {
- isObjectTag: true,
- type,
- value: "$text",
- _value: "Sample text content",
- },
- ];
-
- const createMockStore = (overrides: any = {}): MSTStore["annotationStore"] =>
- ({
- store: {
- task: {
- dataObj: { text: "Sample text", id: 1 },
- agreement: 85.5,
- ...overrides.task,
- },
- project: {
- review_settings: {
- show_agreement_to_reviewers: true,
- },
- ...overrides.project,
- },
- ...overrides.store,
- },
- names: new Map([createMockControlTag("label"), createMockObjectTag("text"), ...(overrides.names || [])]),
- ...overrides,
- }) as MSTStore["annotationStore"];
-
- it("renders the review summary heading", () => {
- const annotations = [createMockAnnotation()];
- const store = createMockStore();
-
- render();
-
- expect(screen.getByText("Review Summary")).toBeInTheDocument();
- });
-
- it("renders the task data heading", () => {
- const annotations = [createMockAnnotation()];
- const store = createMockStore();
-
- render();
-
- expect(screen.getByText("Task Data")).toBeInTheDocument();
- });
-
- it("displays agreement when show_agreement_to_reviewers is true", () => {
- const annotations = [createMockAnnotation()];
- const store = createMockStore({
- store: {
- project: {
- review_settings: {
- show_agreement_to_reviewers: true,
- },
- },
- },
- });
-
- render();
-
- expect(screen.getByText("Agreement")).toBeInTheDocument();
- expect(screen.getByText("85.50%")).toBeInTheDocument();
- expect(screen.getByText("Overall agreement over all submitted annotations")).toBeInTheDocument();
- });
-
- it("hides agreement when show_agreement_to_reviewers is false", () => {
- const annotations = [createMockAnnotation()];
- const store = createMockStore({
- store: {
- project: {
- review_settings: {
- show_agreement_to_reviewers: false,
- },
- },
- },
- });
-
- render();
-
- expect(screen.queryByText("Agreement")).not.toBeInTheDocument();
- expect(screen.queryByText("85.50%")).not.toBeInTheDocument();
- });
-
- it("hides agreement when project settings are not available", () => {
- const annotations = [createMockAnnotation()];
- const store = createMockStore({
- store: {
- project: null,
- },
- });
-
- render();
-
- expect(screen.queryByText("Agreement")).not.toBeInTheDocument();
- });
-
- it("counts submitted annotations correctly", () => {
- const annotations = [
- createMockAnnotation({ pk: 1, type: "annotation" }),
- createMockAnnotation({ pk: 2, type: "annotation" }),
- createMockAnnotation({ pk: 0, type: "annotation" }), // draft (pk: 0)
- ];
- const store = createMockStore();
-
- render();
-
- expect(screen.getByText("Annotations")).toBeInTheDocument();
- expect(screen.getByText("2")).toBeInTheDocument(); // Only submitted annotations
- expect(screen.getByText("Number of submitted annotations")).toBeInTheDocument();
- });
-
- it("excludes annotations without results from count", () => {
- const annotations = [
- createMockAnnotation({ pk: 1, type: "annotation" }),
- createMockAnnotation({ pk: 2, type: "annotation", versions: { result: [] } }), // no results
- createMockAnnotation({ pk: 3, type: "annotation", versions: { result: undefined } }), // undefined results
- ];
- const store = createMockStore();
-
- render();
-
- expect(screen.getByText("1")).toBeInTheDocument(); // Only annotation with results
- });
-
- it("counts predictions correctly", () => {
- const annotations = [
- createMockAnnotation({ pk: 1, type: "annotation" }),
- createMockAnnotation({ pk: 2, type: "prediction" }),
- createMockAnnotation({ pk: 3, type: "prediction" }),
- ];
- const store = createMockStore();
-
- render();
-
- expect(screen.getByText("Predictions")).toBeInTheDocument();
- expect(screen.getByText("2")).toBeInTheDocument(); // Only predictions
- expect(screen.getByText("Number of predictions")).toBeInTheDocument();
- });
-
- it("filters out annotations without pk (drafts)", () => {
- const annotations = [
- createMockAnnotation({ pk: 1 }),
- createMockAnnotation({ pk: 0 }), // draft
- createMockAnnotation({ pk: undefined }), // draft
- ];
- const store = createMockStore();
-
- render();
-
- // Should only show one annotation in the labeling summary table
- expect(screen.getByText("John Doe")).toBeInTheDocument();
- expect(screen.getByText("#1")).toBeInTheDocument();
- });
-
- it("renders labeling summary table with correct headers", () => {
- const annotations = [createMockAnnotation()];
- const store = createMockStore({
- names: new Map([
- createMockControlTag("sentiment", "choices"),
- createMockControlTag("category", "choices"),
- createMockObjectTag("text"),
- ]),
- });
-
- render();
-
- expect(screen.getByText("Annotation ID")).toBeInTheDocument();
- expect(screen.getByText("sentiment")).toBeInTheDocument();
- expect(screen.getByText("category")).toBeInTheDocument();
- expect(screen.getByText("choices")).toBeInTheDocument(); // badge type
- });
-
- it("displays annotation results in labeling summary", () => {
- const annotations = [
- createMockAnnotation({
- pk: 1,
- user: { id: 1, displayName: "Alice" },
- versions: {
- result: [{ from_name: "sentiment", to_name: "text", type: "choices", value: { choices: ["positive"] } }],
- },
- }),
- ];
- const store = createMockStore({
- names: new Map([createMockControlTag("sentiment", "choices"), createMockObjectTag("text")]),
- });
-
- render();
-
- expect(screen.getByText("Alice")).toBeInTheDocument();
- expect(screen.getByText("#1")).toBeInTheDocument();
- expect(screen.getByText("positive")).toBeInTheDocument();
- });
-
- it("shows prediction icon for prediction annotations", () => {
- const annotations = [
- createMockAnnotation({
- pk: 1,
- type: "prediction",
- user: { id: 1, displayName: "Model" },
- results: [{ toJSON: () => ({ from_name: "sentiment", type: "choices", value: { choices: ["positive"] } }) }],
- }),
- ];
- const store = createMockStore({
- names: new Map([createMockControlTag("sentiment", "choices"), createMockObjectTag("text")]),
- });
-
- render();
-
- expect(screen.getByTestId("sparks-icon")).toBeInTheDocument();
- expect(screen.getByText("Model")).toBeInTheDocument();
- });
-
- it("renders data summary table correctly", () => {
- const annotations = [createMockAnnotation()];
- const store = createMockStore({
- store: {
- task: {
- dataObj: { text: "Sample text", image: "image.jpg" },
- },
- },
- names: new Map([
- createMockControlTag("label"),
- createMockObjectTag("text", "text"),
- createMockObjectTag("image", "image"),
- ]),
- });
-
- render();
-
- expect(screen.getByText("text")).toBeInTheDocument();
- expect(screen.getByText("image")).toBeInTheDocument();
- });
-
- it("handles control tags with per_region setting", () => {
- const annotations = [createMockAnnotation()];
- const controlWithPerRegion = createMockControlTag("regionLabel");
- controlWithPerRegion[1].perregion = true;
-
- const store = createMockStore({
- names: new Map([controlWithPerRegion]),
- });
-
- render();
-
- expect(screen.getByText("regionLabel")).toBeInTheDocument();
- });
-
- it("handles control tags without children", () => {
- const annotations = [createMockAnnotation()];
- const controlWithoutChildren = createMockControlTag("simpleLabel");
- controlWithoutChildren[1].children = undefined;
-
- const store = createMockStore({
- names: new Map([controlWithoutChildren]),
- });
-
- render();
-
- expect(screen.getByText("simpleLabel")).toBeInTheDocument();
- });
-
- it("handles object tags with parsedValue", () => {
- const annotations = [createMockAnnotation()];
- const objectWithParsedValue = createMockObjectTag("image", "image");
- objectWithParsedValue[1].parsedValue = "parsed-image-url.jpg";
-
- const store = createMockStore({
- names: new Map([objectWithParsedValue]),
- });
-
- render();
-
- expect(screen.getByText("image")).toBeInTheDocument();
- });
-
- it("handles empty annotations array", () => {
- const annotations: MSTAnnotation[] = [];
- const store = createMockStore();
-
- render();
-
- expect(screen.getByText("0")).toBeInTheDocument(); // annotations count should be 0
- });
-
- it("handles missing task agreement", () => {
- const annotations = [createMockAnnotation()];
- const store = createMockStore({
- store: {
- task: {
- agreement: undefined,
- },
- project: {
- review_settings: {
- show_agreement_to_reviewers: true,
- },
- },
- },
- });
-
- render();
-
- // Should not display agreement when it's undefined
- expect(screen.queryByText("Agreement")).not.toBeInTheDocument();
- });
-
- it("displays correct info messages with tooltips", () => {
- const annotations = [createMockAnnotation({ type: "annotation" }), createMockAnnotation({ type: "prediction" })];
- const store = createMockStore();
-
- render();
-
- expect(screen.getByText("Number of submitted annotations")).toBeInTheDocument();
- expect(screen.getByText("Number of predictions")).toBeInTheDocument();
- expect(screen.getAllByTestId("info-icon")).toHaveLength(2); // Two info icons for tooltips
- });
-
- it("shows dash when no results for a control tag", () => {
- const annotations = [
- createMockAnnotation({
- versions: {
- result: [], // no results for any control
- },
- }),
- ];
- const store = createMockStore({
- names: new Map([createMockControlTag("sentiment", "choices"), createMockObjectTag("text")]),
- });
-
- render();
-
- expect(screen.getByText("-")).toBeInTheDocument();
- });
-
- it("processes object tags correctly for data types", () => {
- const annotations = [createMockAnnotation()];
- const store = createMockStore({
- names: new Map([
- createMockControlTag("label"),
- createMockObjectTag("text", "text"),
- createMockObjectTag("image", "image"),
- ["nonObjectTag", { isObjectTag: false }], // should be filtered out
- ["objectWithoutDollar", { isObjectTag: true, value: "noDollar" }], // should be filtered out
- ]),
- });
-
- render();
-
- // Should only show object tags that have $ in their value
- expect(screen.getByText("text")).toBeInTheDocument();
- expect(screen.getByText("image")).toBeInTheDocument();
- expect(screen.queryByText("nonObjectTag")).not.toBeInTheDocument();
- expect(screen.queryByText("objectWithoutDollar")).not.toBeInTheDocument();
- });
-});
diff --git a/web/libs/editor/src/components/TaskSummary/__tests__/TaskSummary.test.tsx b/web/libs/editor/src/components/TaskSummary/__tests__/TaskSummary.test.tsx
new file mode 100644
index 000000000000..13c79eb281f9
--- /dev/null
+++ b/web/libs/editor/src/components/TaskSummary/__tests__/TaskSummary.test.tsx
@@ -0,0 +1,362 @@
+import { render, screen } from "@testing-library/react";
+import type { MSTAnnotation, MSTStore } from "../../../stores/types";
+import TaskSummary from "../TaskSummary";
+
+// Polyfill for Object.groupBy which may not be available in test environment
+if (!Object.groupBy) {
+ Object.groupBy = (
+ items: Iterable,
+ keySelector: (item: T, index: number) => K,
+ ): Partial> => {
+ const result: Partial> = {};
+ let index = 0;
+ for (const item of items) {
+ const key = keySelector(item, index++);
+ if (!result[key]) {
+ result[key] = [];
+ }
+ (result[key] as T[]).push(item);
+ }
+ return result;
+ };
+}
+
+// Mock global APP_SETTINGS for user context
+Object.defineProperty(window, "APP_SETTINGS", {
+ value: {
+ user: {
+ id: 1,
+ displayName: "Test User",
+ },
+ },
+ writable: true,
+});
+
+describe("TaskSummary", () => {
+ interface MockUser {
+ id: number;
+ displayName: string;
+ firstName: string;
+ lastName: string;
+ username: string;
+ email: string;
+ initials: string;
+ avatar: string | null;
+ active: boolean;
+ }
+
+ interface MockControlTag {
+ isControlTag: boolean;
+ type: string;
+ toname: string;
+ perregion?: boolean;
+ children?: Array<{ value: string; background: string }>;
+ }
+
+ interface MockObjectTag {
+ isObjectTag: boolean;
+ type: string;
+ value: string;
+ _value?: string;
+ parsedValue?: string;
+ _url?: string;
+ dataObj?: Record;
+ }
+
+ const createMockUser = (overrides: Partial = {}): MockUser => ({
+ id: 1,
+ displayName: "John Doe",
+ firstName: "John",
+ lastName: "Doe",
+ username: "johndoe",
+ email: "john@example.com",
+ initials: "JD",
+ avatar: null,
+ active: true,
+ ...overrides,
+ });
+
+ const createMockAnnotation = (overrides: Partial = {}): MSTAnnotation =>
+ ({
+ id: "1",
+ pk: "1",
+ type: "annotation",
+ user: createMockUser(),
+ createdBy: "John Doe",
+ versions: {
+ result: [{ from_name: "label", to_name: "text", type: "choices", value: { choices: ["positive"] } }],
+ },
+ results: [],
+ ...overrides,
+ }) as MSTAnnotation;
+
+ const createMockControlTag = (name: string, type = "choices"): [string, MockControlTag] => [
+ name,
+ {
+ isControlTag: true,
+ type,
+ toname: "text",
+ perregion: false,
+ children: [
+ { value: "positive", background: "#ff0000" },
+ { value: "negative", background: "#00ff00" },
+ ],
+ },
+ ];
+
+ const createMockObjectTag = (name: string, type = "text"): [string, MockObjectTag] => [
+ name,
+ {
+ isObjectTag: true,
+ type,
+ value: `$${name}`, // Need $ prefix for object tags
+ _value: "Sample text content",
+ },
+ ];
+
+ interface MockStoreOverrides {
+ task?: {
+ dataObj?: Record;
+ agreement?: number;
+ };
+ project?: {
+ review_settings?: {
+ show_agreement_to_reviewers?: boolean;
+ };
+ } | null;
+ store?: Record;
+ names?: Array<[string, MockControlTag | MockObjectTag]>;
+ }
+
+ const createMockStore = (overrides: MockStoreOverrides = {}): MSTStore["annotationStore"] => {
+ const defaultNames = [createMockControlTag("label"), createMockObjectTag("text")];
+ const allNames = [...defaultNames, ...(overrides.names || [])];
+
+ const mockStore = {
+ store: {
+ task: {
+ dataObj: { text: "Sample text", id: 1 },
+ agreement: 85.5,
+ ...overrides.task,
+ },
+ project: {
+ review_settings: {
+ show_agreement_to_reviewers: true,
+ },
+ ...overrides.project,
+ },
+ hasInterface: (interfaceName: string) => false,
+ ...overrides.store,
+ },
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ names: new Map(allNames as Array<[string, any]>),
+ selectAnnotation: jest.fn(),
+ selectPrediction: jest.fn(),
+ };
+
+ return mockStore as unknown as MSTStore["annotationStore"];
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("renders the main headings", () => {
+ const annotations = [createMockAnnotation()];
+ const store = createMockStore();
+
+ render();
+
+ expect(screen.getByText("Review Summary")).toBeInTheDocument();
+ expect(screen.getByText("Task Data")).toBeInTheDocument();
+ });
+
+ it("displays agreement when enabled in project settings", () => {
+ const annotations = [createMockAnnotation()];
+ const store = createMockStore({
+ project: {
+ review_settings: {
+ show_agreement_to_reviewers: true,
+ },
+ },
+ });
+
+ render();
+
+ expect(screen.getByText("Agreement")).toBeInTheDocument();
+ expect(screen.getByText("85.5%")).toBeInTheDocument();
+ });
+
+ it("shows agreement when backend provides it (regardless of frontend settings)", () => {
+ const annotations = [createMockAnnotation()];
+ const store = createMockStore({
+ project: {
+ review_settings: {
+ show_agreement_to_reviewers: false,
+ },
+ },
+ });
+
+ render();
+
+ // Backend controls agreement visibility, so if we have a number, show it
+ expect(screen.getByText("Agreement")).toBeInTheDocument();
+ expect(screen.getByText("85.5%")).toBeInTheDocument();
+ });
+
+ it("shows agreement even when project is null", () => {
+ const annotations = [createMockAnnotation()];
+ const store = createMockStore({
+ project: null,
+ });
+
+ render();
+
+ // Backend controls agreement visibility, so if we have a number, show it
+ expect(screen.getByText("Agreement")).toBeInTheDocument();
+ expect(screen.getByText("85.5%")).toBeInTheDocument();
+ });
+
+ it("counts submitted annotations correctly (excludes drafts)", () => {
+ const annotations = [
+ createMockAnnotation({ pk: "1", type: "annotation" }),
+ createMockAnnotation({ pk: "2", type: "annotation" }),
+ createMockAnnotation({ pk: undefined, type: "annotation" }), // draft - should be excluded
+ createMockAnnotation({ pk: "", type: "annotation" }), // draft - should be excluded
+ ];
+ const store = createMockStore();
+
+ render();
+
+ expect(screen.getByText("Annotations")).toBeInTheDocument();
+ expect(screen.getByText("2")).toBeInTheDocument(); // Only submitted annotations
+ });
+
+ it("counts predictions correctly", () => {
+ const annotations = [
+ createMockAnnotation({ pk: "1", type: "annotation" }),
+ createMockAnnotation({ pk: "2", type: "prediction" }),
+ createMockAnnotation({ pk: "3", type: "prediction" }),
+ createMockAnnotation({ pk: undefined, type: "prediction" }), // draft - should be excluded
+ ];
+ const store = createMockStore();
+
+ render();
+
+ expect(screen.getByText("Predictions")).toBeInTheDocument();
+ expect(screen.getByText("2")).toBeInTheDocument(); // Only submitted predictions
+ });
+
+ it("renders labeling summary table with control tags", () => {
+ const annotations = [createMockAnnotation()];
+ const store = createMockStore({
+ names: new Map([
+ createMockControlTag("sentiment", "choices"),
+ createMockControlTag("category", "choices"),
+ createMockObjectTag("text"),
+ ]),
+ });
+
+ render();
+
+ expect(screen.getByText("Annotation ID")).toBeInTheDocument();
+ expect(screen.getByText("sentiment")).toBeInTheDocument();
+ expect(screen.getByText("category")).toBeInTheDocument();
+ });
+
+ it("renders data summary table with object tags", () => {
+ const annotations = [createMockAnnotation()];
+ const store = createMockStore({
+ store: {
+ task: {
+ dataObj: { text: "Sample text", image: "image.jpg" },
+ },
+ },
+ names: new Map([
+ createMockControlTag("label"),
+ createMockObjectTag("text", "text"),
+ createMockObjectTag("image", "image"),
+ ]),
+ });
+
+ render();
+
+ // Object tags should appear in the data summary (as header and badge)
+ expect(screen.getAllByText("text")).toHaveLength(2); // header + badge
+ expect(screen.getAllByText("image")).toHaveLength(2); // header + badge
+ });
+
+ it("handles empty annotations array", () => {
+ const annotations: MSTAnnotation[] = [];
+ const store = createMockStore();
+
+ render();
+
+ // Should show 0 for both annotations and predictions
+ expect(screen.getByText("Annotations")).toBeInTheDocument();
+ expect(screen.getByText("Predictions")).toBeInTheDocument();
+ expect(screen.getAllByText("0")).toHaveLength(2);
+ });
+
+ it("handles missing task agreement gracefully", () => {
+ const annotations = [createMockAnnotation()];
+ const store = createMockStore({
+ store: {
+ task: {
+ agreement: undefined,
+ },
+ project: {
+ review_settings: {
+ show_agreement_to_reviewers: true,
+ },
+ },
+ },
+ });
+
+ render();
+
+ // Should not display agreement when it's undefined
+ expect(screen.queryByText("Agreement")).not.toBeInTheDocument();
+ });
+
+ it("processes control tags with per_region setting", () => {
+ const annotations = [createMockAnnotation()];
+ const controlWithPerRegion: [string, MockControlTag] = [
+ "regionLabel",
+ {
+ isControlTag: true,
+ type: "choices",
+ toname: "text",
+ perregion: true,
+ children: [{ value: "label1", background: "#ff0000" }],
+ },
+ ];
+
+ const store = createMockStore({
+ names: new Map([controlWithPerRegion]),
+ });
+
+ render();
+
+ expect(screen.getByText("regionLabel")).toBeInTheDocument();
+ });
+
+ it("filters object tags correctly (only those with $ in value)", () => {
+ const annotations = [createMockAnnotation()];
+ const store = createMockStore({
+ names: new Map([
+ createMockControlTag("label"),
+ createMockObjectTag("text", "text"), // has $ prefix - should be included
+ ["invalidObject", { isObjectTag: true, value: "noDollarPrefix", type: "text" }], // no $ - should be excluded
+ ["nonObject", { isObjectTag: false, value: "$text", type: "text" }], // not object tag - should be excluded
+ ]),
+ });
+
+ render();
+
+ // Only valid object tags with $ prefix should appear (as header and badge)
+ expect(screen.getAllByText("text")).toHaveLength(2); // header + badge
+ expect(screen.queryByText("invalidObject")).not.toBeInTheDocument();
+ expect(screen.queryByText("nonObject")).not.toBeInTheDocument();
+ });
+});