chromium/tools/grit/grit/format/html_inline_unittest.py

#!/usr/bin/env python3
# Copyright 2012 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.format.html_inline'''


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

import unittest

from grit import util
from grit.format import html_inline


class FakeGrdNode:
  def EvaluateCondition(self, cond):
    return eval(cond)


class HtmlInlineUnittest(unittest.TestCase):
  '''Unit tests for HtmlInline.'''

  @classmethod
  def setUpClass(cls):
    os.environ["root_gen_dir"] = "gen"

  def testGetResourceFilenames(self):
    '''Tests that all included files are returned by GetResourceFilenames.'''

    files = {
      'index.html': '''
      <!DOCTYPE HTML>
      <html>
        <head>
          <link rel="stylesheet" href="test.css">
          <link rel="stylesheet"
              href="really-long-long-long-long-long-test.css">
        </head>
        <body>
          <include src='test.html'>
          <include
              src="really-long-long-long-long-long-test-file-omg-so-long.html">
          <script src="foo.js"></script>
        </body>
      </html>
      ''',

      'test.html': '''
      <include src="test2.html">
      ''',

      'really-long-long-long-long-long-test-file-omg-so-long.html': '''
      <!-- This really long named resource should be included. -->
      ''',

      'test2.html': '''
      <!-- This second level resource should also be included. -->
      ''',

      'test.css': '''
      .image {
        background: url('test.png');
      }
      ''',

      'really-long-long-long-long-long-test.css': '''
      a:hover {
        font-weight: bold;  /* Awesome effect is awesome! */
      }
      ''',

      'test.png': 'PNG DATA',

      'foo.js': '''
      console.log('hello foo');
      ''',
    }

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(filename))

    resources = html_inline.GetResourceFilenames(tmp_dir.GetPath('index.html'),
                                                 None)
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    tmp_dir.CleanUp()

  def testGetResourceFilenamesWithGeneratedFile(self):
    '''Tests that included files are returned by GetResourceFilenames, even when
       generated files are inlined, and that no exception is thrown by
       accidentally trying to read generated files which are not guaranteed to
       exist yet (prod case is when this is invoked from grit_info.py).'''

    # Create an HTML file that attempts to inline a generated JS file.
    # Intentionally don't create the generated JS file to simulate he case where
    # it does not exist yet, by the time GetResourceFilenames() is invoked.
    files = {
      'index.html': '''
      <!DOCTYPE HTML>
      <html>
        <body>
          <script src="%ROOT_GEN_DIR%/does_not_exist.js"></script>
        </body>
      </html>
      ''',
    }

    source_resources = set()
    tmp_dir = util.TempDir(files)
    # `root_gen_dir` environment valiable must be specified relative to the
    # current working directory.
    os.environ["root_gen_dir"] = os.path.relpath(
        os.path.join(tmp_dir.GetPath(), 'gen'))

    source_resources.add(
        tmp_dir.GetPath(os.path.join('gen', 'does_not_exist.js')))

    resources = html_inline.GetResourceFilenames(tmp_dir.GetPath('index.html'),
                                                 None)

    self.assertEqual(resources, source_resources)
    tmp_dir.CleanUp()

  def testUnmatchedEndIfBlock(self):
    '''Tests that an unmatched </if> raises an exception.'''

    files = {
      'index.html': '''
      <!DOCTYPE HTML>
      <html>
        <if expr="lang == 'fr'">
          bonjour
        </if>
        <if expr='lang == "de"'>
          hallo
        </if>
        </if>
      </html>
      ''',
    }

    tmp_dir = util.TempDir(files)

    with self.assertRaises(Exception) as cm:
      html_inline.GetResourceFilenames(tmp_dir.GetPath('index.html'), None)
    self.assertEqual(str(cm.exception), 'Unmatched </if>')
    tmp_dir.CleanUp()

  def testCompressedJavaScript(self):
    '''Tests that ".src=" doesn't treat as a tag.'''

    files = {
      'index.js': '''
      if(i<j)a.src="hoge.png";
      ''',
    }

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(filename))

    resources = html_inline.GetResourceFilenames(tmp_dir.GetPath('index.js'),
                                                 None)
    resources.add(tmp_dir.GetPath('index.js'))
    self.assertEqual(resources, source_resources)
    tmp_dir.CleanUp()

  def testInlineCSSImports(self):
    '''Tests that @import directives in inlined CSS files are inlined too.
    '''

    files = {
      'index.html': '''
      <html>
      <head>
      <link rel="stylesheet" href="css/test.css">
      </head>
      </html>
      ''',

      'css/test.css': '''
      @import url('test2.css');
      blink {
        display: none;
      }
      ''',

      'css/test2.css': '''
      .image {
        background: url('../images/test.png');
      }
      '''.strip(),

      'images/test.png': 'PNG DATA'
    }

    expected_inlined = '''
      <html>
      <head>
      <style>
      .image {
        background: url('data:image/png;base64,UE5HIERBVEE=');
      }
      blink {
        display: none;
      }
      </style>
      </head>
      </html>
      '''

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(util.normpath(filename)))

    result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    self.assertEqual(expected_inlined,
                         util.FixLineEnd(result.inlined_data, '\n'))

    tmp_dir.CleanUp()

  def testInlineIgnoresPolymerBindings(self):
    '''Tests that polymer bindings are ignored when inlining.
    '''

    files = {
      'index.html': '''
      <html>
      <head>
      <link rel="stylesheet" href="test.css">
      </head>
      <body>
        <iron-icon src="[[icon]]"></iron-icon><!-- Should be ignored. -->
        <iron-icon src="{{src}}"></iron-icon><!-- Also ignored. -->
        <!-- [[image]] should be ignored. -->
        <div style="background: url([[image]]),
                                url('test.png');">
        </div>
        <div style="background: url('test.png'),
                                url([[image]]);">
        </div>
      </body>
      </html>
      ''',

      'test.css': '''
      .image {
        background: url('test.png');
        background-image: url([[ignoreMe]]);
        background-image: image-set(url({{alsoMe}}), 1x);
        background-image: image-set(
            url({{ignore}}) 1x,
            url('test.png') 2x);
      }
      ''',

      'test.png': 'PNG DATA'
    }

    expected_inlined = '''
      <html>
      <head>
      <style>
      .image {
        background: url('data:image/png;base64,UE5HIERBVEE=');
        background-image: url([[ignoreMe]]);
        background-image: image-set(url({{alsoMe}}), 1x);
        background-image: image-set(
            url({{ignore}}) 1x,
            url('data:image/png;base64,UE5HIERBVEE=') 2x);
      }
      </style>
      </head>
      <body>
        <iron-icon src="[[icon]]"></iron-icon><!-- Should be ignored. -->
        <iron-icon src="{{src}}"></iron-icon><!-- Also ignored. -->
        <!-- [[image]] should be ignored. -->
        <div style="background: url([[image]]),
                                url('data:image/png;base64,UE5HIERBVEE=');">
        </div>
        <div style="background: url('data:image/png;base64,UE5HIERBVEE='),
                                url([[image]]);">
        </div>
      </body>
      </html>
      '''

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(util.normpath(filename)))

    result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    self.assertEqual(expected_inlined,
                         util.FixLineEnd(result.inlined_data, '\n'))

    tmp_dir.CleanUp()

  def testInlineCSSWithIncludeDirective(self):
    '''Tests that include directive in external css files also inlined'''

    files = {
      'index.html': '''
      <html>
      <head>
      <link rel="stylesheet" href="foo.css">
      </head>
      </html>
      ''',

      'foo.css': '''<include src="style.css">''',

      'style.css': '''
      <include src="style2.css">
      blink {
        display: none;
      }
      ''',
      'style2.css': '''h1 {}''',
    }

    expected_inlined = '''
      <html>
      <head>
      <style>
      h1 {}
      blink {
        display: none;
      }
      </style>
      </head>
      </html>
      '''

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(filename))

    result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    self.assertEqual(expected_inlined,
                         util.FixLineEnd(result.inlined_data, '\n'))
    tmp_dir.CleanUp()

  def testCssIncludedFileNames(self):
    '''Tests that all included files from css are returned'''

    files = {
      'index.html': '''
      <!DOCTYPE HTML>
      <html>
        <head>
          <link rel="stylesheet" href="test.css">
        </head>
        <body>
        </body>
      </html>
      ''',

      'test.css': '''
      <include src="test2.css">
      ''',

      'test2.css': '''
      <include src="test3.css">
      .image {
        background: url('test.png');
      }
      ''',

      'test3.css': '''h1 {}''',

      'test.png': 'PNG DATA'
    }

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(filename))

    resources = html_inline.GetResourceFilenames(tmp_dir.GetPath('index.html'),
                                                 None)
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    tmp_dir.CleanUp()

  def testInlineCSSLinks(self):
    '''Tests that only CSS files referenced via relative URLs are inlined.'''

    files = {
      'index.html': '''
      <html>
      <head>
      <link rel="stylesheet" href="foo.css">
      <link rel="stylesheet" href="chrome://resources/bar.css">
      </head>
      </html>
      ''',

      'foo.css': '''
      @import url(chrome://resources/blurp.css);
      blink {
        display: none;
      }
      ''',
    }

    expected_inlined = '''
      <html>
      <head>
      <style>
      @import url(chrome://resources/blurp.css);
      blink {
        display: none;
      }
      </style>
      <link rel="stylesheet" href="chrome://resources/bar.css">
      </head>
      </html>
      '''

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(filename))

    result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    self.assertEqual(expected_inlined,
                         util.FixLineEnd(result.inlined_data, '\n'))
    tmp_dir.CleanUp()

  def testFilenameRootGenDirExpansion(self):
    '''Tests that %ROOT_GEN_DIR tokens in filenames are properly expanded'''

    files = {
      'index.html': '''
      <html>
      <body>
      <script src="%ROOT_GEN_DIR%/foo/bar/generated.js"></script>
      </body>
      </html>
      ''',
    }
    files[os.path.join('gen', 'foo', 'bar', 'generated.js')] = \
        '''console.log('hello generated');'''

    expected_inlined = '''
      <html>
      <body>
      <script>console.log('hello generated');</script>
      </body>
      </html>
      '''

    source_resources = set()
    tmp_dir = util.TempDir(files)
    os.environ["root_gen_dir"] = os.path.relpath(
        os.path.join(tmp_dir.GetPath(), 'gen'))
    for filename in files:
      source_resources.add(tmp_dir.GetPath(filename))

    result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    self.assertMultiLineEqual(expected_inlined, result.inlined_data)

  def testFilenameVariableExpansion(self):
    '''Tests that variables are expanded in filenames before inlining.'''

    files = {
      'index.html': '''
      <html>
      <head>
      <link rel="stylesheet" href="style[WHICH].css">
      <script src="script[WHICH].js"></script>
      </head>
      <include src="tmpl[WHICH].html">
      <img src="img[WHICH].png">
      </html>
      ''',
      'style1.css': '''h1 {}''',
      'tmpl1.html': '''<h1></h1>''',
      'script1.js': '''console.log('hello');''',
      'img1.png': '''abc''',
    }

    expected_inlined = '''
      <html>
      <head>
      <style>h1 {}</style>
      <script>console.log('hello');</script>
      </head>
      <h1></h1>
      <img src="data:image/png;base64,YWJj">
      </html>
      '''

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(filename))

    def replacer(var, repl):
      return lambda filename: filename.replace('[%s]' % var, repl)

    # Test normal inlining.
    result = html_inline.DoInline(
        tmp_dir.GetPath('index.html'),
        None,
        filename_expansion_function=replacer('WHICH', '1'))
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    self.assertEqual(expected_inlined,
                         util.FixLineEnd(result.inlined_data, '\n'))

    # Test names-only inlining.
    result = html_inline.DoInline(
        tmp_dir.GetPath('index.html'),
        None,
        names_only=True,
        filename_expansion_function=replacer('WHICH', '1'))
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    tmp_dir.CleanUp()

  def testWithCloseTags(self):
    '''Tests that close tags are removed.'''

    files = {
      'index.html': '''
      <html>
      <head>
      <link rel="stylesheet" href="style1.css"></link>
      <link rel="stylesheet" href="style2.css">
      </link>
      <link rel="stylesheet" href="style2.css"
      >
      </link>
      <script src="script1.js"></script>
      </head>
      <include src="tmpl1.html"></include>
      <include src="tmpl2.html">
      </include>
      <include src="tmpl2.html"
      >
      </include>
      <img src="img1.png">
      <include src='single-double-quotes.html"></include>
      <include src="double-single-quotes.html'></include>
      </html>
      ''',
      'style1.css': '''h1 {}''',
      'style2.css': '''h2 {}''',
      'tmpl1.html': '''<h1></h1>''',
      'tmpl2.html': '''<h2></h2>''',
      'script1.js': '''console.log('hello');''',
      'img1.png': '''abc''',
    }

    expected_inlined = '''
      <html>
      <head>
      <style>h1 {}</style>
      <style>h2 {}</style>
      <style>h2 {}</style>
      <script>console.log('hello');</script>
      </head>
      <h1></h1>
      <h2></h2>
      <h2></h2>
      <img src="data:image/png;base64,YWJj">
      <include src='single-double-quotes.html"></include>
      <include src="double-single-quotes.html'></include>
      </html>
      '''

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(filename))

    # Test normal inlining.
    result = html_inline.DoInline(
        tmp_dir.GetPath('index.html'),
        None)
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    self.assertEqual(expected_inlined,
                         util.FixLineEnd(result.inlined_data, '\n'))
    tmp_dir.CleanUp()

  def testCommentedJsInclude(self):
    '''Tests that <include> works inside a comment.'''

    files = {
      'include.js': '// <include src="other.js">',
      'other.js': '// Copyright somebody\nalert(1);',
    }

    expected_inlined = '// Copyright somebody\nalert(1);'

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(filename))

    result = html_inline.DoInline(tmp_dir.GetPath('include.js'), None)
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('include.js'))
    self.assertEqual(resources, source_resources)
    self.assertEqual(expected_inlined,
                         util.FixLineEnd(result.inlined_data, '\n'))
    tmp_dir.CleanUp()

  def testCommentedJsIf(self):
    '''Tests that <if> works inside a comment.'''

    files = {
      'if.js': '''
      // <if expr="True">
      yep();
      // </if>

      // <if expr="False">
      nope();
      // </if>
      ''',
    }

    expected_inlined = '''
      // 
      yep();
      // 

      // 
      '''

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(filename))

    result = html_inline.DoInline(tmp_dir.GetPath('if.js'), FakeGrdNode())
    resources = result.inlined_files

    resources.add(tmp_dir.GetPath('if.js'))
    self.assertEqual(resources, source_resources)
    self.assertEqual(expected_inlined,
                         util.FixLineEnd(result.inlined_data, '\n'))
    tmp_dir.CleanUp()

  def testBasicRemovalComments(self):
    input = '''Line 1
// <if expr="True">
Line 3
// </if> Line 4
Line 5
// <if expr="False">
// Line 7 removed
// </if>
Line 9
'''
    expected_result_html = '''Line 1
// 
Line 3
//  Line 4
Line 5
// <!--grit-removed-lines:2-->
Line 9
'''
    result = html_inline.CheckConditionalElements(FakeGrdNode(), input, '.html')
    self.assertMultiLineEqual(result, expected_result_html)
    expected_result_js = '''Line 1
// 
Line 3
//  Line 4
Line 5
// /*grit-removed-lines:2*/
Line 9
'''
    result = html_inline.CheckConditionalElements(FakeGrdNode(), input, '.js')
    self.assertMultiLineEqual(result, expected_result_js)
    # add_remove_comments_for of None means no comments.
    expected_result_no_file_extension = '''Line 1
// 
Line 3
//  Line 4
Line 5
// 
Line 9
'''
    result = html_inline.CheckConditionalElements(FakeGrdNode(), input, None)
    self.assertMultiLineEqual(result, expected_result_no_file_extension)

  def testNewlinesInIf(self):
    input = '''Line 1
Line 2 <if expr="[
  'Line 3 removed'
]"> Line 4
</if> Line 5
<if expr="[
  'Line 7 removed'
] and False"> Line 8 removed
Line 9 also removed</if>
<if 
expr="True">Line 11</if>'''
    result = html_inline.CheckConditionalElements(FakeGrdNode(), input)
    expected_result = '''Line 1
Line 2  Line 4
 Line 5

Line 11'''
    self.assertMultiLineEqual(result, expected_result)

  def testNewlinesInIfWithComments(self):
    input = '''Line 1
Line 2 <if expr="[
  'Line 3 removed'
]"> Line 4
</if> Line 5
<if expr="[
  'Line 7 removed'
] and False"> Line 8 removed
Line 9 also removed</if>
<if 
expr="True">Line 11</if>'''
    result = html_inline.CheckConditionalElements(FakeGrdNode(), input, '.ts')
    expected_result = '''Line 1
Line 2 /*grit-removed-lines:2*/ Line 4
 Line 5
/*grit-removed-lines:3*/
/*grit-removed-lines:1*/Line 11'''
    self.assertMultiLineEqual(result, expected_result)

  def testRemovePartOfLine(self):
    input = ('Long line<if expr="False">stuff</if>and more<'
             'if expr="True">stuff</if> and done')
    expected_result = 'Long lineand morestuff and done'
    result = html_inline.CheckConditionalElements(FakeGrdNode(), input)
    self.assertEqual(result, expected_result)
    result = html_inline.CheckConditionalElements(FakeGrdNode(), input, '.js')
    self.assertEqual(result, expected_result)

  def testRemovalNested(self):
    input = '''L1
L2 <if expr="True">
  L3 <if expr="True">
    L4 True inside True kept
  L5 </if>
L6 </if>
L7 <if expr="True">
  L8 <if expr="False">
    L9 False inside True removed
  L10 removed </if>
L11 </if>
L12 <if expr="False">
  L13 removed <if expr="True">
    L14 True inside False removed
  L15 removed </if>
L16 removed </if>
L17 <if expr="False">
  L18 removed <if expr="False">
    L19 False inside False removed
  L20 removed </if>
L21 removed </if>
'''
    result = html_inline.CheckConditionalElements(FakeGrdNode(), input)
    expected_result = '''L1
L2 
  L3 
    L4 True inside True kept
  L5 
L6 
L7 
  L8 
L11 
L12 
L17 
'''
    self.assertMultiLineEqual(result, expected_result)

  def testErasureListNestedWithComments(self):
    input = '''L1
L2 <if expr="True">
  L3 <if expr="True">
    L4 True inside True kept
  L5 </if>
L6 </if>
L7 <if expr="True">
  L8 <if expr="False">
    L9 False inside True removed
  L10 removed </if>
L11 </if>
L12 <if expr="False">
  L13 removed <if expr="True">
    L14 True inside False removed
  L15 removed </if>
L16 removed </if>
L17 <if expr="False">
  L18 removed <if expr="False">
    L19 False inside False removed
  L20 removed </if>
L21 removed </if>
'''
    result = html_inline.CheckConditionalElements(FakeGrdNode(), input, '.css')
    expected_result = '''L1
L2 
  L3 
    L4 True inside True kept
  L5 
L6 
L7 
  L8 /*grit-removed-lines:2*/
L11 
L12 /*grit-removed-lines:4*/
L17 /*grit-removed-lines:4*/
'''
    self.assertMultiLineEqual(result, expected_result)

  def testImgSrcset(self):
    '''Tests that img srcset="" attributes are converted.'''

    # Note that there is no space before "img10.png" and that
    # "img11.png" has no descriptor.
    files = {
      'index.html': '''
      <html>
      <img src="img1.png" srcset="img2.png 1x, img3.png 2x">
      <img src="img4.png" srcset=" img5.png   1x , img6.png 2x ">
      <img src="chrome://theme/img11.png" srcset="img7.png 1x, '''\
          '''chrome://theme/img13.png 2x">
      <img srcset="img8.png 300w, img9.png 11E-2w,img10.png -1e2w">
      <img srcset="img11.png">
      <img srcset="img11.png, img2.png 1x">
      <img srcset="img2.png 1x, img11.png">
      </html>
      ''',
      'img1.png': '''a1''',
      'img2.png': '''a2''',
      'img3.png': '''a3''',
      'img4.png': '''a4''',
      'img5.png': '''a5''',
      'img6.png': '''a6''',
      'img7.png': '''a7''',
      'img8.png': '''a8''',
      'img9.png': '''a9''',
      'img10.png': '''a10''',
      'img11.png': '''a11''',
    }

    expected_inlined = '''
      <html>
      <img src="data:image/png;base64,YTE=" srcset="data:image/png;base64,'''\
          '''YTI= 1x,data:image/png;base64,YTM= 2x">
      <img src="data:image/png;base64,YTQ=" srcset="data:image/png;base64,'''\
          '''YTU= 1x,data:image/png;base64,YTY= 2x">
      <img src="chrome://theme/img11.png" srcset="data:image/png;base64,'''\
          '''YTc= 1x,chrome://theme/img13.png 2x">
      <img srcset="data:image/png;base64,YTg= 300w,data:image/png;base64,'''\
          '''YTk= 11E-2w,data:image/png;base64,YTEw -1e2w">
      <img srcset="data:image/png;base64,YTEx">
      <img srcset="data:image/png;base64,YTEx,data:image/png;base64,YTI= 1x">
      <img srcset="data:image/png;base64,YTI= 1x,data:image/png;base64,YTEx">
      </html>
      '''

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(filename))

    # Test normal inlining.
    result = html_inline.DoInline(
        tmp_dir.GetPath('index.html'),
        None)
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    self.assertEqual(expected_inlined,
                         util.FixLineEnd(result.inlined_data, '\n'))
    tmp_dir.CleanUp()

  def testImgSrcsetIgnoresI18n(self):
    '''Tests that $i18n{...} strings are ignored when inlining.
    '''

    src_html = '''
      <html>
      <head></head>
      <body>
        <img srcset="$i18n{foo}">
      </body>
      </html>
      '''

    files = {
      'index.html': src_html,
    }

    expected_inlined = src_html

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(util.normpath(filename)))

    result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    self.assertEqual(expected_inlined,
                         util.FixLineEnd(result.inlined_data, '\n'))
    tmp_dir.CleanUp()

  def testSourceSrcset(self):
    '''Tests that source srcset="" attributes are converted.'''

    # Note that there is no space before "img10.png" and that
    # "img11.png" has no descriptor.
    files = {
      'index.html': '''
      <html>
      <source src="img1.png" srcset="img2.png 1x, img3.png 2x">
      <source src="img4.png" srcset=" img5.png   1x , img6.png 2x ">
      <source src="chrome://theme/img11.png" srcset="img7.png 1x, '''\
          '''chrome://theme/img13.png 2x">
      <source srcset="img8.png 300w, img9.png 11E-2w,img10.png -1e2w">
      <source srcset="img11.png">
      </html>
      ''',
      'img1.png': '''a1''',
      'img2.png': '''a2''',
      'img3.png': '''a3''',
      'img4.png': '''a4''',
      'img5.png': '''a5''',
      'img6.png': '''a6''',
      'img7.png': '''a7''',
      'img8.png': '''a8''',
      'img9.png': '''a9''',
      'img10.png': '''a10''',
      'img11.png': '''a11''',
    }

    expected_inlined = '''
      <html>
      <source src="data:image/png;base64,YTE=" srcset="data:image/png;'''\
          '''base64,YTI= 1x,data:image/png;base64,YTM= 2x">
      <source src="data:image/png;base64,YTQ=" srcset="data:image/png;'''\
          '''base64,YTU= 1x,data:image/png;base64,YTY= 2x">
      <source src="chrome://theme/img11.png" srcset="data:image/png;'''\
          '''base64,YTc= 1x,chrome://theme/img13.png 2x">
      <source srcset="data:image/png;base64,YTg= 300w,data:image/png;'''\
          '''base64,YTk= 11E-2w,data:image/png;base64,YTEw -1e2w">
      <source srcset="data:image/png;base64,YTEx">
      </html>
      '''

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in files:
      source_resources.add(tmp_dir.GetPath(filename))

    # Test normal inlining.
    result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None)
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)
    self.assertEqual(expected_inlined,
                         util.FixLineEnd(result.inlined_data, '\n'))
    tmp_dir.CleanUp()

  def testConditionalInclude(self):
    '''Tests that output and dependency generation includes only files not'''\
        ''' blocked by  <if> macros.'''

    files = {
      'index.html': '''
      <html>
      <if expr="True">
        <img src="img1.png" srcset="img2.png 1x, img3.png 2x">
      </if>
      <if expr="False">
        <img src="img4.png" srcset=" img5.png 1x, img6.png 2x ">
      </if>
      <if expr="True">
        <img src="chrome://theme/img11.png" srcset="img7.png 1x, '''\
            '''chrome://theme/img13.png 2x">
      </if>
      <img srcset="img8.png 300w, img9.png 11E-2w,img10.png -1e2w">
      </html>
      ''',
      'img1.png': '''a1''',
      'img2.png': '''a2''',
      'img3.png': '''a3''',
      'img4.png': '''a4''',
      'img5.png': '''a5''',
      'img6.png': '''a6''',
      'img7.png': '''a7''',
      'img8.png': '''a8''',
      'img9.png': '''a9''',
      'img10.png': '''a10''',
    }

    expected_inlined = '''
      <html>
      <img src="data:image/png;base64,YTE=" srcset="data:image/png;base64,'''\
          '''YTI= 1x,data:image/png;base64,YTM= 2x">
      <img src="chrome://theme/img11.png" srcset="data:image/png;base64,'''\
          '''YTc= 1x,chrome://theme/img13.png 2x">
      <img srcset="data:image/png;base64,YTg= 300w,data:image/png;base64,'''\
          '''YTk= 11E-2w,data:image/png;base64,YTEw -1e2w">
      </html>
      '''

    expected_files = [
      'index.html',
      'img1.png',
      'img2.png',
      'img3.png',
      'img7.png',
      'img8.png',
      'img9.png',
      'img10.png'
    ]

    source_resources = set()
    tmp_dir = util.TempDir(files)
    for filename in expected_files:
      source_resources.add(tmp_dir.GetPath(filename))

    # Test normal inlining.
    result = html_inline.DoInline(
        tmp_dir.GetPath('index.html'),
        FakeGrdNode())
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)

    # ignore whitespace
    expected_inlined = re.sub(r'\s+', ' ', expected_inlined)
    actually_inlined = re.sub(r'\s+', ' ',
                              util.FixLineEnd(result.inlined_data, '\n'))
    self.assertEqual(expected_inlined, actually_inlined);
    tmp_dir.CleanUp()

  def testPreprocessOnlyEvaluatesIncludeAndIf(self):
    '''Tests that preprocess_only=true evaluates <include> and <if> only.  '''

    files = {
      'index.html': '''
      <html>
        <head>
          <link rel="stylesheet" href="not_inlined.css">
          <script src="also_not_inlined.js">
        </head>
        <body>
          <include src="inline_this.html">
          <if expr="True">
            <p>'if' should be evaluated.</p>
          </if>
        </body>
      </html>
      ''',
      'not_inlined.css': ''' /* <link> should not be inlined. */ ''',
      'also_not_inlined.js': ''' // <script> should not be inlined. ''',
      'inline_this.html': ''' <p>'include' should be inlined.</p> '''
    }

    expected_inlined = '''
      <html>
        <head>
          <link rel="stylesheet" href="not_inlined.css">
          <script src="also_not_inlined.js">
        </head>
        <body>
          <p>'include' should be inlined.</p>
          <p>'if' should be evaluated.</p>
        </body>
      </html>
      '''

    source_resources = set()
    tmp_dir = util.TempDir(files)
    source_resources.add(tmp_dir.GetPath('index.html'))
    source_resources.add(tmp_dir.GetPath('inline_this.html'))

    result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None,
                                  preprocess_only=True)
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)

    # Ignore whitespace
    expected_inlined = re.sub(r'\s+', ' ', expected_inlined)
    actually_inlined = re.sub(r'\s+', ' ',
                              util.FixLineEnd(result.inlined_data, '\n'))
    self.assertEqual(expected_inlined, actually_inlined)

    tmp_dir.CleanUp()

  def testPreprocessOnlyAppliesRecursively(self):
    '''Tests that preprocess_only=true propagates to included files. '''

    files = {
      'index.html': '''
      <html>
        <include src="outer_include.html">
      </html>
      ''',
      'outer_include.html': '''
      <include src="inner_include.html">
      <link rel="stylesheet" href="not_inlined.css">
      ''',
      'inner_include.html': ''' <p>This should be inlined in index.html</p> ''',
      'not_inlined.css': ''' /* This should not be inlined. */ '''
    }

    expected_inlined = '''
      <html>
        <p>This should be inlined in index.html</p>
        <link rel="stylesheet" href="not_inlined.css">
      </html>
      '''

    source_resources = set()
    tmp_dir = util.TempDir(files)
    source_resources.add(tmp_dir.GetPath('index.html'))
    source_resources.add(tmp_dir.GetPath('outer_include.html'))
    source_resources.add(tmp_dir.GetPath('inner_include.html'))

    result = html_inline.DoInline(tmp_dir.GetPath('index.html'), None,
                                  preprocess_only=True)
    resources = result.inlined_files
    resources.add(tmp_dir.GetPath('index.html'))
    self.assertEqual(resources, source_resources)

    # Ignore whitespace
    expected_inlined = re.sub(r'\s+', ' ', expected_inlined)
    actually_inlined = re.sub(r'\s+', ' ',
                              util.FixLineEnd(result.inlined_data, '\n'))
    self.assertEqual(expected_inlined, actually_inlined)

    tmp_dir.CleanUp()

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