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