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

agentic-dev-library / thumbcode / 21935229190

12 Feb 2026 05:49AM UTC coverage: 64.742%. First build
21935229190

Pull #127

github

web-flow
Merge 4a7599785 into af48ebeae
Pull Request #127: refactor: decompose 8 large files into focused components

1379 of 2375 branches covered (58.06%)

Branch coverage included in aggregate %.

55 of 103 new or added lines in 19 files covered. (53.4%)

2253 of 3235 relevant lines covered (69.64%)

22.25 hits per line

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

84.0
/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 { Pressable, View } from 'react-native';
10
import {
11
  ChevronDownIcon,
12
  FileCodeIcon,
13
  FileConfigIcon,
14
  FileDataIcon,
15
  FileDocIcon,
16
  FileIcon,
17
  FileMediaIcon,
18
  FileStyleIcon,
19
  FileWebIcon,
20
  FolderIcon,
21
  FolderOpenIcon,
22
  type IconColor,
23
} from '@/components/icons';
24
import { Text } from '@/components/ui';
25
import type { FileNode } from './FileTree';
26

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

29
interface FileIconInfo {
30
  Icon: FileIconComponent;
31
  color: IconColor;
32
}
33

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

60
function getStatusColor(node: FileNode): string {
61
  if (node.added) return 'text-teal-400';
30✔
62
  if (node.modified) return 'text-gold-400';
22✔
63
  if (node.deleted) return 'text-coral-400';
19!
64
  return '';
19✔
65
}
66

67
function getStatusText(node: FileNode): string {
68
  if (node.added) return 'added';
30✔
69
  if (node.modified) return 'modified';
22✔
70
  if (node.deleted) return 'deleted';
19!
71
  return '';
19✔
72
}
73

74
function getStatusLabel(node: FileNode): string {
75
  if (node.added) return 'A';
11✔
76
  if (node.modified) return 'M';
3!
NEW
77
  if (node.deleted) return 'D';
×
NEW
78
  return '';
×
79
}
80

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

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

122
  const rowClass = isSelected ? 'bg-teal-600/20' : 'active:bg-neutral-700';
30!
123
  const textClass = isSelected ? 'text-teal-300' : 'text-neutral-200';
30!
124

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

151
export interface TreeNodeProps {
152
  node: FileNode;
153
  depth: number;
154
  onSelectFile?: (path: string) => void;
155
  selectedPath?: string;
156
  expandedPaths: Set<string>;
157
  toggleExpanded: (path: string) => void;
158
  showStatus?: boolean;
159
}
160

161
export function TreeNode({
162
  node,
163
  depth,
164
  onSelectFile,
165
  selectedPath,
166
  expandedPaths,
167
  toggleExpanded,
168
  showStatus,
169
}: Readonly<TreeNodeProps>) {
170
  const isExpanded = expandedPaths.has(node.path);
30✔
171
  const isSelected = selectedPath === node.path;
30✔
172
  const isFolder = node.type === 'folder';
30✔
173
  const hasChildren = Boolean(node.children?.length);
30✔
174
  const statusColor = getStatusColor(node);
30✔
175

176
  const handlePress = () => {
30✔
177
    if (isFolder && hasChildren) {
1!
NEW
178
      toggleExpanded(node.path);
×
179
    } else if (!isFolder) {
1!
180
      onSelectFile?.(node.path);
1✔
181
    }
182
  };
183

184
  const shouldShowStatus = showStatus && Boolean(node.added || node.modified || node.deleted);
30✔
185

186
  const accessibilityLabel = [node.name, isFolder ? 'folder' : 'file', getStatusText(node)]
30✔
187
    .filter(Boolean)
188
    .join(', ');
189

190
  return (
30✔
191
    <View>
192
      <View className="flex-row items-center">
193
        <View className="flex-1">
194
          <FileTreeNodeRow
195
            node={node}
196
            depth={depth}
197
            isSelected={isSelected}
198
            isExpanded={isExpanded}
199
            isFolder={isFolder}
200
            hasChildren={hasChildren}
201
            statusColor={statusColor}
202
            onPress={handlePress}
203
            accessibilityLabel={accessibilityLabel}
204
            accessibilityHint={getAccessibilityHint(isFolder, hasChildren, isExpanded)}
205
          />
206
        </View>
207
        {shouldShowStatus && (
41✔
208
          <Text className={`text-xs ${statusColor} mr-2`}>{getStatusLabel(node)}</Text>
209
        )}
210
      </View>
211

212
      {isFolder && isExpanded && hasChildren && (
44✔
213
        <View>
214
          {node.children?.map((child) => (
215
            <TreeNode
6✔
216
              key={child.path}
217
              node={child}
218
              depth={depth + 1}
219
              onSelectFile={onSelectFile}
220
              selectedPath={selectedPath}
221
              expandedPaths={expandedPaths}
222
              toggleExpanded={toggleExpanded}
223
              showStatus={showStatus}
224
            />
225
          ))}
226
        </View>
227
      )}
228
    </View>
229
  );
230
}
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