Coverage for app / backend / src / tests / test_templating.py: 93%

98 statements  

« prev     ^ index     » next       coverage.py v7.13.2, created at 2026-02-03 06:18 +0000

1# Tests jinja template rendering 

2 

3from datetime import date 

4from typing import Any 

5from zoneinfo import ZoneInfo 

6 

7from markupsafe import Markup 

8 

9from couchers.i18n import LocalizationContext 

10from couchers.i18n.i18next import I18Next 

11from couchers.i18n.plurals import PluralRules 

12from couchers.templating import Jinja2Template 

13 

14 

15def _render_en_utc(template: Jinja2Template, args: dict[str, Any]) -> str: 

16 return template.render(args, LocalizationContext.en_utc(), i18next=I18Next()) 

17 

18 

19def test_multiline() -> None: 

20 template = Jinja2Template(source="{{ text|multiline }}", html=False) 

21 rendered = _render_en_utc(template, {"text": "a\nb"}) 

22 assert rendered == "a\nb" 

23 

24 template = Jinja2Template(source="{{ text|multiline }}", html=True) 

25 rendered = _render_en_utc(template, {"text": "a\nb"}) 

26 assert rendered == "a<br>b" 

27 

28 

29def test_quotelines() -> None: 

30 template = Jinja2Template(source="{{ text|quotelines }}", html=False) 

31 rendered = _render_en_utc(template, {"text": "a\nb"}) 

32 assert rendered == "> a\n> b" 

33 

34 

35def test_html_escaping() -> None: 

36 template = Jinja2Template(source="Hello {{ name }}!", html=True) 

37 rendered = _render_en_utc(template, {"name": "<script />"}) 

38 assert rendered == "Hello &lt;script /&gt;!" 

39 

40 

41def test_safe_html() -> None: 

42 template = Jinja2Template(source="Hello {{ name|html }}!", html=True) 

43 rendered = _render_en_utc(template, {"name": "<script />"}) 

44 assert rendered == "Hello <script />!" 

45 

46 

47def test_date_formatting() -> None: 

48 the_date = date(1970, 1, 1) 

49 template = Jinja2Template(source="Date: {{ date }}", html=False) 

50 rendered = _render_en_utc(template, {"date": the_date}) 

51 assert rendered == "Date: Thursday 1 January 1970" 

52 

53 

54def _greeting_i18next(value: str) -> I18Next: 

55 i18next = I18Next() 

56 language = i18next.add_language("en", PluralRules.en) 

57 language.add_string("greeting", value) 

58 return i18next 

59 

60 

61def _i18next_from_dict(value: dict[str, dict[str, str]]) -> I18Next: 

62 i18next = I18Next() 

63 for lang_code, strings in value.items(): 

64 language = i18next.add_language(lang_code, PluralRules.en) 

65 language.load_json_dict(strings) 

66 return i18next 

67 

68 

69def test_translate_no_substitutions() -> None: 

70 template = Jinja2Template(source='{{ "greeting"|translate }}', html=False) 

71 i18next = _greeting_i18next("Hello!") 

72 rendered = template.render({}, LocalizationContext.en_utc(), i18next) 

73 assert rendered == "Hello!" 

74 

75 

76def test_translate_multiple_languages() -> None: 

77 template = Jinja2Template(source='{{ "greeting"|translate }}', html=False) 

78 i18next = I18Next() 

79 i18next.add_language("en", PluralRules.en).add_string("greeting", "Hello!") 

80 i18next.add_language("fr", PluralRules.en).add_string("greeting", "Bonjour!") 

81 fr_loc_context = LocalizationContext(locale="fr", timezone=ZoneInfo("Etc/UTC")) 

82 rendered = template.render({}, fr_loc_context, i18next) 

83 assert rendered == "Bonjour!" 

84 

85 

86def test_translate_with_substitutions() -> None: 

87 template = Jinja2Template(source='{{ "greeting"|translate(name=user_name) }}', html=False) 

88 i18next = _greeting_i18next("Hello, {{name}}!") 

89 rendered = template.render({"user_name": "Jack"}, LocalizationContext.en_utc(), i18next) 

90 assert rendered == "Hello, Jack!" 

91 

92 

93def test_translate_substitution_escaping() -> None: 

94 template = Jinja2Template(source='{{ "greeting"|translate(name=name) }}', html=True) 

95 i18next = _greeting_i18next("Hello, {{name}}!") 

96 rendered = template.render({"name": "<script />"}, LocalizationContext.en_utc(), i18next) 

97 assert rendered == "Hello, &lt;script /&gt;!" 

98 

99 

100def test_translate_substitution_safe_html() -> None: 

101 template = Jinja2Template(source='{{ "greeting"|translate(name=name) }}', html=True) 

102 i18next = _greeting_i18next("Hello, {{name}}!") 

103 rendered = template.render({"name": Markup("<script />")}, LocalizationContext.en_utc(), i18next) 

104 assert rendered == "Hello, <script />!" 

105 

106 

107def test_translate_translation_tags() -> None: 

108 template = Jinja2Template(source='{{ "greeting"|translate }}', html=True) 

109 i18next = _greeting_i18next("<b>Hello!</b>") 

110 rendered = template.render({}, LocalizationContext.en_utc(), i18next) 

111 assert rendered == "<b>Hello!</b>" 

112 

113 

114def test_translate_newlines_br() -> None: 

115 template = Jinja2Template(source='{{ "greeting"|translate }}', html=True) 

116 i18next = _greeting_i18next("Hello!\nWelcome!") 

117 rendered = template.render({}, LocalizationContext.en_utc(), i18next) 

118 assert rendered == "Hello!<br>Welcome!" 

119 

120 

121def test_translate_plain_strip_tags() -> None: 

122 template = Jinja2Template(source='{{ "greeting"|translate }}', html=False) 

123 i18next = _greeting_i18next("<b>Hello!</b>") 

124 rendered = template.render({}, LocalizationContext.en_utc(), i18next) 

125 assert rendered == "Hello!" 

126 

127 

128def test_translate_plain_strip_links() -> None: 

129 template = Jinja2Template(source='{{ "greeting"|translate }}', html=False) 

130 i18next = _greeting_i18next('<a href="https://example.com">Hello!</a>') 

131 rendered = template.render({}, LocalizationContext.en_utc(), i18next) 

132 assert rendered == "<https://example.com>" 

133 

134 

135def test_translate_plain_strip_mailto() -> None: 

136 template = Jinja2Template(source='{{ "greeting"|translate }}', html=False) 

137 i18next = _greeting_i18next('<a href="mailto:me@example.com">Hello!</a>') 

138 rendered = template.render({}, LocalizationContext.en_utc(), i18next) 

139 assert rendered == "<me@example.com>"