page_tests.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852
  1. import unittest
  2. import os
  3. import sys
  4. from unittest import mock
  5. from tempfile import TemporaryDirectory
  6. from mkdocs.structure.pages import Page
  7. from mkdocs.structure.files import File, Files
  8. from mkdocs.tests.base import load_config, dedent
  9. class PageTests(unittest.TestCase):
  10. DOCS_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../integration/subpages/docs')
  11. def test_homepage(self):
  12. cfg = load_config(docs_dir=self.DOCS_DIR)
  13. fl = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  14. self.assertIsNone(fl.page)
  15. pg = Page('Foo', fl, cfg)
  16. self.assertEqual(fl.page, pg)
  17. self.assertEqual(pg.url, '')
  18. self.assertEqual(pg.abs_url, None)
  19. self.assertEqual(pg.canonical_url, None)
  20. self.assertEqual(pg.edit_url, None)
  21. self.assertEqual(pg.file, fl)
  22. self.assertEqual(pg.content, None)
  23. self.assertTrue(pg.is_homepage)
  24. self.assertTrue(pg.is_index)
  25. self.assertTrue(pg.is_page)
  26. self.assertFalse(pg.is_section)
  27. self.assertTrue(pg.is_top_level)
  28. self.assertEqual(pg.markdown, None)
  29. self.assertEqual(pg.meta, {})
  30. self.assertEqual(pg.next_page, None)
  31. self.assertEqual(pg.parent, None)
  32. self.assertEqual(pg.previous_page, None)
  33. self.assertEqual(pg.title, 'Foo')
  34. self.assertEqual(pg.toc, [])
  35. def test_nested_index_page(self):
  36. cfg = load_config(docs_dir=self.DOCS_DIR)
  37. fl = File('sub1/index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  38. pg = Page('Foo', fl, cfg)
  39. pg.parent = 'foo'
  40. self.assertEqual(pg.url, 'sub1/')
  41. self.assertEqual(pg.abs_url, None)
  42. self.assertEqual(pg.canonical_url, None)
  43. self.assertEqual(pg.edit_url, None)
  44. self.assertEqual(pg.file, fl)
  45. self.assertEqual(pg.content, None)
  46. self.assertFalse(pg.is_homepage)
  47. self.assertTrue(pg.is_index)
  48. self.assertTrue(pg.is_page)
  49. self.assertFalse(pg.is_section)
  50. self.assertFalse(pg.is_top_level)
  51. self.assertEqual(pg.markdown, None)
  52. self.assertEqual(pg.meta, {})
  53. self.assertEqual(pg.next_page, None)
  54. self.assertEqual(pg.parent, 'foo')
  55. self.assertEqual(pg.previous_page, None)
  56. self.assertEqual(pg.title, 'Foo')
  57. self.assertEqual(pg.toc, [])
  58. def test_nested_index_page_no_parent(self):
  59. cfg = load_config(docs_dir=self.DOCS_DIR)
  60. fl = File('sub1/index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  61. pg = Page('Foo', fl, cfg)
  62. pg.parent = None # non-homepage at nav root level; see #1919.
  63. self.assertEqual(pg.url, 'sub1/')
  64. self.assertEqual(pg.abs_url, None)
  65. self.assertEqual(pg.canonical_url, None)
  66. self.assertEqual(pg.edit_url, None)
  67. self.assertEqual(pg.file, fl)
  68. self.assertEqual(pg.content, None)
  69. self.assertFalse(pg.is_homepage)
  70. self.assertTrue(pg.is_index)
  71. self.assertTrue(pg.is_page)
  72. self.assertFalse(pg.is_section)
  73. self.assertTrue(pg.is_top_level)
  74. self.assertEqual(pg.markdown, None)
  75. self.assertEqual(pg.meta, {})
  76. self.assertEqual(pg.next_page, None)
  77. self.assertEqual(pg.parent, None)
  78. self.assertEqual(pg.previous_page, None)
  79. self.assertEqual(pg.title, 'Foo')
  80. self.assertEqual(pg.toc, [])
  81. def test_nested_index_page_no_parent_no_directory_urls(self):
  82. cfg = load_config(docs_dir=self.DOCS_DIR, use_directory_urls=False)
  83. fl = File('sub1/index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  84. pg = Page('Foo', fl, cfg)
  85. pg.parent = None # non-homepage at nav root level; see #1919.
  86. self.assertEqual(pg.url, 'sub1/index.html')
  87. self.assertEqual(pg.abs_url, None)
  88. self.assertEqual(pg.canonical_url, None)
  89. self.assertEqual(pg.edit_url, None)
  90. self.assertEqual(pg.file, fl)
  91. self.assertEqual(pg.content, None)
  92. self.assertFalse(pg.is_homepage)
  93. self.assertTrue(pg.is_index)
  94. self.assertTrue(pg.is_page)
  95. self.assertFalse(pg.is_section)
  96. self.assertTrue(pg.is_top_level)
  97. self.assertEqual(pg.markdown, None)
  98. self.assertEqual(pg.meta, {})
  99. self.assertEqual(pg.next_page, None)
  100. self.assertEqual(pg.parent, None)
  101. self.assertEqual(pg.previous_page, None)
  102. self.assertEqual(pg.title, 'Foo')
  103. self.assertEqual(pg.toc, [])
  104. def test_nested_nonindex_page(self):
  105. cfg = load_config(docs_dir=self.DOCS_DIR)
  106. fl = File('sub1/non-index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  107. pg = Page('Foo', fl, cfg)
  108. pg.parent = 'foo'
  109. self.assertEqual(pg.url, 'sub1/non-index/')
  110. self.assertEqual(pg.abs_url, None)
  111. self.assertEqual(pg.canonical_url, None)
  112. self.assertEqual(pg.edit_url, None)
  113. self.assertEqual(pg.file, fl)
  114. self.assertEqual(pg.content, None)
  115. self.assertFalse(pg.is_homepage)
  116. self.assertFalse(pg.is_index)
  117. self.assertTrue(pg.is_page)
  118. self.assertFalse(pg.is_section)
  119. self.assertFalse(pg.is_top_level)
  120. self.assertEqual(pg.markdown, None)
  121. self.assertEqual(pg.meta, {})
  122. self.assertEqual(pg.next_page, None)
  123. self.assertEqual(pg.parent, 'foo')
  124. self.assertEqual(pg.previous_page, None)
  125. self.assertEqual(pg.title, 'Foo')
  126. self.assertEqual(pg.toc, [])
  127. def test_page_defaults(self):
  128. cfg = load_config()
  129. fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  130. pg = Page('Foo', fl, cfg)
  131. self.assertRegex(pg.update_date, r'\d{4}-\d{2}-\d{2}')
  132. self.assertEqual(pg.url, 'testing/')
  133. self.assertEqual(pg.abs_url, None)
  134. self.assertEqual(pg.canonical_url, None)
  135. self.assertEqual(pg.edit_url, None)
  136. self.assertEqual(pg.file, fl)
  137. self.assertEqual(pg.content, None)
  138. self.assertFalse(pg.is_homepage)
  139. self.assertFalse(pg.is_index)
  140. self.assertTrue(pg.is_page)
  141. self.assertFalse(pg.is_section)
  142. self.assertTrue(pg.is_top_level)
  143. self.assertEqual(pg.markdown, None)
  144. self.assertEqual(pg.meta, {})
  145. self.assertEqual(pg.next_page, None)
  146. self.assertEqual(pg.parent, None)
  147. self.assertEqual(pg.previous_page, None)
  148. self.assertEqual(pg.title, 'Foo')
  149. self.assertEqual(pg.toc, [])
  150. def test_page_no_directory_url(self):
  151. cfg = load_config(use_directory_urls=False)
  152. fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  153. pg = Page('Foo', fl, cfg)
  154. self.assertEqual(pg.url, 'testing.html')
  155. self.assertEqual(pg.abs_url, None)
  156. self.assertEqual(pg.canonical_url, None)
  157. self.assertEqual(pg.edit_url, None)
  158. self.assertEqual(pg.file, fl)
  159. self.assertEqual(pg.content, None)
  160. self.assertFalse(pg.is_homepage)
  161. self.assertFalse(pg.is_index)
  162. self.assertTrue(pg.is_page)
  163. self.assertFalse(pg.is_section)
  164. self.assertTrue(pg.is_top_level)
  165. self.assertEqual(pg.markdown, None)
  166. self.assertEqual(pg.meta, {})
  167. self.assertEqual(pg.next_page, None)
  168. self.assertEqual(pg.parent, None)
  169. self.assertEqual(pg.previous_page, None)
  170. self.assertEqual(pg.title, 'Foo')
  171. self.assertEqual(pg.toc, [])
  172. def test_page_canonical_url(self):
  173. cfg = load_config(site_url='http://example.com')
  174. fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  175. pg = Page('Foo', fl, cfg)
  176. self.assertEqual(pg.url, 'testing/')
  177. self.assertEqual(pg.abs_url, '/testing/')
  178. self.assertEqual(pg.canonical_url, 'http://example.com/testing/')
  179. self.assertEqual(pg.edit_url, None)
  180. self.assertEqual(pg.file, fl)
  181. self.assertEqual(pg.content, None)
  182. self.assertFalse(pg.is_homepage)
  183. self.assertFalse(pg.is_index)
  184. self.assertTrue(pg.is_page)
  185. self.assertFalse(pg.is_section)
  186. self.assertTrue(pg.is_top_level)
  187. self.assertEqual(pg.markdown, None)
  188. self.assertEqual(pg.meta, {})
  189. self.assertEqual(pg.next_page, None)
  190. self.assertEqual(pg.parent, None)
  191. self.assertEqual(pg.previous_page, None)
  192. self.assertEqual(pg.title, 'Foo')
  193. self.assertEqual(pg.toc, [])
  194. def test_page_canonical_url_nested(self):
  195. cfg = load_config(site_url='http://example.com/foo/')
  196. fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  197. pg = Page('Foo', fl, cfg)
  198. self.assertEqual(pg.url, 'testing/')
  199. self.assertEqual(pg.abs_url, '/foo/testing/')
  200. self.assertEqual(pg.canonical_url, 'http://example.com/foo/testing/')
  201. self.assertEqual(pg.edit_url, None)
  202. self.assertEqual(pg.file, fl)
  203. self.assertEqual(pg.content, None)
  204. self.assertFalse(pg.is_homepage)
  205. self.assertFalse(pg.is_index)
  206. self.assertTrue(pg.is_page)
  207. self.assertFalse(pg.is_section)
  208. self.assertTrue(pg.is_top_level)
  209. self.assertEqual(pg.markdown, None)
  210. self.assertEqual(pg.meta, {})
  211. self.assertEqual(pg.next_page, None)
  212. self.assertEqual(pg.parent, None)
  213. self.assertEqual(pg.previous_page, None)
  214. self.assertEqual(pg.title, 'Foo')
  215. self.assertEqual(pg.toc, [])
  216. def test_page_canonical_url_nested_no_slash(self):
  217. cfg = load_config(site_url='http://example.com/foo')
  218. fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  219. pg = Page('Foo', fl, cfg)
  220. self.assertEqual(pg.url, 'testing/')
  221. self.assertEqual(pg.abs_url, '/foo/testing/')
  222. self.assertEqual(pg.canonical_url, 'http://example.com/foo/testing/')
  223. self.assertEqual(pg.edit_url, None)
  224. self.assertEqual(pg.file, fl)
  225. self.assertEqual(pg.content, None)
  226. self.assertFalse(pg.is_homepage)
  227. self.assertFalse(pg.is_index)
  228. self.assertTrue(pg.is_page)
  229. self.assertFalse(pg.is_section)
  230. self.assertTrue(pg.is_top_level)
  231. self.assertEqual(pg.markdown, None)
  232. self.assertEqual(pg.meta, {})
  233. self.assertEqual(pg.next_page, None)
  234. self.assertEqual(pg.parent, None)
  235. self.assertEqual(pg.previous_page, None)
  236. self.assertEqual(pg.title, 'Foo')
  237. self.assertEqual(pg.toc, [])
  238. def test_predefined_page_title(self):
  239. cfg = load_config()
  240. fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  241. pg = Page('Page Title', fl, cfg)
  242. pg.read_source(cfg)
  243. self.assertEqual(pg.url, 'testing/')
  244. self.assertEqual(pg.abs_url, None)
  245. self.assertEqual(pg.canonical_url, None)
  246. self.assertEqual(pg.edit_url, None)
  247. self.assertEqual(pg.file, fl)
  248. self.assertEqual(pg.content, None)
  249. self.assertFalse(pg.is_homepage)
  250. self.assertFalse(pg.is_index)
  251. self.assertTrue(pg.is_page)
  252. self.assertFalse(pg.is_section)
  253. self.assertTrue(pg.is_top_level)
  254. self.assertTrue(pg.markdown.startswith('# Welcome to MkDocs\n'))
  255. self.assertEqual(pg.meta, {})
  256. self.assertEqual(pg.next_page, None)
  257. self.assertEqual(pg.parent, None)
  258. self.assertEqual(pg.previous_page, None)
  259. self.assertEqual(pg.title, 'Page Title')
  260. self.assertEqual(pg.toc, [])
  261. def test_page_title_from_markdown(self):
  262. cfg = load_config()
  263. fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  264. pg = Page(None, fl, cfg)
  265. pg.read_source(cfg)
  266. self.assertEqual(pg.url, 'testing/')
  267. self.assertEqual(pg.abs_url, None)
  268. self.assertEqual(pg.canonical_url, None)
  269. self.assertEqual(pg.edit_url, None)
  270. self.assertEqual(pg.file, fl)
  271. self.assertEqual(pg.content, None)
  272. self.assertFalse(pg.is_homepage)
  273. self.assertFalse(pg.is_index)
  274. self.assertTrue(pg.is_page)
  275. self.assertFalse(pg.is_section)
  276. self.assertTrue(pg.is_top_level)
  277. self.assertTrue(pg.markdown.startswith('# Welcome to MkDocs\n'))
  278. self.assertEqual(pg.meta, {})
  279. self.assertEqual(pg.next_page, None)
  280. self.assertEqual(pg.parent, None)
  281. self.assertEqual(pg.previous_page, None)
  282. self.assertEqual(pg.title, 'Welcome to MkDocs')
  283. self.assertEqual(pg.toc, [])
  284. def test_page_title_from_meta(self):
  285. cfg = load_config(docs_dir=self.DOCS_DIR)
  286. fl = File('metadata.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  287. pg = Page(None, fl, cfg)
  288. pg.read_source(cfg)
  289. self.assertEqual(pg.url, 'metadata/')
  290. self.assertEqual(pg.abs_url, None)
  291. self.assertEqual(pg.canonical_url, None)
  292. self.assertEqual(pg.edit_url, None)
  293. self.assertEqual(pg.file, fl)
  294. self.assertEqual(pg.content, None)
  295. self.assertFalse(pg.is_homepage)
  296. self.assertFalse(pg.is_index)
  297. self.assertTrue(pg.is_page)
  298. self.assertFalse(pg.is_section)
  299. self.assertTrue(pg.is_top_level)
  300. self.assertTrue(pg.markdown.startswith('# Welcome to MkDocs\n'))
  301. self.assertEqual(pg.meta, {'title': 'A Page Title'})
  302. self.assertEqual(pg.next_page, None)
  303. self.assertEqual(pg.parent, None)
  304. self.assertEqual(pg.previous_page, None)
  305. self.assertEqual(pg.title, 'A Page Title')
  306. self.assertEqual(pg.toc, [])
  307. def test_page_title_from_filename(self):
  308. cfg = load_config(docs_dir=self.DOCS_DIR)
  309. fl = File('page-title.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  310. pg = Page(None, fl, cfg)
  311. pg.read_source(cfg)
  312. self.assertEqual(pg.url, 'page-title/')
  313. self.assertEqual(pg.abs_url, None)
  314. self.assertEqual(pg.canonical_url, None)
  315. self.assertEqual(pg.edit_url, None)
  316. self.assertEqual(pg.file, fl)
  317. self.assertEqual(pg.content, None)
  318. self.assertFalse(pg.is_homepage)
  319. self.assertFalse(pg.is_index)
  320. self.assertTrue(pg.is_page)
  321. self.assertFalse(pg.is_section)
  322. self.assertTrue(pg.is_top_level)
  323. self.assertTrue(pg.markdown.startswith('Page content.\n'))
  324. self.assertEqual(pg.meta, {})
  325. self.assertEqual(pg.next_page, None)
  326. self.assertEqual(pg.parent, None)
  327. self.assertEqual(pg.previous_page, None)
  328. self.assertEqual(pg.title, 'Page title')
  329. self.assertEqual(pg.toc, [])
  330. def test_page_title_from_capitalized_filename(self):
  331. cfg = load_config(docs_dir=self.DOCS_DIR)
  332. fl = File('pageTitle.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  333. pg = Page(None, fl, cfg)
  334. pg.read_source(cfg)
  335. self.assertEqual(pg.url, 'pageTitle/')
  336. self.assertEqual(pg.abs_url, None)
  337. self.assertEqual(pg.canonical_url, None)
  338. self.assertEqual(pg.edit_url, None)
  339. self.assertEqual(pg.file, fl)
  340. self.assertEqual(pg.content, None)
  341. self.assertFalse(pg.is_homepage)
  342. self.assertFalse(pg.is_index)
  343. self.assertTrue(pg.is_page)
  344. self.assertFalse(pg.is_section)
  345. self.assertTrue(pg.is_top_level)
  346. self.assertTrue(pg.markdown.startswith('Page content.\n'))
  347. self.assertEqual(pg.meta, {})
  348. self.assertEqual(pg.next_page, None)
  349. self.assertEqual(pg.parent, None)
  350. self.assertEqual(pg.previous_page, None)
  351. self.assertEqual(pg.title, 'pageTitle')
  352. self.assertEqual(pg.toc, [])
  353. def test_page_title_from_homepage_filename(self):
  354. cfg = load_config(docs_dir=self.DOCS_DIR)
  355. fl = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  356. pg = Page(None, fl, cfg)
  357. pg.read_source(cfg)
  358. self.assertEqual(pg.url, '')
  359. self.assertEqual(pg.abs_url, None)
  360. self.assertEqual(pg.canonical_url, None)
  361. self.assertEqual(pg.edit_url, None)
  362. self.assertEqual(pg.file, fl)
  363. self.assertEqual(pg.content, None)
  364. self.assertTrue(pg.is_homepage)
  365. self.assertTrue(pg.is_index)
  366. self.assertTrue(pg.is_page)
  367. self.assertFalse(pg.is_section)
  368. self.assertTrue(pg.is_top_level)
  369. self.assertTrue(pg.markdown.startswith('## Test'))
  370. self.assertEqual(pg.meta, {})
  371. self.assertEqual(pg.next_page, None)
  372. self.assertEqual(pg.parent, None)
  373. self.assertEqual(pg.previous_page, None)
  374. self.assertEqual(pg.title, 'Home')
  375. self.assertEqual(pg.toc, [])
  376. def test_page_eq(self):
  377. cfg = load_config()
  378. fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  379. pg = Page('Foo', fl, cfg)
  380. self.assertTrue(pg == Page('Foo', fl, cfg))
  381. def test_page_ne(self):
  382. cfg = load_config()
  383. f1 = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  384. f2 = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  385. pg = Page('Foo', f1, cfg)
  386. # Different Title
  387. self.assertTrue(pg != Page('Bar', f1, cfg))
  388. # Different File
  389. self.assertTrue(pg != Page('Foo', f2, cfg))
  390. def test_BOM(self):
  391. md_src = '# An UTF-8 encoded file with a BOM'
  392. with TemporaryDirectory() as docs_dir:
  393. # We don't use mkdocs.tests.base.tempdir decorator here due to uniqueness of this test.
  394. cfg = load_config(docs_dir=docs_dir)
  395. fl = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  396. pg = Page(None, fl, cfg)
  397. # Create an UTF-8 Encoded file with BOM (as Micorsoft editors do). See #1186
  398. with open(fl.abs_src_path, 'w', encoding='utf-8-sig') as f:
  399. f.write(md_src)
  400. # Now read the file.
  401. pg.read_source(cfg)
  402. # Ensure the BOM (`\ufeff`) is removed
  403. self.assertNotIn('\ufeff', pg.markdown)
  404. self.assertEqual(pg.markdown, md_src)
  405. self.assertEqual(pg.meta, {})
  406. def test_page_edit_url(self):
  407. configs = [
  408. {
  409. 'repo_url': 'http://github.com/mkdocs/mkdocs'
  410. },
  411. {
  412. 'repo_url': 'https://github.com/mkdocs/mkdocs/'
  413. }, {
  414. 'repo_url': 'http://example.com'
  415. }, {
  416. 'repo_url': 'http://example.com',
  417. 'edit_uri': 'edit/master'
  418. }, {
  419. 'repo_url': 'http://example.com',
  420. 'edit_uri': '/edit/master'
  421. }, {
  422. 'repo_url': 'http://example.com/foo/',
  423. 'edit_uri': '/edit/master/'
  424. }, {
  425. 'repo_url': 'http://example.com/foo',
  426. 'edit_uri': '/edit/master/'
  427. }, {
  428. 'repo_url': 'http://example.com/foo/',
  429. 'edit_uri': '/edit/master'
  430. }, {
  431. 'repo_url': 'http://example.com/foo/',
  432. 'edit_uri': 'edit/master/'
  433. }, {
  434. 'repo_url': 'http://example.com/foo',
  435. 'edit_uri': 'edit/master/'
  436. }, {
  437. 'repo_url': 'http://example.com',
  438. 'edit_uri': '?query=edit/master'
  439. }, {
  440. 'repo_url': 'http://example.com/',
  441. 'edit_uri': '?query=edit/master/'
  442. }, {
  443. 'repo_url': 'http://example.com',
  444. 'edit_uri': '#edit/master'
  445. }, {
  446. 'repo_url': 'http://example.com/',
  447. 'edit_uri': '#edit/master/'
  448. }, {
  449. 'repo_url': 'http://example.com',
  450. 'edit_uri': '' # Set to blank value
  451. }, {
  452. # Nothing defined
  453. }
  454. ]
  455. expected = [
  456. 'http://github.com/mkdocs/mkdocs/edit/master/docs/testing.md',
  457. 'https://github.com/mkdocs/mkdocs/edit/master/docs/testing.md',
  458. None,
  459. 'http://example.com/edit/master/testing.md',
  460. 'http://example.com/edit/master/testing.md',
  461. 'http://example.com/edit/master/testing.md',
  462. 'http://example.com/edit/master/testing.md',
  463. 'http://example.com/edit/master/testing.md',
  464. 'http://example.com/foo/edit/master/testing.md',
  465. 'http://example.com/foo/edit/master/testing.md',
  466. 'http://example.com?query=edit/master/testing.md',
  467. 'http://example.com/?query=edit/master/testing.md',
  468. 'http://example.com#edit/master/testing.md',
  469. 'http://example.com/#edit/master/testing.md',
  470. None,
  471. None
  472. ]
  473. for i, c in enumerate(configs):
  474. cfg = load_config(**c)
  475. fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  476. pg = Page('Foo', fl, cfg)
  477. self.assertEqual(pg.url, 'testing/')
  478. self.assertEqual(pg.edit_url, expected[i])
  479. def test_nested_page_edit_url(self):
  480. configs = [
  481. {
  482. 'repo_url': 'http://github.com/mkdocs/mkdocs'
  483. },
  484. {
  485. 'repo_url': 'https://github.com/mkdocs/mkdocs/'
  486. }, {
  487. 'repo_url': 'http://example.com'
  488. }, {
  489. 'repo_url': 'http://example.com',
  490. 'edit_uri': 'edit/master'
  491. }, {
  492. 'repo_url': 'http://example.com',
  493. 'edit_uri': '/edit/master'
  494. }, {
  495. 'repo_url': 'http://example.com/foo/',
  496. 'edit_uri': '/edit/master/'
  497. }, {
  498. 'repo_url': 'http://example.com/foo',
  499. 'edit_uri': '/edit/master/'
  500. }, {
  501. 'repo_url': 'http://example.com/foo/',
  502. 'edit_uri': '/edit/master'
  503. }, {
  504. 'repo_url': 'http://example.com/foo/',
  505. 'edit_uri': 'edit/master/'
  506. }, {
  507. 'repo_url': 'http://example.com/foo',
  508. 'edit_uri': 'edit/master/'
  509. }, {
  510. 'repo_url': 'http://example.com',
  511. 'edit_uri': '?query=edit/master'
  512. }, {
  513. 'repo_url': 'http://example.com/',
  514. 'edit_uri': '?query=edit/master/'
  515. }, {
  516. 'repo_url': 'http://example.com',
  517. 'edit_uri': '#edit/master'
  518. }, {
  519. 'repo_url': 'http://example.com/',
  520. 'edit_uri': '#edit/master/'
  521. }
  522. ]
  523. expected = [
  524. 'http://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md',
  525. 'https://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md',
  526. None,
  527. 'http://example.com/edit/master/sub1/non-index.md',
  528. 'http://example.com/edit/master/sub1/non-index.md',
  529. 'http://example.com/edit/master/sub1/non-index.md',
  530. 'http://example.com/edit/master/sub1/non-index.md',
  531. 'http://example.com/edit/master/sub1/non-index.md',
  532. 'http://example.com/foo/edit/master/sub1/non-index.md',
  533. 'http://example.com/foo/edit/master/sub1/non-index.md',
  534. 'http://example.com?query=edit/master/sub1/non-index.md',
  535. 'http://example.com/?query=edit/master/sub1/non-index.md',
  536. 'http://example.com#edit/master/sub1/non-index.md',
  537. 'http://example.com/#edit/master/sub1/non-index.md'
  538. ]
  539. for i, c in enumerate(configs):
  540. c['docs_dir'] = self.DOCS_DIR
  541. cfg = load_config(**c)
  542. fl = File('sub1/non-index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  543. pg = Page('Foo', fl, cfg)
  544. self.assertEqual(pg.url, 'sub1/non-index/')
  545. self.assertEqual(pg.edit_url, expected[i])
  546. @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
  547. def test_nested_page_edit_url_windows(self):
  548. configs = [
  549. {
  550. 'repo_url': 'http://github.com/mkdocs/mkdocs'
  551. },
  552. {
  553. 'repo_url': 'https://github.com/mkdocs/mkdocs/'
  554. }, {
  555. 'repo_url': 'http://example.com'
  556. }, {
  557. 'repo_url': 'http://example.com',
  558. 'edit_uri': 'edit/master'
  559. }, {
  560. 'repo_url': 'http://example.com',
  561. 'edit_uri': '/edit/master'
  562. }, {
  563. 'repo_url': 'http://example.com/foo/',
  564. 'edit_uri': '/edit/master/'
  565. }, {
  566. 'repo_url': 'http://example.com/foo',
  567. 'edit_uri': '/edit/master/'
  568. }, {
  569. 'repo_url': 'http://example.com/foo/',
  570. 'edit_uri': '/edit/master'
  571. }, {
  572. 'repo_url': 'http://example.com/foo/',
  573. 'edit_uri': 'edit/master/'
  574. }, {
  575. 'repo_url': 'http://example.com/foo',
  576. 'edit_uri': 'edit/master/'
  577. }, {
  578. 'repo_url': 'http://example.com',
  579. 'edit_uri': '?query=edit/master'
  580. }, {
  581. 'repo_url': 'http://example.com/',
  582. 'edit_uri': '?query=edit/master/'
  583. }, {
  584. 'repo_url': 'http://example.com',
  585. 'edit_uri': '#edit/master'
  586. }, {
  587. 'repo_url': 'http://example.com/',
  588. 'edit_uri': '#edit/master/'
  589. }
  590. ]
  591. expected = [
  592. 'http://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md',
  593. 'https://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md',
  594. None,
  595. 'http://example.com/edit/master/sub1/non-index.md',
  596. 'http://example.com/edit/master/sub1/non-index.md',
  597. 'http://example.com/edit/master/sub1/non-index.md',
  598. 'http://example.com/edit/master/sub1/non-index.md',
  599. 'http://example.com/edit/master/sub1/non-index.md',
  600. 'http://example.com/foo/edit/master/sub1/non-index.md',
  601. 'http://example.com/foo/edit/master/sub1/non-index.md',
  602. 'http://example.com?query=edit/master/sub1/non-index.md',
  603. 'http://example.com/?query=edit/master/sub1/non-index.md',
  604. 'http://example.com#edit/master/sub1/non-index.md',
  605. 'http://example.com/#edit/master/sub1/non-index.md'
  606. ]
  607. for i, c in enumerate(configs):
  608. c['docs_dir'] = self.DOCS_DIR
  609. cfg = load_config(**c)
  610. fl = File('sub1\\non-index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  611. pg = Page('Foo', fl, cfg)
  612. self.assertEqual(pg.url, 'sub1/non-index/')
  613. self.assertEqual(pg.edit_url, expected[i])
  614. def test_page_render(self):
  615. cfg = load_config()
  616. fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  617. pg = Page('Foo', fl, cfg)
  618. pg.read_source(cfg)
  619. self.assertEqual(pg.content, None)
  620. self.assertEqual(pg.toc, [])
  621. pg.render(cfg, [fl])
  622. self.assertTrue(pg.content.startswith(
  623. '<h1 id="welcome-to-mkdocs">Welcome to MkDocs</h1>\n'
  624. ))
  625. self.assertEqual(str(pg.toc).strip(), dedent("""
  626. Welcome to MkDocs - #welcome-to-mkdocs
  627. Commands - #commands
  628. Project layout - #project-layout
  629. """))
  630. def test_missing_page(self):
  631. cfg = load_config()
  632. fl = File('missing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  633. pg = Page('Foo', fl, cfg)
  634. self.assertRaises(OSError, pg.read_source, cfg)
  635. class SourceDateEpochTests(unittest.TestCase):
  636. def setUp(self):
  637. self.default = os.environ.get('SOURCE_DATE_EPOCH', None)
  638. os.environ['SOURCE_DATE_EPOCH'] = '0'
  639. def test_source_date_epoch(self):
  640. cfg = load_config()
  641. fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
  642. pg = Page('Foo', fl, cfg)
  643. self.assertEqual(pg.update_date, '1970-01-01')
  644. def tearDown(self):
  645. if self.default is not None:
  646. os.environ['SOURCE_DATE_EPOCH'] = self.default
  647. else:
  648. del os.environ['SOURCE_DATE_EPOCH']
  649. class RelativePathExtensionTests(unittest.TestCase):
  650. DOCS_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../integration/subpages/docs')
  651. def get_rendered_result(self, files):
  652. cfg = load_config(docs_dir=self.DOCS_DIR)
  653. fs = []
  654. for f in files:
  655. fs.append(File(f.replace('/', os.sep), cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']))
  656. pg = Page('Foo', fs[0], cfg)
  657. pg.read_source(cfg)
  658. pg.render(cfg, Files(fs))
  659. return pg.content
  660. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](non-index.md)'))
  661. def test_relative_html_link(self):
  662. self.assertEqual(
  663. self.get_rendered_result(['index.md', 'non-index.md']),
  664. '<p><a href="non-index/">link</a></p>' # No trailing /
  665. )
  666. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](index.md)'))
  667. def test_relative_html_link_index(self):
  668. self.assertEqual(
  669. self.get_rendered_result(['non-index.md', 'index.md']),
  670. '<p><a href="..">link</a></p>'
  671. )
  672. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](sub2/index.md)'))
  673. def test_relative_html_link_sub_index(self):
  674. self.assertEqual(
  675. self.get_rendered_result(['index.md', 'sub2/index.md']),
  676. '<p><a href="sub2/">link</a></p>' # No trailing /
  677. )
  678. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](sub2/non-index.md)'))
  679. def test_relative_html_link_sub_page(self):
  680. self.assertEqual(
  681. self.get_rendered_result(['index.md', 'sub2/non-index.md']),
  682. '<p><a href="sub2/non-index/">link</a></p>' # No trailing /
  683. )
  684. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](file%20name.md)'))
  685. def test_relative_html_link_with_encoded_space(self):
  686. self.assertEqual(
  687. self.get_rendered_result(['index.md', 'file name.md']),
  688. '<p><a href="file%20name/">link</a></p>'
  689. )
  690. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](file name.md)'))
  691. def test_relative_html_link_with_unencoded_space(self):
  692. self.assertEqual(
  693. self.get_rendered_result(['index.md', 'file name.md']),
  694. '<p><a href="file%20name/">link</a></p>'
  695. )
  696. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](../index.md)'))
  697. def test_relative_html_link_parent_index(self):
  698. self.assertEqual(
  699. self.get_rendered_result(['sub2/non-index.md', 'index.md']),
  700. '<p><a href="../..">link</a></p>'
  701. )
  702. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](non-index.md#hash)'))
  703. def test_relative_html_link_hash(self):
  704. self.assertEqual(
  705. self.get_rendered_result(['index.md', 'non-index.md']),
  706. '<p><a href="non-index/#hash">link</a></p>'
  707. )
  708. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](sub2/index.md#hash)'))
  709. def test_relative_html_link_sub_index_hash(self):
  710. self.assertEqual(
  711. self.get_rendered_result(['index.md', 'sub2/index.md']),
  712. '<p><a href="sub2/#hash">link</a></p>'
  713. )
  714. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](sub2/non-index.md#hash)'))
  715. def test_relative_html_link_sub_page_hash(self):
  716. self.assertEqual(
  717. self.get_rendered_result(['index.md', 'sub2/non-index.md']),
  718. '<p><a href="sub2/non-index/#hash">link</a></p>'
  719. )
  720. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](#hash)'))
  721. def test_relative_html_link_hash_only(self):
  722. self.assertEqual(
  723. self.get_rendered_result(['index.md']),
  724. '<p><a href="#hash">link</a></p>'
  725. )
  726. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='![image](image.png)'))
  727. def test_relative_image_link_from_homepage(self):
  728. self.assertEqual(
  729. self.get_rendered_result(['index.md', 'image.png']),
  730. '<p><img alt="image" src="image.png" /></p>' # no opening ./
  731. )
  732. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='![image](../image.png)'))
  733. def test_relative_image_link_from_subpage(self):
  734. self.assertEqual(
  735. self.get_rendered_result(['sub2/non-index.md', 'image.png']),
  736. '<p><img alt="image" src="../../image.png" /></p>'
  737. )
  738. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='![image](image.png)'))
  739. def test_relative_image_link_from_sibling(self):
  740. self.assertEqual(
  741. self.get_rendered_result(['non-index.md', 'image.png']),
  742. '<p><img alt="image" src="../image.png" /></p>'
  743. )
  744. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='*__not__ a link*.'))
  745. def test_no_links(self):
  746. self.assertEqual(
  747. self.get_rendered_result(['index.md']),
  748. '<p><em><strong>not</strong> a link</em>.</p>'
  749. )
  750. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](non-existant.md)'))
  751. def test_bad_relative_html_link(self):
  752. with self.assertLogs('mkdocs', level='WARNING') as cm:
  753. self.assertEqual(
  754. self.get_rendered_result(['index.md']),
  755. '<p><a href="non-existant.md">link</a></p>'
  756. )
  757. self.assertEqual(
  758. cm.output,
  759. ["WARNING:mkdocs.structure.pages:Documentation file 'index.md' contains a link "
  760. "to 'non-existant.md' which is not found in the documentation files."]
  761. )
  762. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[external](http://example.com/index.md)'))
  763. def test_external_link(self):
  764. self.assertEqual(
  765. self.get_rendered_result(['index.md']),
  766. '<p><a href="http://example.com/index.md">external</a></p>'
  767. )
  768. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[absolute link](/path/to/file.md)'))
  769. def test_absolute_link(self):
  770. self.assertEqual(
  771. self.get_rendered_result(['index.md']),
  772. '<p><a href="/path/to/file.md">absolute link</a></p>'
  773. )
  774. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[absolute local path](\\image.png)'))
  775. def test_absolute_win_local_path(self):
  776. self.assertEqual(
  777. self.get_rendered_result(['index.md']),
  778. '<p><a href="\\image.png">absolute local path</a></p>'
  779. )
  780. @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='<mail@example.com>'))
  781. def test_email_link(self):
  782. self.assertEqual(
  783. self.get_rendered_result(['index.md']),
  784. # Markdown's default behavior is to obscure email addresses by entity-encoding them.
  785. # The following is equivalent to: '<p><a href="mailto:mail@example.com">mail@example.com</a></p>'
  786. '<p><a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#109;&#97;&#105;&#108;&#64;&#101;'
  787. '&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;">&#109;&#97;&#105;&#108;&#64;'
  788. '&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;</a></p>'
  789. )