ios-unittest-generator 4.19.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ venv/
8
+ env/
9
+ ENV/
10
+ *.egg-info/
11
+ dist/
12
+ build/
13
+
14
+ # IDEs
15
+ .vscode/
16
+ .idea/
17
+ *.swp
18
+ *.swo
19
+ *~
20
+
21
+ # OS
22
+ .DS_Store
23
+ Thumbs.db
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) Microsoft Corporation. All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.4
2
+ Name: ios-unittest-generator
3
+ Version: 4.19.3
4
+ Summary: MCP Server for generating iOS unit tests for Chromium/Edge projects
5
+ Author: Microsoft Edge iOS Team
6
+ License-Expression: BSD-3-Clause
7
+ License-File: LICENSE
8
+ Keywords: chromium,edge,ios,mcp,test-generation,unittest
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: BSD License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Testing
16
+ Requires-Python: >=3.11
17
+ Requires-Dist: mcp<2.0.0,>=1.9.4
18
+ Description-Content-Type: text/markdown
19
+
20
+ # iOS Unit Test Generator
21
+
22
+ 智能 MCP 服务器,自动生成、编译、运行和修复 iOS 单元测试。
23
+
24
+ ## 🚀 快速开始
25
+
26
+ **无需拉取代码,直接在 VS Code 中配置即可使用!**
27
+
28
+ ### 方式一:VS Code 一键安装(最简单 ⭐)
29
+
30
+ 1. 打开 VS Code
31
+ 2. `Cmd+Shift+P` → 输入 `MCP: Add Server`
32
+ 3. 选择 **"Pip Package"**
33
+ 4. 输入包名:`ios-unittest-generator`
34
+ 5. 设置环境变量 `CHROMIUM_SRC` 为你的 Chromium 源码路径
35
+ 6. 完成!
36
+
37
+ ### 方式二:手动编辑配置文件
38
+
39
+ **macOS/Linux** - 编辑 `~/.vscode/mcp.json`:
40
+ ```json
41
+ {
42
+ "servers": {
43
+ "ios-unittest-generator": {
44
+ "command": "uvx",
45
+ "args": ["ios-unittest-generator"],
46
+ "env": {
47
+ "CHROMIUM_SRC": "/path/to/chromium/src"
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ **Windows** - 编辑 `%APPDATA%\Code\User\mcp.json`:
55
+ ```json
56
+ {
57
+ "servers": {
58
+ "ios-unittest-generator": {
59
+ "command": "uvx",
60
+ "args": ["ios-unittest-generator"],
61
+ "env": {
62
+ "CHROMIUM_SRC": "C:\\path\\to\\chromium\\src"
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## 📋 核心功能
70
+
71
+ - ✅ 自动分析源文件,识别可测试元素
72
+ - ✅ 生成完整的测试文件(包含 fixture、SetUp、测试用例)
73
+ - ✅ 智能检测测试目标(支持 `ios/chrome/*`、`components/*/ios/*` 等所有路径)
74
+ - ✅ 自动更新 BUILD.gn 文件(按字母顺序)
75
+ - ✅ 自动编译测试,智能分析编译错误
76
+ - ✅ 自动运行测试,智能分析运行时错误
77
+
78
+ ## 📖 11 个 MCP 工具
79
+
80
+ | 工具 | 功能 |
81
+ |------|------|
82
+ | `full_test_workflow` | 完整工作流(分析→生成→增强→编译→运行) |
83
+ | `analyze_ios_code_for_testing` | 分析源文件,提取可测试元素 |
84
+ | `generate_ios_unittest_file` | 生成测试文件 |
85
+ | `check_ios_test_coverage` | 检查测试覆盖率 |
86
+ | `verify_test_enhancement_complete` | 验证测试增强完成(质量门控) |
87
+ | `compile_ios_unittest` | 编译测试(自动错误分析) |
88
+ | `run_ios_unittest` | 运行测试(自动错误分析) |
89
+ | `analyze_runtime_errors` | 分析运行时错误 |
90
+ | `analyze_compilation_errors` | 分析编译错误 |
91
+ | `update_existing_tests` | 增量更新测试 |
92
+ | `update_build_file_for_test` | 自动更新 BUILD 文件 |
93
+
94
+ ## 💡 使用示例
95
+
96
+ ```bash
97
+ # 一键生成完整测试
98
+ Use full_test_workflow for ios/chrome/browser/ui/foo.mm
99
+
100
+ # 单独编译
101
+ Use compile_ios_unittest for ios/chrome/browser/ui/foo.mm
102
+
103
+ # 单独运行
104
+ Use run_ios_unittest with filter FooTest.*
105
+ ```
106
+
107
+ ## 🔗 相关链接
108
+
109
+ - **PyPI**: https://pypi.org/project/ios-unittest-generator/
110
+ - **快速入门**: [QUICKSTART.md](QUICKSTART.md)
111
+
112
+ ---
113
+
114
+ **版本**: v4.19.1
115
+ **更新日期**: 2026-01-22
@@ -0,0 +1,96 @@
1
+ # iOS Unit Test Generator
2
+
3
+ 智能 MCP 服务器,自动生成、编译、运行和修复 iOS 单元测试。
4
+
5
+ ## 🚀 快速开始
6
+
7
+ **无需拉取代码,直接在 VS Code 中配置即可使用!**
8
+
9
+ ### 方式一:VS Code 一键安装(最简单 ⭐)
10
+
11
+ 1. 打开 VS Code
12
+ 2. `Cmd+Shift+P` → 输入 `MCP: Add Server`
13
+ 3. 选择 **"Pip Package"**
14
+ 4. 输入包名:`ios-unittest-generator`
15
+ 5. 设置环境变量 `CHROMIUM_SRC` 为你的 Chromium 源码路径
16
+ 6. 完成!
17
+
18
+ ### 方式二:手动编辑配置文件
19
+
20
+ **macOS/Linux** - 编辑 `~/.vscode/mcp.json`:
21
+ ```json
22
+ {
23
+ "servers": {
24
+ "ios-unittest-generator": {
25
+ "command": "uvx",
26
+ "args": ["ios-unittest-generator"],
27
+ "env": {
28
+ "CHROMIUM_SRC": "/path/to/chromium/src"
29
+ }
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ **Windows** - 编辑 `%APPDATA%\Code\User\mcp.json`:
36
+ ```json
37
+ {
38
+ "servers": {
39
+ "ios-unittest-generator": {
40
+ "command": "uvx",
41
+ "args": ["ios-unittest-generator"],
42
+ "env": {
43
+ "CHROMIUM_SRC": "C:\\path\\to\\chromium\\src"
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ ## 📋 核心功能
51
+
52
+ - ✅ 自动分析源文件,识别可测试元素
53
+ - ✅ 生成完整的测试文件(包含 fixture、SetUp、测试用例)
54
+ - ✅ 智能检测测试目标(支持 `ios/chrome/*`、`components/*/ios/*` 等所有路径)
55
+ - ✅ 自动更新 BUILD.gn 文件(按字母顺序)
56
+ - ✅ 自动编译测试,智能分析编译错误
57
+ - ✅ 自动运行测试,智能分析运行时错误
58
+
59
+ ## 📖 11 个 MCP 工具
60
+
61
+ | 工具 | 功能 |
62
+ |------|------|
63
+ | `full_test_workflow` | 完整工作流(分析→生成→增强→编译→运行) |
64
+ | `analyze_ios_code_for_testing` | 分析源文件,提取可测试元素 |
65
+ | `generate_ios_unittest_file` | 生成测试文件 |
66
+ | `check_ios_test_coverage` | 检查测试覆盖率 |
67
+ | `verify_test_enhancement_complete` | 验证测试增强完成(质量门控) |
68
+ | `compile_ios_unittest` | 编译测试(自动错误分析) |
69
+ | `run_ios_unittest` | 运行测试(自动错误分析) |
70
+ | `analyze_runtime_errors` | 分析运行时错误 |
71
+ | `analyze_compilation_errors` | 分析编译错误 |
72
+ | `update_existing_tests` | 增量更新测试 |
73
+ | `update_build_file_for_test` | 自动更新 BUILD 文件 |
74
+
75
+ ## 💡 使用示例
76
+
77
+ ```bash
78
+ # 一键生成完整测试
79
+ Use full_test_workflow for ios/chrome/browser/ui/foo.mm
80
+
81
+ # 单独编译
82
+ Use compile_ios_unittest for ios/chrome/browser/ui/foo.mm
83
+
84
+ # 单独运行
85
+ Use run_ios_unittest with filter FooTest.*
86
+ ```
87
+
88
+ ## 🔗 相关链接
89
+
90
+ - **PyPI**: https://pypi.org/project/ios-unittest-generator/
91
+ - **快速入门**: [QUICKSTART.md](QUICKSTART.md)
92
+
93
+ ---
94
+
95
+ **版本**: v4.19.1
96
+ **更新日期**: 2026-01-22
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ios-unittest-generator"
7
+ version = "4.19.3"
8
+ description = "MCP Server for generating iOS unit tests for Chromium/Edge projects"
9
+ readme = "README.md"
10
+ license = "BSD-3-Clause"
11
+ authors = [
12
+ { name = "Microsoft Edge iOS Team" }
13
+ ]
14
+ keywords = ["mcp", "ios", "unittest", "chromium", "edge", "test-generation"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: BSD License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Software Development :: Testing",
23
+ ]
24
+ requires-python = ">=3.11"
25
+ dependencies = [
26
+ "mcp>=1.9.4,<2.0.0",
27
+ ]
28
+
29
+ [project.scripts]
30
+ ios-unittest-generator = "ios_unittest_generator:main"
31
+ ios_unittest_generator = "ios_unittest_generator:main"
32
+
33
+ [project.urls]
34
+ # Update these URLs when publishing to your actual repository
35
+ # Homepage = "https://github.com/your-org/ios-unittest-generator"
36
+ # Repository = "https://github.com/your-org/ios-unittest-generator"
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["src/ios_unittest_generator"]
40
+
41
+ [tool.hatch.build.targets.wheel.force-include]
42
+ "src/ios_unittest_generator/ios_test_patterns.json" = "ios_unittest_generator/ios_test_patterns.json"
43
+
44
+ [tool.hatch.build.targets.sdist]
45
+ include = [
46
+ "src/",
47
+ "README.md",
48
+ "LICENSE",
49
+ ]
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright (C) Microsoft Corporation. All rights reserved.
3
+ # Use of this source code is governed by a BSD-style license that can be
4
+ # found in the LICENSE file.
5
+
6
+ """iOS Unit Test Generator MCP Server.
7
+
8
+ This package provides MCP tools to generate unit tests for iOS Edge/Chromium code.
9
+ """
10
+
11
+ __version__ = "4.19.3"
12
+
13
+ from .server import mcp, main
14
+
15
+ __all__ = ["mcp", "main", "__version__"]
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright (C) Microsoft Corporation. All rights reserved.
3
+ # Use of this source code is governed by a BSD-style license that can be
4
+ # found in the LICENSE file.
5
+
6
+ """Entry point for running the MCP server directly with `python -m ios_unittest_generator`."""
7
+
8
+ from .server import main
9
+
10
+ if __name__ == "__main__":
11
+ main()
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright (C) Microsoft Corporation. All rights reserved.
3
+ # Use of this source code is governed by a BSD-style license that can be
4
+ # found in the LICENSE file.
5
+
6
+ """BUILD file manipulation utilities.
7
+
8
+ This module provides functions for updating BUILD.gn and BUILD_edge.gni files.
9
+ Refactored from the monolithic _add_component_to_test_target function.
10
+ """
11
+
12
+ import re
13
+ import sys
14
+ from typing import List, Optional, Tuple
15
+
16
+
17
+ class BuildFileUpdater:
18
+ """Handles BUILD file updates with strategy pattern."""
19
+
20
+ def __init__(self, build_content: str, component_target: str, target_name: str):
21
+ """Initialize the updater.
22
+
23
+ Args:
24
+ build_content: Content of the BUILD file
25
+ component_target: Target path like "//components/xxx/ios:unit_tests"
26
+ target_name: Test target name ('ios_chrome_unittests', etc.)
27
+ """
28
+ self.build_content = build_content
29
+ self.component_target = component_target
30
+ self.target_name = target_name
31
+
32
+ def update(self) -> Tuple[bool, str]:
33
+ """Update BUILD file using appropriate strategy.
34
+
35
+ Returns:
36
+ (success, modified_content) tuple
37
+ """
38
+ # Try strategies in order
39
+ strategies = [
40
+ self._strategy_edge_components_template,
41
+ self._strategy_edge_ios_template,
42
+ self._strategy_direct_target,
43
+ ]
44
+
45
+ for strategy in strategies:
46
+ success, content = strategy()
47
+ if success:
48
+ return True, content
49
+
50
+ return False, self.build_content
51
+
52
+ def _strategy_edge_components_template(self) -> Tuple[bool, str]:
53
+ """Strategy 0: edge_overlay_test_components_unittests template."""
54
+ if self.target_name != 'components_unittests':
55
+ return False, self.build_content
56
+
57
+ if 'edge_overlay_test_components_unittests' not in self.build_content:
58
+ return False, self.build_content
59
+
60
+ sys.stderr.write(f"\n[STRATEGY 0] Edge components template\n")
61
+ sys.stderr.flush()
62
+
63
+ # Find template definition
64
+ template_match = self._find_template_definition('edge_overlay_test_components_unittests')
65
+ if not template_match:
66
+ sys.stderr.write(f"[FAIL] Template not found\n")
67
+ sys.stderr.flush()
68
+ return False, self.build_content
69
+
70
+ template_content = template_match.group(1)
71
+ template_start = template_match.start(1)
72
+
73
+ # Find if (is_ios || is_android) block
74
+ ios_block = self._find_ios_conditional_block(template_content)
75
+ if not ios_block:
76
+ sys.stderr.write(f"[FAIL] iOS conditional block not found\n")
77
+ sys.stderr.flush()
78
+ return False, self.build_content
79
+
80
+ ios_content, ios_start, ios_end = ios_block
81
+
82
+ # Find or create deps += array
83
+ deps_match = re.search(r'(deps\s*\+=\s*\[)(.*?)(\])', ios_content, re.DOTALL)
84
+
85
+ if deps_match:
86
+ # Insert into existing deps
87
+ return self._insert_into_deps_array(
88
+ template_start + ios_start + deps_match.start(2),
89
+ template_start + ios_start + deps_match.end(2),
90
+ deps_match.group(2),
91
+ indent=' '
92
+ )
93
+ else:
94
+ # Create new deps += array
95
+ insert_pos = template_start + ios_start
96
+ new_deps = f'\n deps += [\n "{self.component_target}",\n ]'
97
+ new_content = self.build_content[:insert_pos] + new_deps + self.build_content[insert_pos:]
98
+ sys.stderr.write(f"[OK] Created new deps += section\n")
99
+ sys.stderr.flush()
100
+ return True, new_content
101
+
102
+ def _strategy_edge_ios_template(self) -> Tuple[bool, str]:
103
+ """Strategy 1: edge_overlay_test_ios_chrome_unittests template."""
104
+ if self.target_name != 'ios_chrome_unittests':
105
+ return False, self.build_content
106
+
107
+ if 'edge_overlay_test_ios_chrome_unittests' not in self.build_content:
108
+ return False, self.build_content
109
+
110
+ sys.stderr.write(f"\n[STRATEGY 1] Edge iOS template\n")
111
+ sys.stderr.flush()
112
+
113
+ pattern = r'(edge_overlay_test_ios_chrome_unittests\s*\([^)]+\)\s*\{[^}]*?deps\s*(?:\+)?=\s*\[)(.*?)(\])'
114
+ match = re.search(pattern, self.build_content, re.DOTALL)
115
+
116
+ if not match:
117
+ return False, self.build_content
118
+
119
+ return self._insert_into_deps_array(
120
+ match.start(2),
121
+ match.end(2),
122
+ match.group(2),
123
+ indent=' '
124
+ )
125
+
126
+ def _strategy_direct_target(self) -> Tuple[bool, str]:
127
+ """Strategy 2: Find target directly by name."""
128
+ sys.stderr.write(f"\n[STRATEGY 2] Direct target search\n")
129
+ sys.stderr.flush()
130
+
131
+ # Try to find test(...) or source_set(...)
132
+ pattern = rf'((?:source_set|test)\s*\(\s*"{re.escape(self.target_name)}"\s*\)\s*\{{.*?deps\s*(?:\+)?=\s*\[)(.*?)(\])'
133
+ match = re.search(pattern, self.build_content, re.DOTALL)
134
+
135
+ if not match:
136
+ sys.stderr.write(f"[FAIL] Target not found\n")
137
+ sys.stderr.flush()
138
+ return False, self.build_content
139
+
140
+ return self._insert_into_deps_array(
141
+ match.start(2),
142
+ match.end(2),
143
+ match.group(2),
144
+ indent=' '
145
+ )
146
+
147
+ def _find_template_definition(self, template_name: str) -> Optional[re.Match]:
148
+ """Find template definition in BUILD file."""
149
+ template_pattern = rf'template\s*\(\s*"{template_name}"\s*\)\s*\{{'
150
+ match = re.search(template_pattern, self.build_content)
151
+
152
+ if not match:
153
+ return None
154
+
155
+ # Find matching closing brace
156
+ start_pos = match.end()
157
+ brace_count = 1
158
+ pos = start_pos
159
+
160
+ while pos < len(self.build_content) and brace_count > 0:
161
+ if self.build_content[pos] == '{':
162
+ brace_count += 1
163
+ elif self.build_content[pos] == '}':
164
+ brace_count -= 1
165
+ pos += 1
166
+
167
+ if brace_count == 0:
168
+ # Create a match object for the template content
169
+ class TemplateMatch:
170
+ def __init__(self, content, start, end):
171
+ self._content = content
172
+ self._start = start
173
+ self._end = end
174
+
175
+ def group(self, n):
176
+ if n == 1:
177
+ return self._content[self._start:self._end]
178
+ return None
179
+
180
+ def start(self, n):
181
+ return self._start
182
+
183
+ def end(self, n):
184
+ return self._end
185
+
186
+ return TemplateMatch(self.build_content, start_pos, pos - 1)
187
+
188
+ return None
189
+
190
+ def _find_ios_conditional_block(self, content: str) -> Optional[Tuple[str, int, int]]:
191
+ """Find if (is_ios || is_android) or if (is_ios) block."""
192
+ # Try is_ios || is_android first
193
+ markers = [
194
+ 'if (is_ios || is_android) {',
195
+ 'if (is_ios) {',
196
+ ]
197
+
198
+ for marker in markers:
199
+ pos = content.find(marker)
200
+ if pos != -1:
201
+ block_start = pos + len(marker)
202
+ brace_count = 1
203
+ close_pos = block_start
204
+
205
+ while close_pos < len(content) and brace_count > 0:
206
+ if content[close_pos] == '{':
207
+ brace_count += 1
208
+ elif content[close_pos] == '}':
209
+ brace_count -= 1
210
+ close_pos += 1
211
+
212
+ if brace_count == 0:
213
+ block_content = content[block_start:close_pos - 1]
214
+ return (block_content, block_start, close_pos - 1)
215
+
216
+ return None
217
+
218
+ def _insert_into_deps_array(
219
+ self,
220
+ deps_start: int,
221
+ deps_end: int,
222
+ deps_content: str,
223
+ indent: str
224
+ ) -> Tuple[bool, str]:
225
+ """Insert component target into deps array alphabetically.
226
+
227
+ Args:
228
+ deps_start: Start position of deps content
229
+ deps_end: End position of deps content
230
+ deps_content: Current deps content
231
+ indent: Indentation to use for entries
232
+
233
+ Returns:
234
+ (success, modified_content) tuple
235
+ """
236
+ # Parse existing deps
237
+ deps_lines = deps_content.split('\n')
238
+ deps_entries = []
239
+ for line in deps_lines:
240
+ stripped = line.strip()
241
+ if stripped and (stripped.startswith('"//') or stripped.startswith('"-=')):
242
+ deps_entries.append(stripped)
243
+
244
+ # Check if already exists
245
+ new_entry = f'"{self.component_target}",'
246
+ if new_entry.strip() in ' '.join(deps_entries):
247
+ sys.stderr.write(f"[INFO] Component already in deps\n")
248
+ sys.stderr.flush()
249
+ return False, self.build_content
250
+
251
+ # Find insertion point (alphabetical)
252
+ insertion_idx = len(deps_entries)
253
+ for i, entry in enumerate(deps_entries):
254
+ if '-=' in entry:
255
+ continue
256
+ if entry > new_entry:
257
+ insertion_idx = i
258
+ break
259
+ insertion_idx = i + 1
260
+
261
+ # Insert new entry
262
+ deps_entries.insert(insertion_idx, new_entry)
263
+
264
+ # Reconstruct deps
265
+ new_deps_lines = [f'{indent}{entry}' for entry in deps_entries]
266
+ new_deps_content = '\n' + '\n'.join(new_deps_lines) + f'\n{indent[:-2]}'
267
+
268
+ # Replace in content
269
+ new_content = self.build_content[:deps_start] + new_deps_content + self.build_content[deps_end:]
270
+ sys.stderr.write(f"[OK] Component added to deps\n")
271
+ sys.stderr.flush()
272
+ return True, new_content
273
+
274
+
275
+ def add_component_to_test_target(
276
+ build_content: str,
277
+ component_target: str,
278
+ target_name: str
279
+ ) -> Tuple[bool, str]:
280
+ """Add component target to the appropriate test target's deps.
281
+
282
+ Refactored version using strategy pattern.
283
+
284
+ Args:
285
+ build_content: Content of BUILD file
286
+ component_target: Target path like "//components/xxx/ios:unit_tests"
287
+ target_name: Test target name ('ios_chrome_unittests', etc.)
288
+
289
+ Returns:
290
+ (success, modified_content) tuple
291
+ """
292
+ updater = BuildFileUpdater(build_content, component_target, target_name)
293
+ return updater.update()