1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import os
6import unittest
7import StringIO
8
9from tvcm import fake_fs
10from tvcm import generate
11from tvcm import html_generation_controller
12from tvcm import html_module
13from tvcm import parse_html_deps
14from tvcm import project as project_module
15from tvcm import resource
16from tvcm import resource_loader as resource_loader
17
18
19class ResourceWithFakeContents(resource.Resource):
20  def __init__(self, toplevel_dir, absolute_path, fake_contents):
21    """A resource with explicitly provided contents.
22
23    If the resource does not exist, then pass fake_contents=None. This will
24    cause accessing the resource contents to raise an exception mimicking the
25    behavior of regular resources."""
26    super(ResourceWithFakeContents, self).__init__(toplevel_dir, absolute_path)
27    self._fake_contents = fake_contents
28
29  @property
30  def contents(self):
31    if self._fake_contents is None:
32      raise Exception('File not found')
33    return self._fake_contents
34
35
36class FakeLoader(object):
37  def __init__(self, source_paths, initial_filenames_and_contents=None):
38    self._source_paths = source_paths
39    self._file_contents = {}
40    if initial_filenames_and_contents:
41      for k, v in initial_filenames_and_contents.iteritems():
42        self._file_contents[k] = v
43
44  def FindResourceGivenAbsolutePath(self, absolute_path):
45    candidate_paths = []
46    for source_path in self._source_paths:
47      if absolute_path.startswith(source_path):
48        candidate_paths.append(source_path)
49    if len(candidate_paths) == 0:
50      return None
51
52    # Sort by length. Longest match wins.
53    candidate_paths.sort(lambda x, y: len(x) - len(y))
54    longest_candidate = candidate_paths[-1]
55
56    return ResourceWithFakeContents(
57        longest_candidate, absolute_path,
58        self._file_contents.get(absolute_path, None))
59
60  def FindResourceGivenRelativePath(self, relative_path):
61    absolute_path = None
62    for script_path in self._source_paths:
63      absolute_path = os.path.join(script_path, relative_path)
64      if absolute_path in self._file_contents:
65        return ResourceWithFakeContents(script_path, absolute_path,
66                                        self._file_contents[absolute_path])
67    return None
68
69
70class ParseTests(unittest.TestCase):
71  def testMissingDocType(self):
72    parse_results = parse_html_deps.HTMLModuleParserResults('')
73
74    file_contents = {}
75
76    def DoIt():
77      html_module.Parse(FakeLoader([os.path.normpath('/tmp')], file_contents),
78                        'a.b.start',
79                        '/tmp/a/b/',
80                        is_component=False,
81                        parser_results=parse_results)
82    self.assertRaises(Exception, DoIt)
83
84  def testValidExternalScriptReferenceToRawScript(self):
85    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
86      <script src="../foo.js">
87      """)
88
89    file_contents = {}
90    file_contents[os.path.normpath('/tmp/a/foo.js')] = """
91'i am just some raw script';
92"""
93
94    metadata = html_module.Parse(
95        FakeLoader([os.path.normpath('/tmp')], file_contents),
96        'a.b.start',
97        '/tmp/a/b/',
98        is_component=False,
99        parser_results=parse_results)
100    self.assertEquals([], metadata.dependent_module_names)
101    self.assertEquals(
102        ['a/foo.js'], metadata.dependent_raw_script_relative_paths)
103
104  def testExternalScriptReferenceToModuleOutsideScriptPath(self):
105    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
106      <script src="/foo.js">
107      """)
108
109    file_contents = {}
110    file_contents[os.path.normpath('/foo.js')] = ''
111
112    def DoIt():
113      html_module.Parse(FakeLoader([os.path.normpath('/tmp')], file_contents),
114                        'a.b.start',
115                        '/tmp/a/b/',
116                        is_component=False,
117                        parser_results=parse_results)
118    self.assertRaises(Exception, DoIt)
119
120  def testExternalScriptReferenceToFileThatDoesntExist(self):
121    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
122      <script src="/foo.js">
123      """)
124
125    file_contents = {}
126
127    def DoIt():
128      html_module.Parse(FakeLoader([os.path.normpath('/tmp')], file_contents),
129                        'a.b.start',
130                        '/tmp/a/b/',
131                        is_component=False,
132                        parser_results=parse_results)
133    self.assertRaises(Exception, DoIt)
134
135  def testInlineScriptWithoutStrictNote(self):
136    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
137      <script>
138console.log('Logging without strict mode is no fun.');
139      </script>
140      """)
141
142    file_contents = {}
143
144    def DoIt():
145      html_module.Parse(FakeLoader([os.path.normpath('/tmp')], file_contents),
146                        'a.b.start',
147                        '/tmp/a/b/',
148                        is_component=False,
149                        parser_results=parse_results)
150    self.assertRaises(Exception, DoIt)
151
152  def testValidImportOfModule(self):
153    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
154      <link rel="import" href="../foo.html">
155      """)
156
157    file_contents = {}
158    file_contents[os.path.normpath('/tmp/a/foo.html')] = """
159"""
160
161    metadata = html_module.Parse(
162        FakeLoader([os.path.normpath('/tmp')], file_contents),
163        'a.b.start',
164        '/tmp/a/b/',
165        is_component=False,
166        parser_results=parse_results)
167    self.assertEquals(['a.foo'], metadata.dependent_module_names)
168
169  def testStyleSheetImport(self):
170    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
171      <link rel="stylesheet" href="../foo.css">
172      """)
173
174    file_contents = {}
175    file_contents[os.path.normpath('/tmp/a/foo.css')] = """
176"""
177    metadata = html_module.Parse(
178        FakeLoader([os.path.normpath('/tmp')], file_contents),
179        'a.b.start',
180        '/tmp/a/b/',
181        is_component=False,
182        parser_results=parse_results)
183    self.assertEquals([], metadata.dependent_module_names)
184    self.assertEquals(['a.foo'], metadata.style_sheet_names)
185
186  def testUsingAbsoluteHref(self):
187    parse_results = parse_html_deps.HTMLModuleParserResults("""<!DOCTYPE html>
188      <script src="/foo.js">
189      """)
190
191    file_contents = {}
192    file_contents[os.path.normpath('/src/foo.js')] = ''
193
194    metadata = html_module.Parse(
195        FakeLoader([os.path.normpath("/tmp"), os.path.normpath("/src")],
196                   file_contents),
197        "a.b.start",
198        "/tmp/a/b/",
199        is_component=False,
200        parser_results=parse_results)
201    self.assertEquals(['foo.js'], metadata.dependent_raw_script_relative_paths)
202
203
204class HTMLModuleTests(unittest.TestCase):
205  def testBasic(self):
206    file_contents = {}
207    file_contents[os.path.normpath('/tmp/a/b/start.html')] = """
208<!DOCTYPE html>
209<link rel="import" href="/widget.html">
210<link rel="stylesheet" href="../common.css">
211<script src="/raw_script.js"></script>
212<polymer-element name="start">
213  <template>
214  </template>
215  <script>
216    'use strict';
217    console.log('inline script for start.html got written');
218  </script>
219</polymer-element>
220"""
221    file_contents[os.path.normpath('/tvcm/tvcm.html')] = """<!DOCTYPE html>
222"""
223    file_contents[os.path.normpath('/components/widget.html')] = """
224<!DOCTYPE html>
225<link rel="import" href="/tvcm.html">
226<widget name="widget.html"></widget>
227<script>
228'use strict';
229console.log('inline script for widget.html');
230</script>
231"""
232    file_contents[os.path.normpath('/tmp/a/common.css')] = """
233/* /tmp/a/common.css was written */
234"""
235    file_contents[os.path.normpath('/raw/raw_script.js')] = """
236console.log('/raw/raw_script.js was written');
237"""
238    file_contents[os.path.normpath(
239        '/raw/components/polymer/polymer.min.js')] = """
240"""
241
242    with fake_fs.FakeFS(file_contents):
243      project = project_module.Project(
244          [os.path.normpath('/tvcm/'),
245           os.path.normpath('/tmp/'),
246           os.path.normpath('/components/'),
247           os.path.normpath('/raw/')])
248      loader = resource_loader.ResourceLoader(project)
249      a_b_start_module = loader.LoadModule(module_name='a.b.start')
250      load_sequence = project.CalcLoadSequenceForModules([a_b_start_module])
251
252      # Check load sequence names.
253      load_sequence_names = [x.name for x in load_sequence]
254      self.assertEquals(['tvcm',
255                         'widget',
256                         'a.b.start'], load_sequence_names)
257
258      # Check module_deps on a_b_start_module
259      def HasDependentModule(module, name):
260        return [x for x in module.dependent_modules
261                if x.name == name]
262      assert HasDependentModule(a_b_start_module, 'widget')
263
264      # Check JS generation.
265      js = generate.GenerateJS(load_sequence)
266      assert 'inline script for start.html' in js
267      assert 'inline script for widget.html' in js
268      assert '/raw/raw_script.js' in js
269
270      # Check HTML generation.
271      html = generate.GenerateStandaloneHTMLAsString(
272          load_sequence, title='', flattened_js_url='/blah.js')
273      assert '<polymer-element name="start">' in html
274      assert 'inline script for widget.html' not in html
275      assert 'common.css' in html
276
277  def testPolymerConversion(self):
278    file_contents = {}
279    file_contents[os.path.normpath('/tmp/a/b/my_component.html')] = """
280<!DOCTYPE html>
281<polymer-element name="my-component">
282  <template>
283  </template>
284  <script>
285    'use strict';
286    Polymer ( {
287    });
288  </script>
289</polymer-element>
290"""
291    with fake_fs.FakeFS(file_contents):
292      project = project_module.Project([
293          os.path.normpath('/tvcm/'), os.path.normpath('/tmp/')])
294      loader = resource_loader.ResourceLoader(project)
295      my_component = loader.LoadModule(module_name='a.b.my_component')
296
297      f = StringIO.StringIO()
298      my_component.AppendJSContentsToFile(
299          f,
300          use_include_tags_for_scripts=False,
301          dir_for_include_tag_root=None)
302      js = f.getvalue().rstrip()
303      expected_js = """
304    'use strict';
305    Polymer ( 'my-component', {
306    });
307""".rstrip()
308      self.assertEquals(expected_js, js)
309
310  def testPolymerConversion2(self):
311    file_contents = {}
312    file_contents[os.path.normpath('/tmp/a/b/my_component.html')] = """
313<!DOCTYPE html>
314<polymer-element name="my-component">
315  <template>
316  </template>
317  <script>
318    'use strict';
319    Polymer ( );
320  </script>
321</polymer-element>
322"""
323    with fake_fs.FakeFS(file_contents):
324      project = project_module.Project([
325          os.path.normpath('/tvcm/'), os.path.normpath('/tmp/')])
326      loader = resource_loader.ResourceLoader(project)
327      my_component = loader.LoadModule(module_name='a.b.my_component')
328
329      f = StringIO.StringIO()
330      my_component.AppendJSContentsToFile(
331          f,
332          use_include_tags_for_scripts=False,
333          dir_for_include_tag_root=None)
334      js = f.getvalue().rstrip()
335      expected_js = """
336    'use strict';
337    Polymer ( 'my-component');
338""".rstrip()
339      self.assertEquals(expected_js, js)
340
341  def testInlineStylesheetURLs(self):
342    file_contents = {}
343    file_contents[os.path.normpath('/tmp/a/b/my_component.html')] = """
344<!DOCTYPE html>
345<style>
346.some-rule {
347    background-image: url('../something.jpg');
348}
349</style>
350"""
351    file_contents[os.path.normpath('/tmp/a/something.jpg')] = 'jpgdata'
352    with fake_fs.FakeFS(file_contents):
353      project = project_module.Project([
354          os.path.normpath('/tvcm/'), os.path.normpath('/tmp/')])
355      loader = resource_loader.ResourceLoader(project)
356      my_component = loader.LoadModule(module_name='a.b.my_component')
357
358      computed_deps = []
359      my_component.AppendDirectlyDependentFilenamesTo(computed_deps)
360      self.assertEquals(set(computed_deps),
361                        set([os.path.normpath('/tmp/a/b/my_component.html'),
362                             os.path.normpath('/tmp/a/something.jpg')]))
363
364      f = StringIO.StringIO()
365      ctl = html_generation_controller.HTMLGenerationController()
366      my_component.AppendHTMLContentsToFile(f, ctl)
367      html = f.getvalue().rstrip()
368      # FIXME: This is apparently not used.
369      expected_html = """
370.some-rule {
371    background-image: url(data:image/jpg;base64,anBnZGF0YQ==);
372}
373""".rstrip()
374      print html
375