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.
 
 
 

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