Files
plezy/scripts/checks/test_check_hardcoded_strings.py
T

196 lines
6.9 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
import contextlib
import io
import json
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
import check_hardcoded_strings
class HardcodedStringsCheckerTest(unittest.TestCase):
def setUp(self) -> None:
self.temporary_directory = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary_directory.cleanup)
self.root = Path(self.temporary_directory.name)
self.lib = self.root / "lib"
self.lib.mkdir()
self.allowlist_path = self.root / "allowlist.json"
self.allowlist_path.write_text("{}\n", encoding="utf-8")
self.enterContext(patch.object(check_hardcoded_strings, "ROOT", self.root))
self.enterContext(patch.object(check_hardcoded_strings, "LIB_DIR", self.lib))
self.enterContext(
patch.object(check_hardcoded_strings, "ALLOWLIST_PATH", self.allowlist_path)
)
def _write_source(self, source: str, relative: str = "widgets/example.dart") -> None:
path = self.lib / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(source, encoding="utf-8")
def _findings(self) -> list[check_hardcoded_strings.Finding]:
findings, _ = check_hardcoded_strings.scan()
return findings
def test_text_literal_is_reported(self):
self._write_source("Widget build() => const Text('Skip Intro');\n")
findings = self._findings()
self.assertEqual([(finding.literal, finding.rule) for finding in findings], [
("Skip Intro", "Text first argument")
])
def test_phrase_bound_to_a_name_in_a_ui_file_is_reported(self):
# The actual shape of issue #1856: the literal never touches Text()
# directly, it is assigned to a local a few lines above the render.
self._write_source(
"Widget build() {\n"
" String label;\n"
" if (isCredits) {\n"
" label = 'Skip Credits';\n"
" } else {\n"
" label = t.videoControls.skipIntro;\n"
" }\n"
" return Text(label);\n"
"}\n"
)
findings = self._findings()
self.assertEqual([(finding.literal, finding.rule) for finding in findings], [
("Skip Credits", "display string bound to a name")
])
def test_identifier_bound_to_a_name_is_not_reported(self):
# Single-token identifiers are indistinguishable from copy by word
# count alone, so the rule requires a whitespace-separated phrase.
self._write_source(
"Widget build() {\n"
" final section = 'cast_row';\n"
" final mode = 'HDR_UNSUPPORTED';\n"
" final tab = 'liveTv';\n"
" return Text(t.common.close);\n"
"}\n"
)
self.assertEqual(self._findings(), [])
def test_phrase_bound_behind_a_log_argument_is_not_reported(self):
self._write_source(
"void run() => promptAndCreate(\n"
" createdLog: (playlist) => 'Successfully created playlist: ${playlist.title}',\n"
" title: Text(t.playlists.create),\n"
");\n"
)
self.assertEqual(self._findings(), [])
def test_phrase_bound_in_a_non_ui_file_is_not_reported(self):
self._write_source(
"String describe() => 'not name=value';\n",
relative="services/plain_service.dart",
)
self.assertEqual(self._findings(), [])
def test_translated_text_is_not_reported(self):
self._write_source("Widget build() => Text(t.videoControls.skipIntro);\n")
self.assertEqual(self._findings(), [])
def test_tooltip_literal_is_reported(self):
self._write_source("Widget build() => IconButton(tooltip: 'Close');\n")
findings = self._findings()
self.assertEqual([(finding.literal, finding.rule) for finding in findings], [
("Close", "UI named argument")
])
def test_mixed_translation_interpolation_is_reported(self):
self._write_source("final value = '${t.common.pause} auto-scroll';\n")
findings = self._findings()
self.assertEqual([(finding.literal, finding.rule) for finding in findings], [
("${t.common.pause} auto-scroll", "mixed translation interpolation")
])
def test_t_lambda_parameter_is_not_mistaken_for_translation_accessor(self):
self._write_source("final title = tracks.map((t) => 'Track ${t.id}');\n")
self.assertEqual(self._findings(), [])
def test_numeric_season_episode_pattern_is_not_reported(self):
self._write_source(
"Widget build() => Text("
"\"${hasIndex ? 'S${season} E${episode}' : ''}\""
");\n"
)
self.assertEqual(self._findings(), [])
def test_diagnostic_and_debug_label_literals_are_not_reported(self):
self._write_source(
"final widget = Thing(debugLabel: 'Developer controls');\n"
"appLogger.i(Text('Developer details'));\n"
)
self.assertEqual(self._findings(), [])
def test_allowlisted_literal_is_not_reported(self):
self._write_source("Widget build() => const Text('Permanent English');\n")
self.allowlist_path.write_text(
json.dumps(
{
"lib/widgets/example.dart": {
"Permanent English": "Deliberate product terminology"
}
}
),
encoding="utf-8",
)
self.assertEqual(self._findings(), [])
def test_stale_allowlist_report_finds_literal_that_matches_nothing(self):
self._write_source("Widget build() => Text(t.common.close);\n")
self.allowlist_path.write_text(
json.dumps(
{
"lib/widgets/example.dart": {
"Removed English": "Former deliberate terminology"
}
}
),
encoding="utf-8",
)
output = io.StringIO()
with contextlib.redirect_stdout(output):
result = check_hardcoded_strings.main(["--allowlist-missing"])
self.assertEqual(result, 1)
self.assertIn("lib/widgets/example.dart: 'Removed English'", output.getvalue())
def test_comments_directives_keys_units_and_generated_files_are_ignored(self):
self._write_source(
"// Text('Comment words')\n"
"import 'Text(\\'Imported words\\')';\n"
"final key = ValueKey('Stable widget identity');\n"
"final duration = Text('min');\n"
)
self._write_source("const Text('Generated English');\n", "model.g.dart")
self._write_source("const Text('Generated English');\n", "model.freezed.dart")
self._write_source("const Text('Generated English');\n", "i18n/generated.dart")
self.assertEqual(self._findings(), [])
if __name__ == "__main__":
unittest.main()