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

agentic-dev-library / thumbcode / 21935184468

12 Feb 2026 05:47AM UTC coverage: 42.344% (+0.08%) from 42.263%
21935184468

Pull #127

github

web-flow
Merge 9b8bc04da into 8a2dd33e7
Pull Request #127: refactor: decompose 8 large files into focused components

751 of 2374 branches covered (31.63%)

Branch coverage included in aggregate %.

18 of 103 new or added lines in 19 files covered. (17.48%)

50 existing lines in 3 files now uncovered.

1619 of 3223 relevant lines covered (50.23%)

19.76 hits per line

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

0.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 {
NEW
35
  const ext = name.split('.').pop()?.toLowerCase();
×
NEW
36
  const iconMap: Record<string, FileIconInfo> = {
×
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
  };
NEW
57
  return iconMap[ext || ''] || { Icon: FileIcon, color: 'warmGray' };
×
58
}
59

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

67
function getStatusText(node: FileNode): string {
NEW
68
  if (node.added) return 'added';
×
NEW
69
  if (node.modified) return 'modified';
×
NEW
70
  if (node.deleted) return 'deleted';
×
NEW
71
  return '';
×
72
}
73

74
function getStatusLabel(node: FileNode): string {
NEW
75
  if (node.added) return 'A';
×
NEW
76
  if (node.modified) return 'M';
×
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 {
NEW
86
  if (!isFolder) return 'Open file';
×
NEW
87
  if (!hasChildren) return 'Empty folder';
×
NEW
88
  return isExpanded ? 'Collapse folder' : 'Expand folder';
×
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;
NEW
115
  if (isFolder) {
×
NEW
116
    const Icon = isExpanded ? FolderOpenIcon : FolderIcon;
×
NEW
117
    iconInfo = { Icon, color: 'gold' };
×
118
  } else {
NEW
119
    iconInfo = getFileIconInfo(node.name);
×
120
  }
121

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

NEW
125
  return (
×
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 ? (
×
136
          <View style={{ transform: [{ rotate: isExpanded ? '0deg' : '-90deg' }] }}>
×
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>) {
NEW
170
  const isExpanded = expandedPaths.has(node.path);
×
NEW
171
  const isSelected = selectedPath === node.path;
×
NEW
172
  const isFolder = node.type === 'folder';
×
NEW
173
  const hasChildren = Boolean(node.children?.length);
×
NEW
174
  const statusColor = getStatusColor(node);
×
175

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

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

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

NEW
190
  return (
×
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 && (
×
208
          <Text className={`text-xs ${statusColor} mr-2`}>{getStatusLabel(node)}</Text>
209
        )}
210
      </View>
211

212
      {isFolder && isExpanded && hasChildren && (
×
213
        <View>
214
          {node.children?.map((child) => (
NEW
215
            <TreeNode
×
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