|
19 | 19 | from weblate.glossary.models import get_glossary_terms, get_glossary_tsv |
20 | 20 | from weblate.glossary.tasks import ( |
21 | 21 | cleanup_stale_glossaries, |
| 22 | + get_stale_glossary_translations, |
22 | 23 | sync_terminology, |
23 | 24 | ) |
24 | 25 | from weblate.lang.models import Language |
| 26 | +from weblate.trans.alerts.registry import update_alerts |
25 | 27 | from weblate.trans.models import PendingUnitChange, Unit |
26 | 28 | from weblate.trans.tests.test_views import ViewTestCase |
27 | 29 | from weblate.trans.tests.utils import get_test_file |
@@ -136,6 +138,35 @@ def add_term(self, source, target, context="") -> None: |
136 | 138 | ) |
137 | 139 | self.glossary.invalidate_cache() |
138 | 140 |
|
| 141 | + def make_glossary_language_stale( |
| 142 | + self, language_code: str, source: str | None = None |
| 143 | + ) -> Translation: |
| 144 | + language = Language.objects.get(code=language_code) |
| 145 | + self.component.translation_set.filter(language=language).delete() |
| 146 | + glossary = self.glossary_component.translation_set.get(language=language) |
| 147 | + if source is not None: |
| 148 | + with self.captureOnCommitCallbacks(execute=True): |
| 149 | + glossary.add_unit(None, "", source, source, author=self.user) |
| 150 | + return glossary |
| 151 | + |
| 152 | + def assert_unused_glossary_language_alert(self, *language_codes: str) -> None: |
| 153 | + if not language_codes: |
| 154 | + self.assertFalse( |
| 155 | + self.glossary_component.alert_set.filter( |
| 156 | + name="UnusedGlossaryLanguage" |
| 157 | + ).exists() |
| 158 | + ) |
| 159 | + return |
| 160 | + |
| 161 | + alert = self.glossary_component.alert_set.get(name="UnusedGlossaryLanguage") |
| 162 | + self.assertEqual( |
| 163 | + { |
| 164 | + occurrence["language_code"] |
| 165 | + for occurrence in alert.details["occurrences"] |
| 166 | + }, |
| 167 | + set(language_codes), |
| 168 | + ) |
| 169 | + |
139 | 170 | def test_import(self) -> None: |
140 | 171 | """Test for importing of TBX into glossary.""" |
141 | 172 |
|
@@ -651,6 +682,83 @@ def test_stale_glossaries_cleanup(self) -> None: |
651 | 682 | self.glossary_component.translation_set.count(), initial_count - 1 |
652 | 683 | ) |
653 | 684 |
|
| 685 | + def test_stale_glossary_translations(self) -> None: |
| 686 | + self.assertFalse(get_stale_glossary_translations(self.project).exists()) |
| 687 | + |
| 688 | + self.make_glossary_language_stale("de") |
| 689 | + |
| 690 | + self.assertEqual( |
| 691 | + list( |
| 692 | + get_stale_glossary_translations(self.project).values_list( |
| 693 | + "language_code", flat=True |
| 694 | + ) |
| 695 | + ), |
| 696 | + ["de"], |
| 697 | + ) |
| 698 | + |
| 699 | + self.component.delete() |
| 700 | + |
| 701 | + self.assertFalse(get_stale_glossary_translations(self.project).exists()) |
| 702 | + |
| 703 | + def test_unused_glossary_language_alert(self) -> None: |
| 704 | + glossary = self.make_glossary_language_stale("de", "unused de") |
| 705 | + |
| 706 | + cleanup_stale_glossaries(self.project.id) |
| 707 | + |
| 708 | + self.assertTrue( |
| 709 | + self.glossary_component.translation_set.filter(pk=glossary.pk).exists() |
| 710 | + ) |
| 711 | + self.assert_unused_glossary_language_alert() |
| 712 | + |
| 713 | + update_alerts(self.glossary_component, {"UnusedGlossaryLanguage"}) |
| 714 | + |
| 715 | + self.assert_unused_glossary_language_alert("de") |
| 716 | + alert = self.glossary_component.alert_set.get(name="UnusedGlossaryLanguage") |
| 717 | + removal_url = f"{glossary.get_absolute_url()}#organize" |
| 718 | + |
| 719 | + self.assertFalse(self.user.has_perm("translation.delete", glossary)) |
| 720 | + self.assertNotIn(removal_url, alert.render(self.user)) |
| 721 | + |
| 722 | + self.make_manager() |
| 723 | + self.user.clear_cache() |
| 724 | + self.assertTrue(self.user.has_perm("translation.delete", glossary)) |
| 725 | + self.assertIn(removal_url, alert.render(self.user)) |
| 726 | + |
| 727 | + def test_unused_glossary_language_alert_ignores_blank_local_glossary(self) -> None: |
| 728 | + self.make_glossary_language_stale("de") |
| 729 | + |
| 730 | + update_alerts(self.glossary_component, {"UnusedGlossaryLanguage"}) |
| 731 | + |
| 732 | + self.assert_unused_glossary_language_alert() |
| 733 | + |
| 734 | + def test_unused_glossary_language_alert_ignores_matching_language(self) -> None: |
| 735 | + update_alerts(self.glossary_component, {"UnusedGlossaryLanguage"}) |
| 736 | + |
| 737 | + self.assert_unused_glossary_language_alert() |
| 738 | + |
| 739 | + def test_unused_glossary_language_alert_ignores_glossary_only_project(self) -> None: |
| 740 | + self.component.delete() |
| 741 | + |
| 742 | + update_alerts(self.glossary_component, {"UnusedGlossaryLanguage"}) |
| 743 | + |
| 744 | + self.assert_unused_glossary_language_alert() |
| 745 | + |
| 746 | + def test_unused_glossary_language_alert_updates_on_removal(self) -> None: |
| 747 | + czech_glossary = self.make_glossary_language_stale("cs", "unused cs") |
| 748 | + german_glossary = self.make_glossary_language_stale("de", "unused de") |
| 749 | + update_alerts(self.glossary_component, {"UnusedGlossaryLanguage"}) |
| 750 | + self.assert_unused_glossary_language_alert("cs", "de") |
| 751 | + |
| 752 | + with self.captureOnCommitCallbacks(execute=True): |
| 753 | + german_glossary.remove(self.user) |
| 754 | + |
| 755 | + self.assert_unused_glossary_language_alert("cs") |
| 756 | + |
| 757 | + with self.captureOnCommitCallbacks(execute=True): |
| 758 | + czech_glossary.remove(self.user) |
| 759 | + |
| 760 | + self.assert_unused_glossary_language_alert() |
| 761 | + |
654 | 762 | def test_prohibited_initial_character(self) -> None: |
655 | 763 | """Test that a prohibited initial character in views.""" |
656 | 764 | self.make_manager() |
|
0 commit comments