All files / src/components/tools SearchCard.tsx

82.35% Statements 14/17
76.92% Branches 30/39
60% Functions 3/5
86.66% Lines 13/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102                15x     15x 15x 15x 15x       44x       15x 15x 15x   35x 15x 15x   15x                                                                                                                                              
import { useState, type JSX } from "react";
import { ChevronRight, Search } from "lucide-react";
import type { ToolCardProps } from "./ToolCardProps.js";
import { ICON_SM, ICON_MD } from "../../utils/iconSize.js";
import styles from "./toolCards.module.scss";
 
/** Extracts search-relevant fields from tool args. */
function getSearchInfo(args: unknown): { pattern: string; path: string } {
  Iif (args === null || args === undefined || typeof args !== "object") {
    return { pattern: "", path: "" };
  }
  const a = args as Record<string, unknown>;
  const pattern = typeof a.pattern === "string" ? a.pattern : "";
  const path = typeof a.path === "string" ? a.path : "";
  return { pattern, path };
}
 
/** Number of result lines shown when collapsed. */
const PREVIEW_LINES: number = 5;
 
/** Renders a search tool call (Grep, Glob) with pattern and match results. */
export function SearchCard({ tool, args, result, isError }: ToolCardProps): JSX.Element {
  const [expanded, setExpanded] = useState(false);
  const { pattern, path } = getSearchInfo(args);
  const inProgress = result === undefined;
 
  const lines = result?.split("\n").filter((l) => l.length > 0) ?? [];
  const hasMore = lines.length > PREVIEW_LINES;
  const displayLines = expanded ? lines : lines.slice(0, PREVIEW_LINES);
 
  return (
    <div
      className={`${styles.card} ${isError ? styles.cardRed : styles.cardPurple} ${inProgress ? styles.inProgress : ""}`}
      data-testid="tool-card-search"
    >
      <div className={styles.header}>
        <span className={styles.icon}>
          <Search size={ICON_MD} />
        </span>
        <span className={styles.toolName} style={{ color: "var(--accent-purple, #a78bfa)" }}>
          {tool}
        </span>
        {pattern && (
          <span className={styles.fileName} data-testid="tool-card-pattern">
            &quot;{pattern}&quot;
          </span>
        )}
        {path && (
          <span
            className={styles.fileName}
            style={{ flexShrink: 1 }}
            data-testid="tool-card-search-path"
          >
            in {path}
          </span>
        )}
        {!inProgress && !isError && lines.length > 0 && (
          <>
            <span className={styles.spacer} />
            <span className={styles.badge} data-testid="tool-card-match-count">
              {lines.length} {lines.length === 1 ? "match" : "matches"}
            </span>
          </>
        )}
      </div>
 
      {isError && result && (
        <pre className={styles.pre} data-testid="tool-card-error">
          {result}
        </pre>
      )}
 
      {!isError && !inProgress && displayLines.length > 0 && (
        <>
          <pre className={styles.pre} data-testid="tool-card-results">
            {displayLines.join("\n")}
          </pre>
          {hasMore && (
            <button
              type="button"
              className={styles.bodyToggle}
              onClick={() => {
                setExpanded((v) => !v);
              }}
              aria-expanded={expanded}
              data-testid="tool-card-toggle"
            >
              <span
                className={`${styles.chevron} ${expanded ? styles.chevronExpanded : ""}`}
                aria-hidden="true"
              >
                <ChevronRight size={ICON_SM} />
              </span>
              {expanded ? "collapse" : `${lines.length - PREVIEW_LINES} more matches`}
            </button>
          )}
        </>
      )}
    </div>
  );
}