Last active
January 16, 2022 16:24
-
-
Save angelikatyborska/fadbde5c3d4f2db25a58a4519d3b94ac to your computer and use it in GitHub Desktop.
A unit test for Gettext translations that checks if the original and the translation use the same HTML tags. Uses Floki to parse HTML.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyAppWeb.GettextTest do | |
use ExUnit.Case | |
import MyAppWeb.Gettext | |
# A unit test for Gettext translations that checks if the original and the translation | |
# use the same HTML tags. | |
# | |
# Uses Floki to parse HTML. | |
describe "translations" do | |
test "all translation use the same HTML tags as the original" do | |
files = | |
"priv/gettext/**/LC_MESSAGES/" | |
|> Path.wildcard() | |
|> Enum.map(fn dir -> Enum.map(File.ls!(dir), &{dir, &1}) end) | |
|> List.flatten() | |
translations = | |
Enum.reduce(files, [], fn {dir, file}, acc -> | |
path = "#{dir}/#{file}" | |
{:ok, po} = | |
path | |
|> File.read!() | |
|> Gettext.PO.parse_string() | |
Enum.reduce(po.translations, acc, fn translation, acc2 -> | |
strings = | |
case translation do | |
%{msgstr: strings} when is_list(strings) -> strings | |
%{msgstr: map} when is_map(map) -> List.flatten(Map.values(map)) | |
end | |
[ | |
%{ | |
id: translation.msgid, | |
path: "#{path}:#{translation.po_source_line}", | |
strings: strings | |
} | |
| acc2 | |
] | |
end) | |
end) | |
translations_with_errors = | |
Enum.reduce(translations, [], fn %{id: id, path: path, strings: strings}, acc -> | |
expected_tags = get_html_tags_from_string(Enum.join(id, " ")) | |
Enum.reduce(strings, [], fn string, acc2 -> | |
actual_tags = get_html_tags_from_string(string) | |
unexpected_tags = actual_tags -- expected_tags | |
missing_tags = expected_tags -- actual_tags | |
if string == "" or (unexpected_tags == [] and missing_tags == []) do | |
acc2 | |
else | |
[%{path: path, missing_tags: missing_tags, unexpected_tags: unexpected_tags} | acc2] | |
end | |
end) ++ | |
acc | |
end) | |
assert Enum.empty?(translations_with_errors), | |
Enum.map(translations_with_errors, fn error -> | |
""" | |
#{error.path}: | |
\tmissing: #{Enum.join(error.missing_tags, ", ")} | |
\t extra: #{Enum.join(error.unexpected_tags, ", ")} | |
""" | |
end) | |
|> Enum.join("\n") | |
end | |
end | |
defp get_html_tags_from_string(string) do | |
html = Floki.parse_fragment!(string) | |
get_html_tags_from_html(html, []) | |
end | |
# sort so that order in which tags appear in the string doesn't matter for comparisons | |
defp get_html_tags_from_html([], acc), do: Enum.sort(acc) | |
defp get_html_tags_from_html([string | rest], acc) when is_bitstring(string) do | |
get_html_tags_from_html(rest, acc) | |
end | |
defp get_html_tags_from_html([{tag, _attrs, children} | rest], acc) do | |
children_tags = get_html_tags_from_html(children, []) | |
get_html_tags_from_html(rest, children_tags ++ [tag | acc]) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment