refactor(frontend): reduce heading text size for plan preview content (#12620)

This commit is contained in:
Hiep Le
2026-01-29 00:30:40 +07:00
committed by GitHub
parent c483c80a3c
commit d76ac44dc3
5 changed files with 295 additions and 2 deletions

View File

@@ -156,4 +156,72 @@ describe("PlanPreview", () => {
expect(container.textContent).toContain("Heading 1");
expect(container.textContent).toContain("Heading 2");
});
it("should use planHeadings components for h1 headings", () => {
// Arrange
const planContent = "# Main Title";
// Act
const { container } = renderWithProviders(
<PlanPreview planContent={planContent} />,
);
// Assert
const h1 = container.querySelector("h1");
expect(h1).toBeInTheDocument();
expect(h1).toHaveTextContent("Main Title");
});
it("should use planHeadings components for h2 headings", () => {
// Arrange
const planContent = "## Section Title";
// Act
const { container } = renderWithProviders(
<PlanPreview planContent={planContent} />,
);
// Assert
const h2 = container.querySelector("h2");
expect(h2).toBeInTheDocument();
expect(h2).toHaveTextContent("Section Title");
});
it("should use planHeadings components for h3 headings", () => {
// Arrange
const planContent = "### Subsection Title";
// Act
const { container } = renderWithProviders(
<PlanPreview planContent={planContent} />,
);
// Assert
const h3 = container.querySelector("h3");
expect(h3).toBeInTheDocument();
expect(h3).toHaveTextContent("Subsection Title");
});
it("should use planHeadings components for all heading levels", () => {
// Arrange
const planContent = `# H1 Title
## H2 Title
### H3 Title
#### H4 Title
##### H5 Title
###### H6 Title`;
// Act
const { container } = renderWithProviders(
<PlanPreview planContent={planContent} />,
);
// Assert
expect(container.querySelector("h1")).toBeInTheDocument();
expect(container.querySelector("h2")).toBeInTheDocument();
expect(container.querySelector("h3")).toBeInTheDocument();
expect(container.querySelector("h4")).toBeInTheDocument();
expect(container.querySelector("h5")).toBeInTheDocument();
expect(container.querySelector("h6")).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,188 @@
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { planHeadings } from "#/components/features/markdown/plan-headings";
describe("planHeadings", () => {
describe("h1", () => {
it("should render h1 with correct text content", () => {
// Arrange
const H1 = planHeadings.h1;
const text = "Main Heading";
// Act
render(<H1>{text}</H1>);
// Assert
const heading = screen.getByRole("heading", { level: 1 });
expect(heading).toBeInTheDocument();
expect(heading).toHaveTextContent(text);
});
it("should handle undefined children gracefully", () => {
// Arrange
const H1 = planHeadings.h1;
// Act
render(<H1>{undefined}</H1>);
// Assert
const heading = screen.getByRole("heading", { level: 1 });
expect(heading).toBeInTheDocument();
expect(heading).toBeEmptyDOMElement();
});
it("should render complex children content", () => {
// Arrange
const H1 = planHeadings.h1;
// Act
render(
<H1>
<span>Nested</span> Content
</H1>,
);
// Assert
const heading = screen.getByRole("heading", { level: 1 });
expect(heading).toHaveTextContent("Nested Content");
expect(heading.querySelector("span")).toHaveTextContent("Nested");
});
});
describe("h2", () => {
it("should render h2 with correct text content", () => {
// Arrange
const H2 = planHeadings.h2;
const text = "Section Heading";
// Act
render(<H2>{text}</H2>);
// Assert
const heading = screen.getByRole("heading", { level: 2 });
expect(heading).toBeInTheDocument();
expect(heading).toHaveTextContent(text);
});
it("should handle null children gracefully", () => {
// Arrange
const H2 = planHeadings.h2;
// Act
render(<H2>{null}</H2>);
// Assert
const heading = screen.getByRole("heading", { level: 2 });
expect(heading).toBeInTheDocument();
expect(heading).toBeEmptyDOMElement();
});
});
describe("h3", () => {
it("should render h3 with correct text content", () => {
// Arrange
const H3 = planHeadings.h3;
const text = "Subsection Heading";
// Act
render(<H3>{text}</H3>);
// Assert
const heading = screen.getByRole("heading", { level: 3 });
expect(heading).toBeInTheDocument();
expect(heading).toHaveTextContent(text);
});
});
describe("h4", () => {
it("should render h4 with correct text content", () => {
// Arrange
const H4 = planHeadings.h4;
const text = "Level 4 Heading";
// Act
render(<H4>{text}</H4>);
// Assert
const heading = screen.getByRole("heading", { level: 4 });
expect(heading).toBeInTheDocument();
expect(heading).toHaveTextContent(text);
});
});
describe("h5", () => {
it("should render h5 with correct text content", () => {
// Arrange
const H5 = planHeadings.h5;
const text = "Level 5 Heading";
// Act
render(<H5>{text}</H5>);
// Assert
const heading = screen.getByRole("heading", { level: 5 });
expect(heading).toBeInTheDocument();
expect(heading).toHaveTextContent(text);
});
});
describe("h6", () => {
it("should render h6 with correct text content", () => {
// Arrange
const H6 = planHeadings.h6;
const text = "Level 6 Heading";
// Act
render(<H6>{text}</H6>);
// Assert
const heading = screen.getByRole("heading", { level: 6 });
expect(heading).toBeInTheDocument();
expect(heading).toHaveTextContent(text);
});
});
describe("heading hierarchy", () => {
it("should render all heading levels correctly in sequence", () => {
// Arrange
const H1 = planHeadings.h1;
const H2 = planHeadings.h2;
const H3 = planHeadings.h3;
const H4 = planHeadings.h4;
const H5 = planHeadings.h5;
const H6 = planHeadings.h6;
// Act
render(
<div>
<H1>Heading 1</H1>
<H2>Heading 2</H2>
<H3>Heading 3</H3>
<H4>Heading 4</H4>
<H5>Heading 5</H5>
<H6>Heading 6</H6>
</div>,
);
// Assert
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent(
"Heading 1",
);
expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent(
"Heading 2",
);
expect(screen.getByRole("heading", { level: 3 })).toHaveTextContent(
"Heading 3",
);
expect(screen.getByRole("heading", { level: 4 })).toHaveTextContent(
"Heading 4",
);
expect(screen.getByRole("heading", { level: 5 })).toHaveTextContent(
"Heading 5",
);
expect(screen.getByRole("heading", { level: 6 })).toHaveTextContent(
"Heading 6",
);
});
});
});

View File

@@ -6,6 +6,7 @@ import { USE_PLANNING_AGENT } from "#/utils/feature-flags";
import { Typography } from "#/ui/typography";
import { I18nKey } from "#/i18n/declaration";
import { MarkdownRenderer } from "#/components/features/markdown/markdown-renderer";
import { planHeadings } from "#/components/features/markdown/plan-headings";
const MAX_CONTENT_LENGTH = 300;
@@ -66,7 +67,7 @@ export function PlanPreview({
>
{truncatedContent && (
<>
<MarkdownRenderer includeStandard includeHeadings>
<MarkdownRenderer includeStandard components={planHeadings}>
{truncatedContent}
</MarkdownRenderer>
{planContent && planContent.length > MAX_CONTENT_LENGTH && (

View File

@@ -0,0 +1,35 @@
import React from "react";
// Custom heading components for plan views with reduced font sizes and tighter spacing
export const planHeadings = {
h1: ({ children }: { children?: React.ReactNode }) => (
<h1 className="text-lg text-white font-bold leading-6 mb-1.5 mt-3 first:mt-0">
{children}
</h1>
),
h2: ({ children }: { children?: React.ReactNode }) => (
<h2 className="text-base font-semibold leading-5 text-white mb-1 mt-2.5 first:mt-0">
{children}
</h2>
),
h3: ({ children }: { children?: React.ReactNode }) => (
<h3 className="text-sm font-semibold text-white mb-1 mt-2 first:mt-0">
{children}
</h3>
),
h4: ({ children }: { children?: React.ReactNode }) => (
<h4 className="text-sm font-semibold text-white mb-1 mt-2 first:mt-0">
{children}
</h4>
),
h5: ({ children }: { children?: React.ReactNode }) => (
<h5 className="text-xs font-semibold text-white mb-0.5 mt-1.5 first:mt-0">
{children}
</h5>
),
h6: ({ children }: { children?: React.ReactNode }) => (
<h6 className="text-xs font-medium text-gray-300 mb-0.5 mt-1.5 first:mt-0">
{children}
</h6>
),
};

View File

@@ -5,6 +5,7 @@ import LessonPlanIcon from "#/icons/lesson-plan.svg?react";
import { useConversationStore } from "#/stores/conversation-store";
import { useScrollToBottom } from "#/hooks/use-scroll-to-bottom";
import { MarkdownRenderer } from "#/components/features/markdown/markdown-renderer";
import { planHeadings } from "#/components/features/markdown/plan-headings";
import { useHandlePlanClick } from "#/hooks/use-handle-plan-click";
function PlannerTab() {
@@ -23,7 +24,7 @@ function PlannerTab() {
onScroll={(e) => onChatBodyScroll(e.currentTarget)}
className="flex flex-col w-full h-full p-4 overflow-auto"
>
<MarkdownRenderer includeStandard includeHeadings>
<MarkdownRenderer includeStandard components={planHeadings}>
{planContent}
</MarkdownRenderer>
</div>