@@ -18,7 +18,7 @@ def test_generate_with_large_localized_strings(self):
18
18
localized_file_1 = FileInfo (
19
19
full_path = Path ("en.lproj/Localizable.strings" ),
20
20
path = "en.lproj/Localizable.strings" ,
21
- size = 60 * 1024 , # 60KB
21
+ size = 150 * 1024 , # 150KB
22
22
file_type = "strings" ,
23
23
treemap_type = TreemapType .RESOURCES ,
24
24
hash = "hash1" ,
@@ -27,7 +27,7 @@ def test_generate_with_large_localized_strings(self):
27
27
localized_file_2 = FileInfo (
28
28
full_path = Path ("es.lproj/Localizable.strings" ),
29
29
path = "es.lproj/Localizable.strings" ,
30
- size = 50 * 1024 , # 50KB
30
+ size = 60 * 1024 , # 60KB
31
31
file_type = "strings" ,
32
32
treemap_type = TreemapType .RESOURCES ,
33
33
hash = "hash2" ,
@@ -56,21 +56,17 @@ def test_generate_with_large_localized_strings(self):
56
56
result = self .insight .generate (insights_input )
57
57
58
58
assert isinstance (result , LocalizedStringInsightResult )
59
- assert len (result .files ) == 2
60
- assert result .total_savings == 110 * 1024 # 110KB total
61
- assert result .files [0 ].file_path == "en.lproj/Localizable.strings"
62
- assert result .files [1 ].file_path == "es.lproj/Localizable.strings"
63
- # Verify savings match file sizes
64
- assert result .files [0 ].total_savings == 60 * 1024
65
- assert result .files [1 ].total_savings == 50 * 1024
59
+ # Total savings should be 50% of the total file size (210KB * 0.5 = 105KB)
60
+ expected_total_savings = int ((150 + 60 ) * 1024 * 0.5 )
61
+ assert result .total_savings == expected_total_savings
66
62
67
63
def test_generate_with_small_localized_strings (self ):
68
- """Test that no insight is generated when total size is below 100KB threshold."""
69
- # Create localized strings files that don't exceed 100KB total
64
+ """Test that no insight is generated when estimated savings is below 100KB threshold."""
65
+ # Create localized strings files where estimated savings (50%) don't exceed 100KB
70
66
localized_file_1 = FileInfo (
71
67
full_path = Path ("en.lproj/Localizable.strings" ),
72
68
path = "en.lproj/Localizable.strings" ,
73
- size = 40 * 1024 , # 40KB
69
+ size = 60 * 1024 , # 60KB * 0.5 = 30KB savings
74
70
file_type = "strings" ,
75
71
treemap_type = TreemapType .RESOURCES ,
76
72
hash = "hash1" ,
@@ -79,7 +75,7 @@ def test_generate_with_small_localized_strings(self):
79
75
localized_file_2 = FileInfo (
80
76
full_path = Path ("es.lproj/Localizable.strings" ),
81
77
path = "es.lproj/Localizable.strings" ,
82
- size = 30 * 1024 , # 30KB
78
+ size = 50 * 1024 , # 50KB * 0.5 = 25KB savings (total 55KB < 100KB threshold)
83
79
file_type = "strings" ,
84
80
treemap_type = TreemapType .RESOURCES ,
85
81
hash = "hash2" ,
@@ -97,14 +93,15 @@ def test_generate_with_small_localized_strings(self):
97
93
98
94
result = self .insight .generate (insights_input )
99
95
100
- assert result is None # Should return None when below threshold
96
+ assert result is None # Should return None when estimated savings below threshold
101
97
102
98
def test_generate_with_exactly_threshold_size (self ):
103
- """Test that insight is generated when total size exactly equals 100KB threshold."""
99
+ """Test that insight is generated when estimated savings exceeds 100KB threshold."""
100
+ # Need 200KB to get exactly 100KB savings (200KB * 0.5 = 100KB)
104
101
localized_file = FileInfo (
105
102
full_path = Path ("en.lproj/Localizable.strings" ),
106
103
path = "en.lproj/Localizable.strings" ,
107
- size = 100 * 1024 , # Exactly 100KB
104
+ size = 200 * 1024 , # 200KB * 0.5 = 100KB savings
108
105
file_type = "strings" ,
109
106
treemap_type = TreemapType .RESOURCES ,
110
107
hash = "hash1" ,
@@ -122,7 +119,7 @@ def test_generate_with_exactly_threshold_size(self):
122
119
123
120
result = self .insight .generate (insights_input )
124
121
125
- assert result is None # Should return None when exactly at threshold
122
+ assert result is None # Should return None when exactly at threshold (100KB, not >100KB)
126
123
127
124
def test_generate_with_no_localized_strings (self ):
128
125
"""Test that no insight is generated when no localized strings files exist."""
@@ -173,28 +170,37 @@ def test_generate_with_empty_file_list(self):
173
170
174
171
assert result is None
175
172
176
- def test_generate_ignores_non_localizable_strings (self ):
177
- """Test that only Localizable .strings files are considered, not other .strings files ."""
178
- localized_file = FileInfo (
173
+ def test_generate_includes_all_strings_files_except_denylisted (self ):
174
+ """Test that all .strings files in .lproj directories are considered, except denylisted ones ."""
175
+ localizable_file = FileInfo (
179
176
full_path = Path ("en.lproj/Localizable.strings" ),
180
177
path = "en.lproj/Localizable.strings" ,
181
- size = 150 * 1024 , # 150KB - should trigger insight
178
+ size = 150 * 1024 , # 150KB
182
179
file_type = "strings" ,
183
180
treemap_type = TreemapType .RESOURCES ,
184
181
hash = "hash1" ,
185
182
is_dir = False ,
186
183
)
187
- other_strings_file = FileInfo (
188
- full_path = Path ("en.lproj/Other .strings" ),
189
- path = "en.lproj/Other .strings" ,
190
- size = 50 * 1024 , # 50KB - should be ignored
184
+ infoplist_file = FileInfo (
185
+ full_path = Path ("en.lproj/InfoPlist .strings" ),
186
+ path = "en.lproj/InfoPlist .strings" ,
187
+ size = 100 * 1024 , # 100KB - should be included
191
188
file_type = "strings" ,
192
189
treemap_type = TreemapType .RESOURCES ,
193
190
hash = "hash2" ,
194
191
is_dir = False ,
195
192
)
193
+ launchscreen_file = FileInfo (
194
+ full_path = Path ("en.lproj/LaunchScreen.strings" ),
195
+ path = "en.lproj/LaunchScreen.strings" ,
196
+ size = 30 * 1024 , # 30KB - should be ignored (in denylist)
197
+ file_type = "strings" ,
198
+ treemap_type = TreemapType .RESOURCES ,
199
+ hash = "hash3" ,
200
+ is_dir = False ,
201
+ )
196
202
197
- file_analysis = FileAnalysis (files = [localized_file , other_strings_file ], directories = [])
203
+ file_analysis = FileAnalysis (files = [localizable_file , infoplist_file , launchscreen_file ], directories = [])
198
204
199
205
insights_input = InsightsInput (
200
206
app_info = Mock (spec = BaseAppInfo ),
@@ -206,17 +212,17 @@ def test_generate_ignores_non_localizable_strings(self):
206
212
result = self .insight .generate (insights_input )
207
213
208
214
assert isinstance (result , LocalizedStringInsightResult )
209
- assert len ( result . files ) == 1
210
- assert result . files [ 0 ]. file_path == "en.lproj/Localizable.strings"
211
- assert result . total_savings == 150 * 1024 # Only the Localizable.strings file
212
- assert result .files [ 0 ]. total_savings == 150 * 1024
215
+ # Should include localizable and infoplist, but not launchscreen
216
+ # Total size: (150KB + 100KB) * 0.5 = 125KB savings
217
+ expected_savings = int (( 150 + 100 ) * 1024 * 0.5 )
218
+ assert result .total_savings == expected_savings
213
219
214
220
def test_generate_ignores_non_lproj_localizable_strings (self ):
215
221
"""Test that Localizable.strings files outside .lproj directories are ignored."""
216
222
valid_localized_file = FileInfo (
217
223
full_path = Path ("en.lproj/Localizable.strings" ),
218
224
path = "en.lproj/Localizable.strings" ,
219
- size = 150 * 1024 , # 150KB - should be included
225
+ size = 250 * 1024 , # 250KB - should be included
220
226
file_type = "strings" ,
221
227
treemap_type = TreemapType .RESOURCES ,
222
228
hash = "hash1" ,
@@ -244,7 +250,88 @@ def test_generate_ignores_non_lproj_localizable_strings(self):
244
250
result = self .insight .generate (insights_input )
245
251
246
252
assert isinstance (result , LocalizedStringInsightResult )
247
- assert len (result .files ) == 1
248
- assert result .files [0 ].file_path == "en.lproj/Localizable.strings"
249
- assert result .total_savings == 150 * 1024 # Only the valid file
250
- assert result .files [0 ].total_savings == 150 * 1024
253
+ # Only the valid file should be included: 250KB * 0.5 = 125KB savings
254
+ expected_savings = int (250 * 1024 * 0.5 )
255
+ assert result .total_savings == expected_savings
256
+
257
+ def test_regex_pattern_matching (self ):
258
+ """Test that the regex pattern correctly matches .lproj/.strings files."""
259
+ # Valid patterns that should match
260
+ valid_files = [
261
+ FileInfo (
262
+ full_path = Path ("en.lproj/Localizable.strings" ),
263
+ path = "en.lproj/Localizable.strings" ,
264
+ size = 250 * 1024 , # Large enough to trigger insight after 0.5 ratio
265
+ file_type = "strings" ,
266
+ treemap_type = TreemapType .RESOURCES ,
267
+ hash = "hash1" ,
268
+ is_dir = False ,
269
+ ),
270
+ FileInfo (
271
+ full_path = Path ("Base.lproj/InfoPlist.strings" ),
272
+ path = "Base.lproj/InfoPlist.strings" ,
273
+ size = 30 * 1024 ,
274
+ file_type = "strings" ,
275
+ treemap_type = TreemapType .RESOURCES ,
276
+ hash = "hash2" ,
277
+ is_dir = False ,
278
+ ),
279
+ FileInfo (
280
+ full_path = Path ("pt-BR.lproj/Custom.strings" ),
281
+ path = "pt-BR.lproj/Custom.strings" ,
282
+ size = 20 * 1024 ,
283
+ file_type = "strings" ,
284
+ treemap_type = TreemapType .RESOURCES ,
285
+ hash = "hash3" ,
286
+ is_dir = False ,
287
+ ),
288
+ FileInfo (
289
+ full_path = Path ("some/path/en.lproj/file.strings" ), # Valid: frameworks can have .lproj dirs
290
+ path = "some/path/en.lproj/file.strings" ,
291
+ size = 50 * 1024 ,
292
+ file_type = "strings" ,
293
+ treemap_type = TreemapType .RESOURCES ,
294
+ hash = "hash5" ,
295
+ is_dir = False ,
296
+ ),
297
+ ]
298
+
299
+ # Invalid patterns that should NOT match
300
+ invalid_files = [
301
+ FileInfo (
302
+ full_path = Path ("Localizable.strings" ), # Not in .lproj
303
+ path = "Localizable.strings" ,
304
+ size = 50 * 1024 ,
305
+ file_type = "strings" ,
306
+ treemap_type = TreemapType .RESOURCES ,
307
+ hash = "hash4" ,
308
+ is_dir = False ,
309
+ ),
310
+ FileInfo (
311
+ full_path = Path ("en.lproj/subdir/file.strings" ), # Has subdirectory in .lproj
312
+ path = "en.lproj/subdir/file.strings" ,
313
+ size = 50 * 1024 ,
314
+ file_type = "strings" ,
315
+ treemap_type = TreemapType .RESOURCES ,
316
+ hash = "hash6" ,
317
+ is_dir = False ,
318
+ ),
319
+ ]
320
+
321
+ all_files = valid_files + invalid_files
322
+ file_analysis = FileAnalysis (files = all_files , directories = [])
323
+
324
+ insights_input = InsightsInput (
325
+ app_info = Mock (spec = BaseAppInfo ),
326
+ file_analysis = file_analysis ,
327
+ treemap = Mock (),
328
+ binary_analysis = [],
329
+ )
330
+
331
+ result = self .insight .generate (insights_input )
332
+
333
+ assert isinstance (result , LocalizedStringInsightResult )
334
+ # Should include valid files: (250 + 30 + 20 + 50) * 1024 * 0.5 = 175KB
335
+ # Note: some/path/en.lproj/file.strings is valid (frameworks can have .lproj dirs)
336
+ expected_savings = int ((250 + 30 + 20 + 50 ) * 1024 * 0.5 )
337
+ assert result .total_savings == expected_savings
0 commit comments