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