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

1"""Tests for project files and settings.""" 

2 

3from __future__ import annotations 

4 

5import pathlib 

6 

7import pytest 

8 

9from coursebook import settings 

10from tests.conftest import PROJECTS 

11 

12 

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 

18 

19 

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 

30 

31 

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'] 

41 

42 

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'] 

51 

52 

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} 

65 

66 

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'] 

76 

77 

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] 

86 

87 

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' 

97 

98 

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) 

117 

118 

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) 

124 

125 

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) 

133 

134 

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') 

143 

144 

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