chromium/tools/grit/grit/pseudolocales_unittest.py

#!/usr/bin/env python3
# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
'''Unit tests for grit.pseudolocales'''

import sys
import os.path
import unittest

if __name__ == '__main__':
  sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

import grit.pseudolocales as pl

from grit import tclib
import grit.extern.tclib

PLACEHOLDER_NODE = pl.BasicVariable(pl.PLACEHOLDER_STRING)
VAR_NODE = pl.BasicVariable('{VAR}')


class TclibUnittest(unittest.TestCase):
  def assertBuildTree(self, text, tree):
    self.assertTreesEqual(pl.BuildTree(text), tree)

  def assertTreesEqual(self, lhs, rhs):
    try:
      self.assertSubtreesEqual(lhs, rhs)
    except AssertionError as e:
      print('Actual:', lhs, 'Expected:', rhs, sep='\n')
      raise e

  def assertSubtreesEqual(self, lhs, rhs):
    try:
      self.assertIsInstance(lhs, rhs.__class__)
      self.assertIsInstance(rhs, lhs.__class__)
      self.assertEqual(lhs.text, rhs.text)
      self.assertEqual(lhs.after, rhs.after)
      self.assertEqual(len(lhs.children), len(rhs.children))
    except AssertionError as e:
      print('Failing subtree actual:',
            lhs,
            'Failing subtree expected:',
            rhs,
            sep='\n')
      raise e
    for lhs_child, rhs_child in zip(lhs.children, rhs.children):
      self.assertSubtreesEqual(lhs_child, rhs_child)

  def testSplitTextBasic(self):
    self.assertBuildTree('foo bar baz', pl.RawText('foo bar baz'))

  def testSplitTextTags(self):
    self.assertBuildTree(
        'foo<a href="url">bar</a>baz',
        pl.NodeSequence([
            pl.RawText('foo'),
            pl.HtmlTag('<a href="url">'),
            pl.RawText('bar'),
            pl.HtmlTag('</a>'),
            pl.RawText('baz'),
        ]))

  def testSplitTextVariables(self):
    self.assertBuildTree(
        'Downloaded by <a href="{PLACEHOLDER}">{PLACEHOLDER}</a>${foo}{bar}',
        pl.NodeSequence([
            pl.RawText('Downloaded by '),
            pl.HtmlTag('<a href="{PLACEHOLDER}">'),
            pl.BasicVariable('{PLACEHOLDER}'),
            pl.HtmlTag('</a>'),
            pl.BasicVariable('${foo}'),
            pl.BasicVariable('{bar}'),
        ]))

  def testSplitTextWithCurlies(self):
    self.assertBuildTree(
        '''{COUNT, plural,
  =0  {Open all in &incognito window}
  =1 {Open in &incognito window}
  other {Open all ({COUNT}) in &incognito window}}''',
        pl.Plural('{COUNT, plural,\n  ', [
            pl.PluralOption('=0  {',
                            [pl.RawText('Open all in &incognito window')]),
            pl.PluralOption('=1 {', [pl.RawText('Open in &incognito window')]),
            pl.PluralOption('other {', [
                pl.RawText('Open all ('),
                pl.BasicVariable('{COUNT}'),
                pl.RawText(') in &incognito window'),
            ]),
        ]))

    self.assertBuildTree(
        '''{ATTEMPTS_LEFT, plural,
  =1 {{0} attempt left}
  other {{0}<a>attempts</a> left}}''',
        pl.Plural('{ATTEMPTS_LEFT, plural,\n  ', [
            pl.PluralOption(
                '=1 {', [pl.BasicVariable('{0}'),
                         pl.RawText(' attempt left')]),
            pl.PluralOption('other {', [
                pl.BasicVariable('{0}'),
                pl.HtmlTag('<a>'),
                pl.RawText('attempts'),
                pl.HtmlTag('</a>'),
                pl.RawText(' left')
            ])
        ]))

  def testRawTextNumWords(self):
    self.assertEqual(pl.RawText('hello').GetNumWords(), 1)
    self.assertEqual(pl.RawText('hello world').GetNumWords(), 2)
    self.assertEqual(pl.RawText('  ').GetNumWords(), 0)
    self.assertEqual(pl.RawText('hello \n world').GetNumWords(), 2)
    self.assertEqual(pl.RawText('that\'s nice, right').GetNumWords(), 3)
    self.assertEqual(pl.RawText('1 2 3 4 5').GetNumWords(), 5)

  def testTreeNumWords(self):
    self.assertEqual(pl.HtmlTag('<a href="blah">').GetNumWords(), 0)
    self.assertEqual(pl.BasicVariable('{COUNT}').GetNumWords(), 1)

    self.assertEqual(
        pl.NodeSequence([pl.RawText('hi'),
                         pl.RawText('World')]).GetNumWords(), 2)
    self.assertEqual(
        pl.PluralOption(
            'other {',
            [pl.RawText('hello'), pl.RawText('World')]).GetNumWords(), 2)
    self.assertEqual(
        pl.Plural('{COUNT, plural, {', [
            pl.PluralOption('=0 {', [pl.RawText('1 2')]),
            pl.PluralOption('=0 {', [pl.RawText('1 2 3')]),
            pl.PluralOption('=0 {', [pl.RawText('1 2 3 4 5 6 7 8 9 10')]),
        ]).GetNumWords(), 10)

  def assertTransformsInto(self, initial, expected):
    initial.Transform(lambda x: x.title())
    self.assertTreesEqual(initial, expected)

  def testTransform(self):
    self.assertTransformsInto(pl.RawText('HI WORLD'), pl.RawText('Hi World'))
    self.assertTransformsInto(pl.BasicVariable('{HELLO}'),
                              pl.BasicVariable('{HELLO}'))
    self.assertTransformsInto(pl.HtmlTag('<a>'), pl.HtmlTag('<a>'))
    self.assertTransformsInto(
        pl.NodeSequence(
            [pl.RawText('HELLO'),
             pl.HtmlTag('<a>'),
             pl.RawText('WORLD')]),
        pl.NodeSequence(
            [pl.RawText('Hello'),
             pl.HtmlTag('<a>'),
             pl.RawText('World')]))
    self.assertTransformsInto(
        pl.Plural('{ATTEMPTS_LEFT, plural,\n  ', [
            pl.PluralOption('=1 {', [pl.RawText('hello')]),
            pl.PluralOption('other {', [pl.RawText('world')])
        ]),
        pl.Plural('{ATTEMPTS_LEFT, plural,\n  ', [
            pl.PluralOption('=1 {', [pl.RawText('Hello')]),
            pl.PluralOption('other {', [pl.RawText('World')])
        ]))

  def testToString(self):
    self.assertEqual(pl.RawText('Hello world').ToString(), 'Hello world')
    self.assertEqual(pl.BasicVariable('{0}').ToString(), '{0}')
    self.assertEqual(pl.HtmlTag('<a>').ToString(), '<a>')
    self.assertEqual(
        pl.NodeSequence([pl.RawText('Hello'),
                         pl.RawText('World')]).ToString(), 'HelloWorld')
    self.assertEqual(
        pl.Plural('{ATTEMPTS_LEFT, plural,\n  ', [
            pl.PluralOption('=1 {', [pl.RawText('hello')]),
            pl.PluralOption('other {', [pl.RawText('world')])
        ]).ToString(),
        '{ATTEMPTS_LEFT, plural,\n  =1 {hello}\nother {world}\n}')

  def testBuildAndUnbuildTree(self):
    p1 = tclib.Placeholder('USERNAME', '%s', 'foo')
    p2 = tclib.Placeholder('EMAIL', '%s', 'bar')

    msg = tclib.Message()
    msg.AppendText('hello')
    msg.AppendPlaceholder(p1)
    msg.AppendPlaceholder(p2)
    msg.AppendText('world')

    tree, placeholders = pl.BuildTreeFromMessage(msg)
    self.assertTreesEqual(
        tree,
        pl.NodeSequence([
            pl.RawText('hello'), PLACEHOLDER_NODE, PLACEHOLDER_NODE,
            pl.RawText('world')
        ]))
    self.assertEqual(placeholders, [p1, p2])

    transl = pl.ToTranslation(tree, placeholders)
    self.assertEqual(transl.GetContent(), ['hello', p1, p2, 'world'])

  def testPseudolocales(self):
    p1 = tclib.Placeholder('USERNAME', '%s', 'foo')
    p2 = tclib.Placeholder('EMAIL', '%s', 'bar')
    msg = tclib.Message()
    msg.AppendText('h_')
    msg.AppendPlaceholder(p1)
    msg.AppendPlaceholder(p2)
    msg.AppendText('w')

    self.assertEqual(
        pl.PseudoLongStringMessage(msg).GetContent(),
        ['\u0125_', p1, p2, '\u0175', ' - one two three four'])

    msg.AppendText('hello world')
    self.assertEqual(
        pl.PseudoRTLMessage(msg).GetContent(),
        ['\u202eh\u202c_', p1, p2, '\u202ewhello\u202c \u202eworld\u202c'])

  # If it fails to translate with prod messages, add the failure to here to
  # make sure it doesn't happen again.
  def testProdFailures(self):
    p1 = tclib.Placeholder('USERNAME', '%s', 'foo')

    msg = tclib.Message()
    msg.AppendText('{LINE_COUNT, plural,\n      =1 {<1 line not shown>}\n'
                   '      other {<')
    msg.AppendPlaceholder(p1)
    msg.AppendText(' lines not shown>}\n}')
    tree, _ = pl.BuildTreeFromMessage(msg)
    self.assertTreesEqual(
        tree,
        pl.Plural('{LINE_COUNT, plural,\n      ', [
            pl.PluralOption('=1 {', [pl.RawText('<1 line not shown>')]),
            pl.PluralOption('other {', [
                pl.RawText('<'), PLACEHOLDER_NODE,
                pl.RawText(' lines not shown>')
            ])
        ]))

    msg = tclib.Message()
    msg.AppendText('{1, plural,\n   \n             =1 {Rated ')
    msg.AppendPlaceholder(p1)
    msg.AppendText(' by one user.}\n      other{Rated ')
    msg.AppendPlaceholder(p1)
    msg.AppendText(' by # users.}}')
    tree, _ = pl.BuildTreeFromMessage(msg)
    self.assertTreesEqual(
        tree,
        pl.Plural('{1, plural,\n   \n             ', [
            pl.PluralOption('=1 {', [
                pl.RawText('Rated '), PLACEHOLDER_NODE,
                pl.RawText(' by one user.')
            ]),
            pl.PluralOption('other{', [
                pl.RawText('Rated '), PLACEHOLDER_NODE,
                pl.RawText(' by # users.')
            ]),
        ]))

    self.assertBuildTree(
        '{count, plural, offset:2\n'
        '        =1 {{VAR}}\n'
        '        =2 {{VAR}, {VAR}}\n'
        '        other {{VAR}, {VAR}, and # more}\n'
        '      }',
        pl.Plural('{count, plural, offset:2\n        ', [
            pl.PluralOption('=1 {', [VAR_NODE]),
            pl.PluralOption('=2 {',
                            [VAR_NODE, pl.RawText(', '), VAR_NODE]),
            pl.PluralOption('other {', [
                VAR_NODE,
                pl.RawText(', '), VAR_NODE,
                pl.RawText(', and # more')
            ]),
        ]))

    self.assertBuildTree(
        '{NUM_POPUPS,plural,=1{Pop-up blocked} other{# pop-ups blocked}}',
        pl.Plural('{NUM_POPUPS,plural,', [
            pl.PluralOption('=1{', [pl.RawText('Pop-up blocked')]),
            pl.PluralOption('other{', [pl.RawText('# pop-ups blocked')])
        ]))

    self.assertBuildTree(
        'Open ${url}',
        pl.NodeSequence([pl.RawText('Open '),
                         pl.BasicVariable('${url}')]))


if __name__ == '__main__':
  unittest.main()