Skip to content

Commit

Permalink
Feat/rtl 2 (#340)
Browse files Browse the repository at this point in the history
* feat(rtl): add rtl feature within template language

* test(rtl): add tests!

* chore: bump version
  • Loading branch information
andrewleith authored Dec 11, 2024
1 parent 8ee66e4 commit b3085f8
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/actions/waffles/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ docopt==0.6.2
Flask==2.3.3
markupsafe==2.1.5
setuptools==75.6.0 # required for distutils in Python 3.12
git+https://github.com/cds-snc/[email protected].1#egg=notifications-utils
git+https://github.com/cds-snc/[email protected].2#egg=notifications-utils
39 changes: 39 additions & 0 deletions notifications_utils/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@
FR_CLOSE = r"\[\[/fr\]\]" # matches [[/fr]]
EN_OPEN = r"\[\[en\]\]" # matches [[en]]
EN_CLOSE = r"\[\[/en\]\]" # matches [[/en]]
RTL_OPEN = r"\[\[rtl\]\]" # matches [[rtl]]
RTL_CLOSE = r"\[\[/rtl\]\]" # matches [[/rtl]]
FR_OPEN_LITERAL = "[[fr]]"
FR_CLOSE_LITERAL = "[[/fr]]"
EN_OPEN_LITERAL = "[[en]]"
EN_CLOSE_LITERAL = "[[/en]]"
RTL_OPEN_LITERAL = "[[rtl]]"
RTL_CLOSE_LITERAL = "[[/rtl]]"
BR_TAG = r"<br\s?/>"


Expand Down Expand Up @@ -621,6 +625,20 @@ def escape_lang_tags(_content: str) -> str:
return _content


def escape_rtl_tags(_content: str) -> str:
"""
Escape RTL tags into code tags in the content so mistune doesn't put them inside p tags. This makes it simple
to replace them afterwards, and avoids creating invalid HTML in the process
"""

# check to ensure we have the same number of opening and closing tags before escaping tags
if _content.count(RTL_OPEN_LITERAL) == _content.count(RTL_CLOSE_LITERAL):
_content = _content.replace(RTL_OPEN_LITERAL, f"\n```\n{RTL_OPEN_LITERAL}\n```\n")
_content = _content.replace(RTL_CLOSE_LITERAL, f"\n```\n{RTL_CLOSE_LITERAL}\n```\n")

return _content


def add_language_divs(_content: str) -> str:
"""
Custom parser to add the language divs.
Expand All @@ -646,6 +664,27 @@ def remove_language_divs(_content: str) -> str:
return remove_tags(_content, FR_OPEN, FR_CLOSE, EN_OPEN, EN_CLOSE)


def add_rtl_divs(_content: str) -> str:
"""
Custom parser to add the language divs.
String replace language tags in-place
"""

# check to ensure we have the same number of opening and closing tags before escaping tags
if _content.count(RTL_OPEN_LITERAL) == _content.count(RTL_CLOSE_LITERAL):
_content = _content.replace(RTL_OPEN_LITERAL, '<div dir="rtl">')
_content = _content.replace(RTL_CLOSE_LITERAL, "</div>")

return _content


def remove_rtl_divs(_content: str) -> str:
"""Remove the tags from content. This fn is for use in the email
preheader, since this is plain text not html"""
return remove_tags(_content, RTL_OPEN, RTL_CLOSE)


def remove_tags(_content: str, *tags) -> str:
"""Remove the tags in parameters from content.
Expand Down
6 changes: 6 additions & 0 deletions notifications_utils/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
from notifications_utils.formatters import (
add_language_divs,
add_prefix,
add_rtl_divs,
add_trailing_newline,
autolink_sms,
escape_html,
escape_lang_tags,
escape_rtl_tags,
make_quotes_smart,
nl2br,
nl2li,
Expand All @@ -28,6 +30,7 @@
notify_plain_text_email_markdown,
remove_empty_lines,
remove_language_divs,
remove_rtl_divs,
remove_smart_quotes_from_email_addresses,
remove_whitespace_before_punctuation,
replace_hyphens_with_en_dashes,
Expand Down Expand Up @@ -398,6 +401,7 @@ def preheader(self):
.then(add_trailing_newline)
.then(notify_email_preheader_markdown)
.then(remove_language_divs)
.then(remove_rtl_divs)
.then(do_nice_typography)
.split()
)[: self.PREHEADER_LENGTH_IN_CHARACTERS].strip()
Expand Down Expand Up @@ -805,8 +809,10 @@ def get_html_email_body(template_content, template_values, redact_missing_person
.then(strip_unsupported_characters)
.then(add_trailing_newline)
.then(escape_lang_tags)
.then(escape_rtl_tags)
.then(notify_email_markdown)
.then(add_language_divs)
.then(add_rtl_divs)
.then(do_nice_typography)
)

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "notifications-utils"
version = "53.0.1"
version = "53.0.2"
description = "Shared python code for Notification - Provides logging utils etc."
authors = ["Canadian Digital Service"]
license = "MIT license"
Expand Down
89 changes: 89 additions & 0 deletions tests/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,92 @@ def test_lang_tags_in_templates_bad_content(bad_content: str):
def test_lang_tags_in_templates_good_content(good_content: str):
html = get_html_email_body(good_content, {})
assert '<div lang="fr-ca">' in html


class TestRTLTags:
def test_rtl_tags_in_templates(self):
content = "[[rtl]]\nRTL content\n[[/rtl]]"
html = get_html_email_body(content, {})
assert '<div dir="rtl">' in html
assert "RTL content" in html

@pytest.mark.parametrize(
"nested_content",
[
"[[rtl]]\nRTL content\n[[/rtl]]\n[[rtl]]\nMore RTL content\n[[/rtl]]",
"[[rtl]]\nRTL content with [[en]]\nEN content\n[[/en]]\n[[/rtl]]",
],
)
def test_rtl_tags_in_templates_nested_content(self, nested_content: str):
html = get_html_email_body(nested_content, {})
assert '<div dir="rtl">' in html
assert "RTL content" in html

@pytest.mark.parametrize(
"bad_content",
[
"[[rtl]\nRTL content\n[[/rtl]]", # missing bracket
"[[RTL]]\nRTL content\n[[/RTL]]", # tags not lowercase
"[[rtl]]\nRTL content\n", # tag missing
"RTL content\n[[/rtl]]", # tag missing
"((rtl))\nRTL content\n((/rtl))", # wrong brackets
],
)
def test_rtl_tags_in_templates_bad_content(self, bad_content: str):
html = get_html_email_body(bad_content, {})
assert '<div dir="rtl">' not in html

@pytest.mark.parametrize(
"mixed_content",
[
"[[rtl]]\nRTL content\n[[/rtl]]\nLTR content",
"LTR content\n[[rtl]]\nRTL content\n[[/rtl]]",
],
)
def test_rtl_tags_in_templates_mixed_content(self, mixed_content: str):
html = get_html_email_body(mixed_content, {})
assert '<div dir="rtl">' in html
assert "RTL content" in html
assert "LTR content" in html

@pytest.mark.parametrize(
"content, extra_tag",
[
("[[rtl]] # RTL CONTENT [[/rtl]]", "h2"),
("[[rtl]] ## RTL CONTENT [[/rtl]]", "h3"),
("[[rtl]]\n- RTL CONTENT 1\n-item 2\n[[/rtl]]", "ul"),
("[[rtl]]\n1. RTL CONTENT 1\n1. item 2\n[[/rtl]]", "ol"),
("[[rtl]]**RTL CONTENT**[[/rtl]]", "strong"),
("[[rtl]]_RTL CONTENT_[[/rtl]]", "em"),
("[[rtl]]---\nRTL CONTENT[[/rtl]]", "hr"),
(
"[[rtl]]1. RTL CONTENT\n1. First level\n 1. Second level\n 1. Second level\n 1. Second level\n 1. Third level\n 1. Third level\n 1. Fourth level\n 1. Fourth level\n 1. Fifth level\n 1. Fifth level[[/rtl]]",
"ol",
),
("[[rtl]]^RTL CONTENT[[/rtl]]", "blockquote"),
("[[rtl]]RTL CONTENT now at https://www.canada.ca[[/rtl]]", "a"),
("[[rtl]][RTL CONTENT](https://www.canada.ca/sign-in)[[/rtl]]", "a"),
("[[rtl]][[en]]RTL CONTENT[[/en]][[/rtl]]", 'div lang="en-ca"'),
("[[rtl]][[fr]]RTL CONTENT[[/fr]][[/rtl]]", 'div lang="fr-ca"'),
],
ids=[
"heading_1",
"heading_2",
"list_unordered",
"list_ordered",
"bold",
"italic",
"hr",
"nested_list",
"blockquote",
"link",
"link_with_text",
"nested_lang_tags_en",
"nested_lang_tags_fr",
],
)
def test_rtl_tags_work_with_other_features(self, content: str, extra_tag: str):
html = get_html_email_body(content, {})
assert '<div dir="rtl">' in html
assert "RTL CONTENT" in html
assert "<{}".format(extra_tag) in html

0 comments on commit b3085f8

Please sign in to comment.