• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

agentic-dev-library / thumbcode / 22045822425

16 Feb 2026 12:16AM UTC coverage: 54.443% (+2.9%) from 51.576%
22045822425

Pull #135

github

web-flow
Merge 6d5cf7fd6 into 4f30dd661
Pull Request #135: Wire up CredentialService in Onboarding API Keys screen

1423 of 2967 branches covered (47.96%)

Branch coverage included in aggregate %.

47 of 57 new or added lines in 3 files covered. (82.46%)

31 existing lines in 3 files now uncovered.

2333 of 3932 relevant lines covered (59.33%)

42.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.85
/src/components/code/TreeNode.tsx
1
/**
2
 * TreeNode Component
3
 *
4
 * Renders a single node (file or folder) in the FileTree,
5
 * including the icon, label, status indicator, and recursive children.
6
 */
7

8
import type React from 'react';
9
import { memo, useContext } from 'react';
10
import { useStore } from 'zustand';
11
import {
12
  ChevronDownIcon,
13
  FileCodeIcon,
14
  FileConfigIcon,
15
  FileDataIcon,
16
  FileDocIcon,
17
  FileIcon,
18
  FileMediaIcon,
19
  FileStyleIcon,
20
  FileWebIcon,
21
  FolderIcon,
22
  FolderOpenIcon,
23
  type IconColor,
24
} from '@/components/icons';
25
import { Text } from '@/components/ui';
26
import type { FileNode } from './FileTree';
27
import { FileTreeContext } from './FileTreeContext';
28

29
type FileIconComponent = React.FC<{ size?: number; color?: IconColor; turbulence?: number }>;
30

31
interface FileIconInfo {
32
  Icon: FileIconComponent;
33
  color: IconColor;
34
}
35

36
function getFileIconInfo(name: string): FileIconInfo {
37
  const ext = name.split('.').pop()?.toLowerCase();
32✔
38
  const iconMap: Record<string, FileIconInfo> = {
32✔
39
    ts: { Icon: FileCodeIcon, color: 'teal' },
40
    tsx: { Icon: FileCodeIcon, color: 'teal' },
41
    js: { Icon: FileCodeIcon, color: 'gold' },
42
    jsx: { Icon: FileCodeIcon, color: 'gold' },
43
    json: { Icon: FileDataIcon, color: 'gold' },
44
    md: { Icon: FileDocIcon, color: 'warmGray' },
45
    css: { Icon: FileStyleIcon, color: 'coral' },
46
    scss: { Icon: FileStyleIcon, color: 'coral' },
47
    html: { Icon: FileWebIcon, color: 'teal' },
48
    png: { Icon: FileMediaIcon, color: 'coral' },
49
    jpg: { Icon: FileMediaIcon, color: 'coral' },
50
    jpeg: { Icon: FileMediaIcon, color: 'coral' },
51
    svg: { Icon: FileMediaIcon, color: 'coral' },
52
    gif: { Icon: FileMediaIcon, color: 'coral' },
53
    git: { Icon: FileConfigIcon, color: 'warmGray' },
54
    env: { Icon: FileConfigIcon, color: 'warmGray' },
55
    lock: { Icon: FileConfigIcon, color: 'warmGray' },
56
    yaml: { Icon: FileConfigIcon, color: 'warmGray' },
57
    yml: { Icon: FileConfigIcon, color: 'warmGray' },
58
  };
59
  return iconMap[ext || ''] || { Icon: FileIcon, color: 'warmGray' };
32!
60
}
61

62
function getStatusColor(node: FileNode): string {
63
  if (node.added) return 'text-teal-400';
44✔
64
  if (node.modified) return 'text-gold-400';
33✔
65
  if (node.deleted) return 'text-coral-400';
27!
66
  return '';
27✔
67
}
68

69
function getStatusText(node: FileNode): string {
70
  if (node.added) return 'added';
44✔
71
  if (node.modified) return 'modified';
33✔
72
  if (node.deleted) return 'deleted';
27!
73
  return '';
27✔
74
}
75

76
function getStatusLabel(node: FileNode): string {
77
  if (node.added) return 'A';
17✔
78
  if (node.modified) return 'M';
6!
UNCOV
79
  if (node.deleted) return 'D';
×
UNCOV
80
  return '';
×
81
}
82

83
function getAccessibilityHint(
84
  isFolder: boolean,
85
  hasChildren: boolean,
86
  isExpanded: boolean
87
): string {
88
  if (!isFolder) return 'Open file';
44✔
89
  if (!hasChildren) return 'Empty folder';
12!
90
  return isExpanded ? 'Collapse folder' : 'Expand folder';
12✔
91
}
92

93
function FileTreeNodeRow({
94
  node,
95
  depth,
96
  isSelected,
97
  isExpanded,
98
  isFolder,
99
  hasChildren,
100
  statusColor,
101
  onPress,
102
  accessibilityLabel,
103
  accessibilityHint,
104
}: {
105
  node: FileNode;
106
  depth: number;
107
  isSelected: boolean;
108
  isExpanded: boolean;
109
  isFolder: boolean;
110
  hasChildren: boolean;
111
  statusColor: string;
112
  onPress: () => void;
113
  accessibilityLabel: string;
114
  accessibilityHint: string;
115
}) {
116
  let iconInfo: FileIconInfo;
117
  if (isFolder) {
44✔
118
    const Icon = isExpanded ? FolderOpenIcon : FolderIcon;
12✔
119
    iconInfo = { Icon, color: 'gold' };
12✔
120
  } else {
121
    iconInfo = getFileIconInfo(node.name);
32✔
122
  }
123

124
  const rowClass = isSelected ? 'bg-teal-600/20' : 'active:bg-neutral-700';
44✔
125
  const textClass = isSelected ? 'text-teal-300' : 'text-neutral-200';
44✔
126

127
  return (
44✔
128
    <button
129
      type="button"
130
      onClick={onPress}
131
      aria-label={accessibilityLabel}
132
      aria-description={accessibilityHint}
133
      className={`flex-row items-center py-1.5 px-2 ${rowClass}`}
134
      style={{ paddingLeft: 8 + depth * 16 }}
135
    >
136
      <div className="w-4 mr-1 items-center justify-center">
137
        {isFolder && hasChildren ? (
100✔
138
          <div style={{ transform: `rotate(${isExpanded ? '0deg' : '-90deg'})` }}>
12✔
139
            <ChevronDownIcon size={12} color="warmGray" turbulence={0.12} />
140
          </div>
141
        ) : null}
142
      </div>
143
      <div className="mr-2">
144
        <iconInfo.Icon size={16} color={iconInfo.color} turbulence={0.15} />
145
      </div>
146
      <Text className={`font-mono text-sm flex-1 ${textClass} ${statusColor}`} numberOfLines={1}>
147
        {node.name}
148
      </Text>
149
    </button>
150
  );
151
}
152

153
export interface TreeNodeProps {
154
  node: FileNode;
155
  depth: number;
156
}
157

158
export const TreeNode = memo(function TreeNode({ node, depth }: Readonly<TreeNodeProps>) {
1✔
159
  const context = useContext(FileTreeContext);
44✔
160
  if (!context) {
44!
UNCOV
161
    throw new Error('TreeNode must be used within a FileTreeContext.Provider');
×
162
  }
163
  const { store, onSelectFile, showStatus } = context;
44✔
164

165
  const isExpanded = useStore(store, (s) => s.expandedPaths.has(node.path));
183✔
166
  const isSelected = useStore(store, (s) => s.selectedPath === node.path);
183✔
167
  const isFolder = node.type === 'folder';
44✔
168
  const hasChildren = Boolean(node.children?.length);
44✔
169
  const statusColor = getStatusColor(node);
44✔
170

171
  const handlePress = () => {
44✔
172
    if (isFolder && hasChildren) {
3✔
173
      store.getState().toggleExpanded(node.path);
2✔
174
    } else if (!isFolder) {
1!
175
      onSelectFile?.(node.path);
1✔
176
    }
177
  };
178

179
  const shouldShowStatus = showStatus && Boolean(node.added || node.modified || node.deleted);
44✔
180

181
  const accessibilityLabel = [node.name, isFolder ? 'folder' : 'file', getStatusText(node)]
44✔
182
    .filter(Boolean)
183
    .join(', ');
184

185
  return (
44✔
186
    <div>
187
      <div className="flex-row items-center">
188
        <div className="flex-1">
189
          <FileTreeNodeRow
190
            node={node}
191
            depth={depth}
192
            isSelected={isSelected}
193
            isExpanded={isExpanded}
194
            isFolder={isFolder}
195
            hasChildren={hasChildren}
196
            statusColor={statusColor}
197
            onPress={handlePress}
198
            accessibilityLabel={accessibilityLabel}
199
            accessibilityHint={getAccessibilityHint(isFolder, hasChildren, isExpanded)}
200
          />
201
        </div>
202
        {shouldShowStatus && (
61✔
203
          <Text className={`text-xs ${statusColor} mr-2`}>{getStatusLabel(node)}</Text>
204
        )}
205
      </div>
206

207
      {isFolder && isExpanded && hasChildren && (
66✔
208
        <div>
209
          {node.children?.map((child) => (
210
            <TreeNode key={child.path} node={child} depth={depth + 1} />
10✔
211
          ))}
212
        </div>
213
      )}
214
    </div>
215
  );
216
});
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc