A fork of hyde, the static site generation. Some patches will be pushed upstream.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

786 lines
19 KiB

  1. # -*- coding: utf-8 -*-
  2. """
  3. Use nose
  4. `$ pip install nose`
  5. `$ nosetests`
  6. Some code borrowed from rwbench.py from the jinja2 examples
  7. """
  8. from datetime import datetime
  9. from random import choice, randrange
  10. from hyde.ext.templates.jinja import Jinja2Template
  11. from hyde.site import Site
  12. from hyde.generator import Generator
  13. from hyde.model import Config
  14. from fswrap import File
  15. from jinja2.utils import generate_lorem_ipsum
  16. from nose.tools import nottest
  17. from pyquery import PyQuery
  18. import yaml
  19. ROOT = File(__file__).parent
  20. JINJA2 = ROOT.child_folder('templates/jinja2')
  21. class Article(object):
  22. def __init__(self, id):
  23. self.id = id
  24. self.href = '/article/%d' % self.id
  25. self.title = generate_lorem_ipsum(1, False, 5, 10)
  26. self.user = choice(users)
  27. self.body = generate_lorem_ipsum()
  28. self.pub_date = datetime.utcfromtimestamp(
  29. randrange(10 ** 9, 2 * 10 ** 9))
  30. self.published = True
  31. def dateformat(x):
  32. return x.strftime('%Y-%m-%d')
  33. class User(object):
  34. def __init__(self, username):
  35. self.href = '/user/%s' % username
  36. self.username = username
  37. users = map(User, [u'John Doe', u'Jane Doe', u'Peter Somewhat'])
  38. articles = map(Article, range(20))
  39. navigation = [
  40. ('index', 'Index'),
  41. ('about', 'About'),
  42. ('foo?bar=1', 'Foo with Bar'),
  43. ('foo?bar=2&s=x', 'Foo with X'),
  44. ('blah', 'Blub Blah'),
  45. ('hehe', 'Haha'),
  46. ] * 5
  47. context = dict(users=users, articles=articles, page_navigation=navigation)
  48. def test_render():
  49. """
  50. Uses pyquery to test the html structure for validity
  51. """
  52. t = Jinja2Template(JINJA2.path)
  53. t.configure(None)
  54. t.env.filters['dateformat'] = dateformat
  55. source = File(JINJA2.child('index.html')).read_all()
  56. html = t.render(source, context)
  57. actual = PyQuery(html)
  58. assert actual(".navigation li").length == 30
  59. assert actual("div.article").length == 20
  60. assert actual("div.article h2").length == 20
  61. assert actual("div.article h2 a").length == 20
  62. assert actual("div.article p.meta").length == 20
  63. assert actual("div.article div.text").length == 20
  64. def test_typogrify():
  65. source = """
  66. {%filter typogrify%}
  67. One & two
  68. {%endfilter%}
  69. """
  70. t = Jinja2Template(JINJA2.path)
  71. t.configure(None)
  72. t.env.filters['dateformat'] = dateformat
  73. html = t.render(source, {}).strip()
  74. assert html == u'One <span class="amp">&amp;</span>&nbsp;two'
  75. def test_spaceless():
  76. source = """
  77. {%spaceless%}
  78. <html>
  79. <body>
  80. <ul>
  81. <li>
  82. One
  83. </li>
  84. <li>
  85. Two
  86. </li>
  87. <li>
  88. Three
  89. </li>
  90. </ul>
  91. </body>
  92. </html>
  93. {%endspaceless%}
  94. """
  95. t = Jinja2Template(JINJA2.path)
  96. t.configure(None)
  97. t.env.filters['dateformat'] = dateformat
  98. html = t.render(source, {}).strip()
  99. expected = u"""
  100. <html><body><ul><li>
  101. One
  102. </li><li>
  103. Two
  104. </li><li>
  105. Three
  106. </li></ul></body></html>
  107. """
  108. assert html.strip() == expected.strip()
  109. def test_asciidoc():
  110. source = """
  111. {%asciidoc%}
  112. == Heading 2 ==
  113. * test1
  114. * test2
  115. * test3
  116. {%endasciidoc%}
  117. """
  118. t = Jinja2Template(JINJA2.path)
  119. t.configure(None)
  120. html = t.render(source, {}).strip()
  121. assert html
  122. q = PyQuery(html)
  123. assert q
  124. assert q("li").length == 3
  125. assert q("li:nth-child(1)").text().strip() == "test1"
  126. assert q("li:nth-child(2)").text().strip() == "test2"
  127. assert q("li:nth-child(3)").text().strip() == "test3"
  128. def test_markdown():
  129. source = """
  130. {%markdown%}
  131. ### Heading 3
  132. {%endmarkdown%}
  133. """
  134. t = Jinja2Template(JINJA2.path)
  135. t.configure(None)
  136. html = t.render(source, {}).strip()
  137. assert html == u'<h3>Heading 3</h3>'
  138. def test_restructuredtext():
  139. source = """
  140. {% restructuredtext %}
  141. Hello
  142. =====
  143. {% endrestructuredtext %}
  144. """
  145. t = Jinja2Template(JINJA2.path)
  146. t.configure(None)
  147. html = t.render(source, {}).strip()
  148. assert html == u"""<div class="document" id="hello">
  149. <h1 class="title">Hello</h1>
  150. </div>""", html
  151. def test_restructuredtext_with_sourcecode():
  152. source = """
  153. {% restructuredtext %}
  154. Code
  155. ====
  156. .. sourcecode:: python
  157. def add(a, b):
  158. return a + b
  159. See `Example`_
  160. .. _Example: example.html
  161. {% endrestructuredtext %}
  162. """
  163. expected = ("""
  164. <div class="document" id="code">
  165. <h1 class="title">Code</h1>
  166. <div class="highlight"><pre><span class="k">def</span> """
  167. """<span class="nf">add</span><span class="p">(</span>"""
  168. """<span class="n">a"""
  169. """</span><span class="p">,</span> <span class="n">b</span>"""
  170. """<span class="p">):</span>
  171. <span class="k">return</span> <span class="n">a</span> """
  172. """<span class="o">+</span> <span class="n">b</span>
  173. </pre></div>
  174. <p>See <a class="reference external" href="example.html">Example</a></p>
  175. </div>
  176. """)
  177. t = Jinja2Template(JINJA2.path)
  178. s = Site(JINJA2.path)
  179. c = Config(JINJA2.path, config_dict=dict(
  180. restructuredtext=dict(highlight_source=True)))
  181. s.config = c
  182. t.configure(s)
  183. html = t.render(source, {}).strip()
  184. assert html.strip() == expected.strip()
  185. def test_markdown_with_extensions():
  186. source = """
  187. {%markdown%}
  188. ### Heading 3
  189. {%endmarkdown%}
  190. """
  191. t = Jinja2Template(JINJA2.path)
  192. s = Site(JINJA2.path)
  193. c = Config(JINJA2.path, config_dict=dict(
  194. markdown=dict(extensions=['headerid'])))
  195. s.config = c
  196. t.configure(s)
  197. t.env.filters['dateformat'] = dateformat
  198. html = t.render(source, {}).strip()
  199. assert html == u'<h3 id="heading-3">Heading 3</h3>'
  200. def test_markdown_with_sourcecode():
  201. source = """
  202. {%markdown%}
  203. # Code
  204. :::python
  205. def add(a, b):
  206. return a + b
  207. See [Example][]
  208. [Example]: example.html
  209. {%endmarkdown%}
  210. """
  211. expected = ("""
  212. <h1>Code</h1>
  213. <div class="codehilite"><pre><span class="k">def</span> """
  214. """<span class="nf">add</span><span class="p">(</span>"""
  215. """<span class="n">a</span><span class="p">,</span> """
  216. """<span class="n">b</span><span class="p">):</span>
  217. <span class="k">return</span> <span class="n">a</span> <span class="o">+"""
  218. """</span> <span class="n">b</span>
  219. </pre></div>
  220. <p>See <a href="example.html">Example</a></p>
  221. """)
  222. t = Jinja2Template(JINJA2.path)
  223. s = Site(JINJA2.path)
  224. c = Config(JINJA2.path, config_dict=dict(
  225. markdown=dict(extensions=['codehilite'])))
  226. s.config = c
  227. t.configure(s)
  228. html = t.render(source, {}).strip()
  229. assert html.strip() == expected.strip()
  230. def test_line_statements():
  231. source = """
  232. $$$ markdown
  233. ### Heading 3
  234. $$$ endmarkdown
  235. """
  236. t = Jinja2Template(JINJA2.path)
  237. s = Site(JINJA2.path)
  238. c = Config(JINJA2.path, config_dict=dict(
  239. markdown=dict(extensions=['headerid'])))
  240. s.config = c
  241. t.configure(s)
  242. t.env.filters['dateformat'] = dateformat
  243. html = t.render(source, {}).strip()
  244. assert html == u'<h3 id="heading-3">Heading 3</h3>'
  245. def test_line_statements_with_config():
  246. source = """
  247. %% markdown
  248. ### Heading 3
  249. %% endmarkdown
  250. """
  251. config = """
  252. markdown:
  253. extensions:
  254. - headerid
  255. jinja2:
  256. line_statement_prefix: '%%'
  257. """
  258. t = Jinja2Template(JINJA2.path)
  259. s = Site(JINJA2.path)
  260. s.config = Config(JINJA2.path, config_dict=yaml.load(config))
  261. t.configure(s)
  262. t.env.filters['dateformat'] = dateformat
  263. html = t.render(source, {}).strip()
  264. assert html == u'<h3 id="heading-3">Heading 3</h3>'
  265. TEST_SITE = File(__file__).parent.child_folder('_test')
  266. @nottest
  267. def assert_markdown_typogrify_processed_well(include_text, includer_text):
  268. site = Site(TEST_SITE)
  269. site.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin']
  270. inc = File(TEST_SITE.child('content/inc.md'))
  271. inc.write(include_text)
  272. site.load()
  273. gen = Generator(site)
  274. gen.load_template_if_needed()
  275. template = gen.template
  276. html = template.render(includer_text, {}).strip()
  277. assert html
  278. q = PyQuery(html)
  279. assert "is_processable" not in html
  280. assert "This is a" in q("h1").text()
  281. assert "heading" in q("h1").text()
  282. assert q(".amp").length == 1
  283. return html
  284. class TestJinjaTemplate(object):
  285. def setUp(self):
  286. TEST_SITE.make()
  287. TEST_SITE.parent.child_folder(
  288. 'sites/test_jinja').copy_contents_to(TEST_SITE)
  289. def tearDown(self):
  290. TEST_SITE.delete()
  291. def test_depends(self):
  292. t = Jinja2Template(JINJA2.path)
  293. t.configure(None)
  294. t.env.filters['dateformat'] = dateformat
  295. deps = list(t.get_dependencies('index.html'))
  296. assert len(deps) == 2
  297. assert 'helpers.html' in deps
  298. assert 'layout.html' in deps
  299. def test_depends_multi_level(self):
  300. site = Site(TEST_SITE)
  301. JINJA2.copy_contents_to(site.content.source)
  302. inc = File(TEST_SITE.child('content/inc.md'))
  303. inc.write("{% extends 'index.html' %}")
  304. site.load()
  305. gen = Generator(site)
  306. gen.load_template_if_needed()
  307. t = gen.template
  308. deps = list(t.get_dependencies('inc.md'))
  309. assert len(deps) == 3
  310. assert 'helpers.html' in deps
  311. assert 'layout.html' in deps
  312. assert 'index.html' in deps
  313. def test_line_statements_with_blocks(self):
  314. site = Site(TEST_SITE)
  315. JINJA2.copy_contents_to(site.content.source)
  316. text = """
  317. {% extends 'index.html' %}
  318. $$$ block body
  319. <div id="article">Heya</div>
  320. $$$ endblock
  321. """
  322. site.load()
  323. gen = Generator(site)
  324. gen.load_template_if_needed()
  325. template = gen.template
  326. template.env.filters['dateformat'] = dateformat
  327. html = template.render(text, {}).strip()
  328. assert html
  329. q = PyQuery(html)
  330. article = q("#article")
  331. assert article.length == 1
  332. assert article.text() == "Heya"
  333. def test_depends_with_reference_tag(self):
  334. site = Site(TEST_SITE)
  335. JINJA2.copy_contents_to(site.content.source)
  336. inc = File(TEST_SITE.child('content/inc.md'))
  337. inc.write("{% refer to 'index.html' as index%}")
  338. site.load()
  339. gen = Generator(site)
  340. gen.load_template_if_needed()
  341. t = gen.template
  342. deps = list(t.get_dependencies('inc.md'))
  343. assert len(deps) == 3
  344. assert 'helpers.html' in deps
  345. assert 'layout.html' in deps
  346. assert 'index.html' in deps
  347. def test_cant_find_depends_with_reference_tag_var(self):
  348. site = Site(TEST_SITE)
  349. JINJA2.copy_contents_to(site.content.source)
  350. inc = File(TEST_SITE.child('content/inc.md'))
  351. inc.write("{% set ind = 'index.html' %}{% refer to ind as index %}")
  352. site.load()
  353. gen = Generator(site)
  354. gen.load_template_if_needed()
  355. t = gen.template
  356. deps = list(t.get_dependencies('inc.md'))
  357. assert len(deps) == 1
  358. assert not deps[0]
  359. def test_can_include_templates_with_processing(self):
  360. text = """
  361. ===
  362. is_processable: False
  363. ===
  364. {% filter typogrify %}{% markdown %}
  365. This is a heading
  366. =================
  367. Hyde & Jinja.
  368. {% endmarkdown %}{% endfilter %}
  369. """
  370. text2 = """{% include "inc.md" %}"""
  371. assert_markdown_typogrify_processed_well(text, text2)
  372. def test_includetext(self):
  373. text = """
  374. ===
  375. is_processable: False
  376. ===
  377. This is a heading
  378. =================
  379. Hyde & Jinja.
  380. """
  381. text2 = """{% includetext "inc.md" %}"""
  382. assert_markdown_typogrify_processed_well(text, text2)
  383. def test_reference_is_noop(self):
  384. text = """
  385. ===
  386. is_processable: False
  387. ===
  388. {% mark heading %}
  389. This is a heading
  390. =================
  391. {% endmark %}
  392. {% reference content %}
  393. Hyde & Jinja.
  394. {% endreference %}
  395. """
  396. text2 = """{% includetext "inc.md" %}"""
  397. html = assert_markdown_typogrify_processed_well(text, text2)
  398. assert "mark" not in html
  399. assert "reference" not in html
  400. def test_reference_is_not_callable(self):
  401. text = """
  402. ===
  403. is_processable: False
  404. ===
  405. {% mark heading %}
  406. This is a heading
  407. =================
  408. {% endmark %}
  409. {% reference content %}
  410. Hyde & Jinja.
  411. {% endreference %}
  412. {% mark repeated %}
  413. <span class="junk">Junk</span>
  414. {% endmark %}
  415. {{ self.repeated() }}
  416. {{ self.repeated }}
  417. """
  418. text2 = """{% includetext "inc.md" %}"""
  419. html = assert_markdown_typogrify_processed_well(text, text2)
  420. assert "mark" not in html
  421. assert "reference" not in html
  422. q = PyQuery(html)
  423. assert q("span.junk").length == 1
  424. def test_refer(self):
  425. text = """
  426. ===
  427. is_processable: False
  428. ===
  429. {% filter markdown|typogrify %}
  430. {% mark heading %}
  431. This is a heading
  432. =================
  433. {% endmark %}
  434. {% reference content %}
  435. Hyde & Jinja.
  436. {% endreference %}
  437. {% endfilter %}
  438. """
  439. text2 = """
  440. {% refer to "inc.md" as inc %}
  441. {% filter markdown|typogrify %}
  442. {{ inc.heading }}
  443. {{ inc.content }}
  444. {% endfilter %}
  445. """
  446. html = assert_markdown_typogrify_processed_well(text, text2)
  447. assert "mark" not in html
  448. assert "reference" not in html
  449. def test_refer_with_full_html(self):
  450. text = """
  451. ===
  452. is_processable: False
  453. ===
  454. <div class="fulltext">
  455. {% filter markdown|typogrify %}
  456. {% mark heading %}
  457. This is a heading
  458. =================
  459. {% endmark %}
  460. {% reference content %}
  461. Hyde & Jinja.
  462. {% endreference %}
  463. {% endfilter %}
  464. </div>
  465. """
  466. text2 = """
  467. {% refer to "inc.md" as inc %}
  468. {{ inc.html('.fulltext') }}
  469. """
  470. html = assert_markdown_typogrify_processed_well(text, text2)
  471. assert "mark" not in html
  472. assert "reference" not in html
  473. def test_two_level_refer_with_var(self):
  474. text = """
  475. ===
  476. is_processable: False
  477. ===
  478. <div class="fulltext">
  479. {% filter markdown|typogrify %}
  480. {% mark heading %}
  481. This is a heading
  482. =================
  483. {% endmark %}
  484. {% reference content %}
  485. Hyde & Jinja.
  486. {% endreference %}
  487. {% endfilter %}
  488. </div>
  489. """
  490. text2 = """
  491. {% set super = 'super.md' %}
  492. {% refer to super as sup %}
  493. <div class="justhead">
  494. {% mark child %}
  495. {{ sup.heading }}
  496. {% endmark %}
  497. {% mark cont %}
  498. {{ sup.content }}
  499. {% endmark %}
  500. </div>
  501. """
  502. text3 = """
  503. {% set incu = 'inc.md' %}
  504. {% refer to incu as inc %}
  505. {% filter markdown|typogrify %}
  506. {{ inc.child }}
  507. {{ inc.cont }}
  508. {% endfilter %}
  509. """
  510. superinc = File(TEST_SITE.child('content/super.md'))
  511. superinc.write(text)
  512. html = assert_markdown_typogrify_processed_well(text2, text3)
  513. assert "mark" not in html
  514. assert "reference" not in html
  515. def test_refer_with_var(self):
  516. text = """
  517. ===
  518. is_processable: False
  519. ===
  520. <div class="fulltext">
  521. {% filter markdown|typogrify %}
  522. {% mark heading %}
  523. This is a heading
  524. =================
  525. {% endmark %}
  526. {% reference content %}
  527. Hyde & Jinja.
  528. {% endreference %}
  529. {% endfilter %}
  530. </div>
  531. """
  532. text2 = """
  533. {% set incu = 'inc.md' %}
  534. {% refer to incu as inc %}
  535. {{ inc.html('.fulltext') }}
  536. """
  537. html = assert_markdown_typogrify_processed_well(text, text2)
  538. assert "mark" not in html
  539. assert "reference" not in html
  540. def test_yaml_tag(self):
  541. text = """
  542. {% yaml test %}
  543. one:
  544. - A
  545. - B
  546. - C
  547. two:
  548. - D
  549. - E
  550. - F
  551. {% endyaml %}
  552. {% for section, values in test.items() %}
  553. <ul class="{{ section }}">
  554. {% for value in values %}
  555. <li>{{ value }}</li>
  556. {% endfor %}
  557. </ul>
  558. {% endfor %}
  559. """
  560. t = Jinja2Template(JINJA2.path)
  561. t.configure(None)
  562. t.env.filters['dateformat'] = dateformat
  563. html = t.render(text, {}).strip()
  564. actual = PyQuery(html)
  565. assert actual("ul").length == 2
  566. assert actual("ul.one").length == 1
  567. assert actual("ul.two").length == 1
  568. assert actual("li").length == 6
  569. assert actual("ul.one li").length == 3
  570. assert actual("ul.two li").length == 3
  571. ones = [item.text for item in actual("ul.one li")]
  572. assert ones == ["A", "B", "C"]
  573. twos = [item.text for item in actual("ul.two li")]
  574. assert twos == ["D", "E", "F"]
  575. def test_top_filter(self):
  576. text = """
  577. {% yaml test %}
  578. item_list:
  579. - A
  580. - B
  581. - C
  582. - D
  583. - E
  584. - F
  585. {% endyaml %}
  586. <ul class="top">
  587. {% for value in test.item_list|top(3) %}
  588. <li>{{ value }}</li>
  589. {% endfor %}
  590. </ul>
  591. <ul class="mid">
  592. {% for value in test.item_list|islice(3, 6) %}
  593. <li>{{ value }}</li>
  594. {% endfor %}
  595. </ul>
  596. """
  597. t = Jinja2Template(JINJA2.path)
  598. t.configure(None)
  599. t.env.filters['dateformat'] = dateformat
  600. html = t.render(text, {}).strip()
  601. actual = PyQuery(html)
  602. assert actual("ul").length == 2
  603. assert actual("li").length == 6
  604. items = [item.text for item in actual("ul.top li")]
  605. assert items == ["A", "B", "C"]
  606. items = [item.text for item in actual("ul.mid li")]
  607. assert items == ["D", "E", "F"]
  608. def test_raw_tag(self):
  609. expected = """
  610. <div class="template secondary">
  611. <aside class="secondary">
  612. <ul>
  613. {{#sections}}
  614. <li><a href="#{{id}}">{{textContent}}</a></li>
  615. {{/sections}}
  616. </ul>
  617. </aside>
  618. """
  619. text = "{%% raw %%}%s{%% endraw %%}" % expected
  620. t = Jinja2Template(JINJA2.path)
  621. t.configure(None)
  622. html = t.render(text, {}).strip()
  623. assert html.strip() == expected.strip()
  624. def test_raw_tag_with_markdown_typogrify(self):
  625. expected = """
  626. <div class="template secondary">
  627. <aside class="secondary">
  628. <ul>
  629. {{#sections}}
  630. <li><a href="#{{id}}">{{textContent}}</a></li>
  631. {{/sections}}
  632. </ul>
  633. </aside>
  634. """
  635. text = """{%% filter markdown|typogrify %%}{%% raw
  636. %%}%s{%% endraw %%}{%% endfilter %%}""" % expected
  637. t = Jinja2Template(JINJA2.path)
  638. t.configure(None)
  639. html = t.render(text, {}).strip()
  640. assert html.strip() == expected.strip()
  641. def test_urlencode_filter(self):
  642. text = u"""
  643. <a href="{{ 'фотография.jpg'|urlencode }}"
  644. >фотография</a><a href="{{ 'http://localhost:8080/"abc.jpg'|urlencode
  645. }}">quoted</a>
  646. """
  647. expected = u"""
  648. <a href="%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%8F.jpg"
  649. >фотография</a><a href="http%3A//localhost%3A8080/%22abc.jpg">quoted</a>
  650. """
  651. t = Jinja2Template(JINJA2.path)
  652. t.configure(None)
  653. html = t.render(text, {}).strip()
  654. assert html.strip() == expected.strip()
  655. def test_urldecode_filter(self):
  656. text = u"""
  657. <a href="{{ 'фотография.jpg'|urlencode }}">{{
  658. "%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%8F.jpg"|urldecode
  659. }}</a>
  660. """
  661. expected = (u'<a href="%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1'
  662. u'%80%D0%B0%D1%84%D0%B8%D1%8F.jpg">фотография.jpg</a>')
  663. t = Jinja2Template(JINJA2.path)
  664. t.configure(None)
  665. html = t.render(text, {}).strip()
  666. assert html.strip() == expected.strip()