Coverage for tests/test_settings.py: 100%
79 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-31 22:06 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-31 22:06 +0000
1"""Tests for project files and settings."""
3from __future__ import annotations
5import pathlib
7import pytest
9from coursebook import settings
10from tests.conftest import PROJECTS
13def _write(folder: pathlib.Path, name: str, text: str) -> pathlib.Path:
14 """Writes a project file and returns its path."""
15 path = folder / name
16 path.write_text(text, encoding = 'utf-8')
17 return path
20def test_read_ini() -> None:
21 """Tests the `ini` project file of the coursebook test project."""
22 project_settings, project_outline = settings.read_project_file(
23 PROJECTS / 'coursebook' / 'project.ini')
24 assert project_settings.name == 'Crazy Idea'
25 assert project_settings.style == 'coursebook'
26 assert project_settings.include == ['text', 'slides', 'notes']
27 assert project_settings.slides_template == 'chalkboard'
28 assert project_settings.author == ['Corey Rayburn Yung']
29 assert len(project_outline) == 8
32def test_read_toml() -> None:
33 """Tests the `toml` project file of the law review test project."""
34 project_settings, project_outline = settings.read_project_file(
35 PROJECTS / 'law_review' / 'project.toml')
36 assert project_settings.style == 'law review'
37 assert project_settings.include == ['text', 'slides']
38 assert project_settings.bibliography == ['references.bib']
39 assert [entry.label for entry in project_outline] == [
40 'I', 'II', 'A', 'B', 'III', 'A', 'B', 'IV']
43def test_ini_repeated_labels(tmp_path: pathlib.Path) -> None:
44 """Tests that an `ini` outline may repeat labels."""
45 path = _write(tmp_path, 'project.ini', (
46 '[project]\nname = Test\n\n[OUTLINE]\n# A comment\nI: One\nA: Sub\n'
47 'II: Two\nA: Sub Two\n'))
48 _, project_outline = settings.read_project_file(path)
49 assert [entry.title for entry in project_outline] == [
50 'One', 'Sub', 'Two', 'Sub Two']
53def test_ini_types(tmp_path: pathlib.Path) -> None:
54 """Tests converting `ini` values."""
55 path = _write(tmp_path, 'project.ini', (
56 '[Project]\nName: Test\nAuthors: King, Jr.; Doe\nTOC: no\n'
57 'Number Headings: yes\nInclude: Slides,manuscript\n'
58 'Course Number: 101\n\n[Outline]\nI: One\n'))
59 project_settings, _ = settings.read_project_file(path)
60 assert project_settings.author == ['King, Jr.', 'Doe']
61 assert project_settings.table_of_contents is False
62 assert project_settings.number_headings is True
63 assert project_settings.include == ['slides', 'text']
64 assert project_settings.extras == {'course_number': 101}
67def test_formats(tmp_path: pathlib.Path) -> None:
68 """Tests settings with more than one format."""
69 path = _write(tmp_path, 'project.ini', (
70 '[Project]\nText Format: word, PDF\nNotes Formats: html\n\n'
71 '[Outline]\nI: One\n'))
72 project_settings, _ = settings.read_project_file(path)
73 assert project_settings.formats_for('text') == ['word', 'PDF']
74 assert project_settings.formats_for('notes') == ['html']
75 assert project_settings.formats_for('slides') == ['powerpoint']
78def test_yaml(tmp_path: pathlib.Path) -> None:
79 """Tests a `yaml` project file with mapping and string outline items."""
80 path = _write(tmp_path, 'project.yaml', (
81 'project:\n name: Test\n author: Doe\n'
82 'outline:\n - I: One\n - "A: Sub"\n - 1: Deep\n'))
83 project_settings, project_outline = settings.read_project_file(path)
84 assert project_settings.author == ['Doe']
85 assert [entry.level for entry in project_outline] == [0, 1, 2]
88def test_outline_inside_project_table(tmp_path: pathlib.Path) -> None:
89 """Tests a `toml` outline inside the project table."""
90 path = _write(tmp_path, 'project.toml', (
91 '[project]\nname = "Test"\noutline = [["I", "One"], "II: Two", '
92 '["III", "Three", "three_notes.md"]]\n'))
93 project_settings, project_outline = settings.read_project_file(path)
94 assert 'outline' not in project_settings.extras
95 assert len(project_outline) == 3
96 assert project_outline.entries[2].file == 'three_notes.md'
99@pytest.mark.parametrize(('name', 'text', 'match'), [
100 ('project.ini', '[Outline]\nI: One\n', 'no \\[Project\\]'),
101 ('project.ini', 'Name: x\n', 'not a valid ini'),
102 ('project.toml', 'outline = []\n', 'no "project" table'),
103 ('project.toml', '[project\n', 'could not be read'),
104 ('project.toml', '[project]\noutline = "I: x"\n', 'must be a list'),
105 ('project.yaml', 'project: {}\noutline: [[1, 2, 3, 4]]\n', 'label and'),
106 ('project.ini', '[Project]\nInclude: handouts\n', 'unknown section'),
107 ('project.ini', '[Project]\nTOC: maybe\n', 'yes or no')])
108def test_invalid_project_files(
109 tmp_path: pathlib.Path,
110 name: str,
111 text: str,
112 match: str) -> None:
113 """Tests project files that are not valid."""
114 path = _write(tmp_path, name, text)
115 with pytest.raises(ValueError, match = match):
116 settings.read_project_file(path)
119def test_unsupported_extension(tmp_path: pathlib.Path) -> None:
120 """Tests a project file with an unsupported extension."""
121 path = _write(tmp_path, 'project.json', '{}')
122 with pytest.raises(ValueError, match = 'not an ini'):
123 settings.read_project_file(path)
126def test_find_project_file(tmp_path: pathlib.Path) -> None:
127 """Tests finding the project file, ignoring case."""
128 path = _write(tmp_path, 'Project.INI', '')
129 assert settings.find_project_file(tmp_path) == path
130 _write(tmp_path, 'project.toml', '')
131 with pytest.raises(ValueError, match = 'more than one'):
132 settings.find_project_file(tmp_path)
135def test_find_project_file_missing(tmp_path: pathlib.Path) -> None:
136 """Tests a folder without a project file and a missing folder."""
137 with pytest.raises(FileNotFoundError, match = 'no project file'):
138 settings.find_project_file(tmp_path)
139 with pytest.raises(FileNotFoundError, match = 'does not exist'):
140 settings.find_project_file(tmp_path / 'missing')
141 with pytest.raises(FileNotFoundError, match = 'not found'):
142 settings.read_project_file(tmp_path / 'project.ini')
145def test_settings_helpers() -> None:
146 """Tests the format and template helpers."""
147 project_settings = settings.Settings(text_template = 'house')
148 assert project_settings.formats_for('slides') == ['powerpoint']
149 assert project_settings.template_for('notes') == 'house'
150 assert project_settings.template_for('slides') is None