mirror of
https://github.com/rendercv/rendercv.git
synced 2026-02-05 19:11:47 -05:00
* Rename `data` folder with schema * Start refactoring data models * Work on entry models * Keep working on entries * Keep working on data models * Push old data files * Keep working on data models * First draft of schema.cv * Keep working on schema * Keep working on schema * Improve schema.models * Keep working on rendercv.schema * Work on schema.design * Keep working on rendercv.schema * Complete variant_class_generator * Keep working rendercv.schema * Keep working on rendercv.schema * Final touches to rendercv.schema * Improve json schema descriptions in rendercv.schema * Start working on rendercv.schema tests * Keep implementing rendercv.schema tests * Add more tests for rendercv.schema * Improve rendercv.schema * Improve docstrings and comments in rendercv.schema * Implement better pydantic error handling in `rendercv.schema` * Improve variant class system * Fix rendercv.schema tests * Start working on rendercv.templater * Update template names * Switching to new rendercv typst template soon * Work on new templater * Rename renderer with renderer_old * Don't use utils in rendercv.schema * Complete connections * Update renderer folder structure * Work on new renderer * Work on new renderer * Date processing on new renderer * Improve date processing, support multiple emails, phones, and websites * Improve markdown to Typst * Complete entry template processing * Time span computation in new renderer * Better entry templates * Setup new templates * Improve rendercv.schema * Start adding tests for rendercv.renderer * New markdown parser! * Improve markdown to typst conversion * Finalize markdown parser * Add new test files for rendercv.renderer * Fix cv and connections * Add connections test * Improve connection tests * Improve entry templates * Add model processor tests * Improve templater * Rename old folders * Improve schema * Add file generation logic to renderer * Fix naming issues * Fix schema tests * Add path type tests * Add font family and typst dimension type tests * Rename old tests * Fix design tests * Start integration testing of renderer * Improve entry tempates * Handle nested highlights properly * Finalize Typst preamble template * Start working on new CLI * Remove old test files * Implement override dictionary in new schema * Start working on new CLI * Better prints on render command * New structure * New render printer * Add all the commands to new CLI * Work on new command in new cli * Improve new command * Add error handler to new cli * Work on create theme command * Complete create theme command * Remove old source files * Improve exceptions * Create new docs * Add writing tests guide * Fix cli printer and write tests * Test copy templates * Add app tests * Bring back accidentally removed files * Imporve cli and tests * Fix path issues * Improve * Improve * Add reference file comparison tests * Fix path resolver * Start working on test_pdf_png * Implement comparison of multiple files (png) * Start testing typst * Fix templating issues * Fix header and entry templates issues * Implement short second rows * Fix date issues * Fix nested bullets and add summary * Update testdata * Implement footer * Update testdata * Reimagined design and locale schema, first iteration * Reimagined design and locale second iteration * Update design and locale schemas * Adapt templater to the new design and locale * Fix tests * Update lib.typ and testdata for the new locale and design * Implement proper tests with all combinations of entries * Remove some docstrings * fix connections logic * Improve * Start working on examples * Update testdata * Fix long second row issue * fix templating issues * Fix lib.typ issues * Update testdata * Fix clean_trailing_parts * Update test cv * update test cv * Update theme defaults * update schema and fix moderncv * Fix moderncv issues * Update testdata * Update testdata and examples * Fix issues about photo * Fix typst photo path issues * improve entry templates from yaml * add new locale * Rename writing tests doc * Update writing tests * Improve tests * Add more cli tests * Increase test coverage * Rename variant pydantic model generator * Improve tests * Update testdata and improve tests * Format, fix pre-commit errors * Fix scripts and update entry figures * Improve tests * Write docstrings of schema * Write schema docstrings * Setup api reference * Start working on new docs * Work on docs * Improve progress panel of render command * Finalize new docs index * Complete CLI docs * Work on YAML input structure page * Finalize user guide * Start working on developer guide * Improve api reference * Improve developer guide * Improve developer guide * Improve developer gide * Improve developer guide * Improve developer guide * Update developer guide * Improve developer guide * Improve developer guide * Improve developer guide * Developer guide first draft * update developer guide * Update examples * Update testdata * Handle wrong installation (rendercv instead of rendercv[full]) * Remove unnecessary files * Write set up vs code page * Update README.md * Change docs description * Compress design options gif * minor updates * Polish all the json schema descriptions * Update testdata and examples * Remove some emdashed from docs * Add whatsapp support * Add TestEscapeTypstCharacters to tests * Implement custom connections * Add page break before sections feature * Revert page break before sections feature * Rebase to main * Fix social network tests, update schema
485 lines
17 KiB
Python
485 lines
17 KiB
Python
import io
|
|
import pathlib
|
|
from datetime import date as Date
|
|
|
|
import pytest
|
|
import ruamel.yaml
|
|
|
|
from rendercv.exception import RenderCVUserError
|
|
from rendercv.schema.models.rendercv_model import RenderCVModel
|
|
from rendercv.schema.rendercv_model_builder import (
|
|
build_rendercv_dictionary,
|
|
build_rendercv_dictionary_and_model,
|
|
build_rendercv_model_from_commented_map,
|
|
)
|
|
from rendercv.schema.sample_generator import dictionary_to_yaml
|
|
|
|
|
|
@pytest.fixture
|
|
def minimal_input_dict():
|
|
return {
|
|
"cv": {"name": "John Doe"},
|
|
"design": {"theme": "classic"},
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def create_yaml_file_fixture(tmp_path):
|
|
"""Factory fixture to create temporary YAML files."""
|
|
|
|
def create_file(name: str, dictionary: dict) -> pathlib.Path:
|
|
file_path = tmp_path / name
|
|
yaml_content = dictionary_to_yaml(dictionary)
|
|
file_path.write_text(yaml_content, encoding="utf-8")
|
|
return file_path
|
|
|
|
return create_file
|
|
|
|
|
|
class TestBuildRendercvDictionary:
|
|
@pytest.mark.parametrize(
|
|
"input_type",
|
|
["string", "file"],
|
|
)
|
|
def test_basic_input_without_overlays(
|
|
self, minimal_input_dict, create_yaml_file_fixture, input_type
|
|
):
|
|
if input_type == "string":
|
|
yaml_input = dictionary_to_yaml(minimal_input_dict)
|
|
else: # file
|
|
yaml_input = create_yaml_file_fixture("input.yaml", minimal_input_dict)
|
|
|
|
result = build_rendercv_dictionary(yaml_input)
|
|
|
|
assert result["cv"]["name"] == "John Doe"
|
|
assert result["design"]["theme"] == "classic"
|
|
|
|
@pytest.mark.parametrize("input_type", ["string", "file"])
|
|
@pytest.mark.parametrize(
|
|
("overlay_key", "overlay_content"),
|
|
[
|
|
("design", {"design": {"theme": "engineeringresumes"}}),
|
|
("locale", {"locale": {"language": "turkish"}}),
|
|
(
|
|
"settings",
|
|
{"settings": {"render_command": {"pdf_path": "custom.pdf"}}},
|
|
),
|
|
],
|
|
)
|
|
def test_single_overlay(
|
|
self,
|
|
minimal_input_dict,
|
|
create_yaml_file_fixture,
|
|
overlay_key,
|
|
overlay_content,
|
|
input_type,
|
|
):
|
|
main_input = {
|
|
**minimal_input_dict,
|
|
"locale": {"language": "english"},
|
|
"settings": {"original_setting": "original"},
|
|
}
|
|
|
|
main_yaml = dictionary_to_yaml(main_input)
|
|
|
|
# Create overlay as string or file
|
|
if input_type == "string":
|
|
overlay_input = dictionary_to_yaml(overlay_content)
|
|
else: # file
|
|
overlay_input = create_yaml_file_fixture(
|
|
f"{overlay_key}.yaml", overlay_content
|
|
)
|
|
|
|
kwargs = {f"{overlay_key}_file_path_or_contents": overlay_input}
|
|
result = build_rendercv_dictionary(main_yaml, **kwargs) # pyright: ignore[reportArgumentType]
|
|
assert result[overlay_key] == overlay_content[overlay_key]
|
|
assert result["cv"]["name"] == "John Doe"
|
|
|
|
def test_all_overlays_simultaneously(self, create_yaml_file_fixture):
|
|
main_input = {
|
|
"cv": {"name": "John Doe"},
|
|
"design": {"theme": "classic"},
|
|
"locale": {"language": "english"},
|
|
"settings": {"render_command": {"pdf_path": "original.pdf"}},
|
|
}
|
|
design_overlay = {"design": {"theme": "sb2nov"}}
|
|
locale_overlay = {"locale": {"language": "turkish"}}
|
|
settings_overlay = {
|
|
"settings": {"render_command": {"pdf_path": "replaced.pdf"}}
|
|
}
|
|
|
|
main_file = create_yaml_file_fixture("main.yaml", main_input)
|
|
design_file = create_yaml_file_fixture("design.yaml", design_overlay)
|
|
locale_file = create_yaml_file_fixture("locale.yaml", locale_overlay)
|
|
settings_file = create_yaml_file_fixture("settings.yaml", settings_overlay)
|
|
|
|
result = build_rendercv_dictionary(
|
|
main_file,
|
|
design_file_path_or_contents=design_file,
|
|
locale_file_path_or_contents=locale_file,
|
|
settings_file_path_or_contents=settings_file,
|
|
)
|
|
|
|
assert result["cv"]["name"] == "John Doe"
|
|
assert result["design"]["theme"] == "sb2nov"
|
|
assert result["locale"]["language"] == "turkish"
|
|
assert result["settings"]["render_command"]["pdf_path"] == "replaced.pdf"
|
|
|
|
@pytest.mark.parametrize(
|
|
("override_key", "override_value"),
|
|
[
|
|
("typst_path", "custom.typ"),
|
|
("pdf_path", "output.pdf"),
|
|
("markdown_path", "output.md"),
|
|
("html_path", "output.html"),
|
|
("png_path", "output.png"),
|
|
("dont_generate_html", True),
|
|
("dont_generate_markdown", True),
|
|
("dont_generate_pdf", True),
|
|
("dont_generate_png", True),
|
|
],
|
|
)
|
|
def test_render_command_single_override(
|
|
self, minimal_input_dict, override_key, override_value
|
|
):
|
|
yaml_input = dictionary_to_yaml(minimal_input_dict)
|
|
|
|
kwargs = {override_key: override_value}
|
|
result = build_rendercv_dictionary(yaml_input, **kwargs)
|
|
|
|
assert result["settings"]["render_command"][override_key] == override_value
|
|
|
|
def test_render_command_multiple_overrides(self, minimal_input_dict):
|
|
yaml_input = dictionary_to_yaml(minimal_input_dict)
|
|
|
|
result = build_rendercv_dictionary(
|
|
yaml_input,
|
|
pdf_path="output.pdf",
|
|
typst_path="output.typ",
|
|
dont_generate_html=True,
|
|
dont_generate_markdown=True,
|
|
)
|
|
|
|
render_cmd = result["settings"]["render_command"]
|
|
assert render_cmd["pdf_path"] == "output.pdf"
|
|
assert render_cmd["typst_path"] == "output.typ"
|
|
assert render_cmd["dont_generate_html"] is True
|
|
assert render_cmd["dont_generate_markdown"] is True
|
|
|
|
def test_render_command_preserves_existing_settings(self):
|
|
input_dict = {
|
|
"cv": {"name": "John Doe"},
|
|
"design": {"theme": "classic"},
|
|
"settings": {
|
|
"render_command": {"pdf_path": "existing.pdf"},
|
|
"other_setting": "preserved",
|
|
},
|
|
}
|
|
yaml_input = dictionary_to_yaml(input_dict)
|
|
|
|
result = build_rendercv_dictionary(yaml_input, typst_path="new.typ")
|
|
|
|
assert result["settings"]["render_command"]["typst_path"] == "new.typ"
|
|
assert result["settings"]["other_setting"] == "preserved"
|
|
|
|
def test_combined_overlays_and_render_overrides(self, create_yaml_file_fixture):
|
|
main_input = {
|
|
"cv": {"name": "John Doe"},
|
|
"design": {"theme": "classic"},
|
|
}
|
|
locale_overlay = {"locale": {"language": "turkish"}}
|
|
|
|
main_file = create_yaml_file_fixture("main.yaml", main_input)
|
|
locale_file = create_yaml_file_fixture("locale.yaml", locale_overlay)
|
|
|
|
result = build_rendercv_dictionary(
|
|
main_file,
|
|
locale_file_path_or_contents=locale_file,
|
|
pdf_path="custom.pdf",
|
|
dont_generate_png=True,
|
|
)
|
|
|
|
assert result["locale"]["language"] == "turkish"
|
|
assert result["settings"]["render_command"]["pdf_path"] == "custom.pdf"
|
|
assert result["settings"]["render_command"]["dont_generate_png"] is True
|
|
|
|
@pytest.mark.parametrize(
|
|
("overrides", "expected_checks"),
|
|
[
|
|
(
|
|
{"cv.name": "Jane Doe"},
|
|
[("cv", "name", "Jane Doe")],
|
|
),
|
|
(
|
|
{"design.theme": "sb2nov"},
|
|
[("design", "theme", "sb2nov")],
|
|
),
|
|
(
|
|
{"cv.name": "Jane Doe", "design.theme": "engineeringresumes"},
|
|
[("cv", "name", "Jane Doe"), ("design", "theme", "engineeringresumes")],
|
|
),
|
|
],
|
|
)
|
|
def test_overrides_parameter(self, minimal_input_dict, overrides, expected_checks):
|
|
yaml_input = dictionary_to_yaml(minimal_input_dict)
|
|
|
|
result = build_rendercv_dictionary(yaml_input, overrides=overrides)
|
|
|
|
for path_and_value in expected_checks:
|
|
value = result
|
|
for key in path_and_value[:-1]:
|
|
value = value[key]
|
|
assert value == path_and_value[-1]
|
|
|
|
def test_overrides_with_nested_paths(self, minimal_input_dict):
|
|
input_dict = {
|
|
**minimal_input_dict,
|
|
"cv": {
|
|
"name": "John Doe",
|
|
"sections": {"education": [{"institution": "MIT", "degree": "PhD"}]},
|
|
},
|
|
}
|
|
yaml_input = dictionary_to_yaml(input_dict)
|
|
|
|
result = build_rendercv_dictionary(
|
|
yaml_input,
|
|
overrides={
|
|
"cv.sections.education.0.institution": "Harvard",
|
|
"cv.sections.education.0.degree": "MS",
|
|
},
|
|
)
|
|
|
|
assert result["cv"]["sections"]["education"][0]["institution"] == "Harvard"
|
|
assert result["cv"]["sections"]["education"][0]["degree"] == "MS"
|
|
assert result["cv"]["name"] == "John Doe"
|
|
|
|
|
|
class TestBuildRendercvModelFromDictionary:
|
|
def test_basic_model_creation_without_optionals(self, minimal_input_dict):
|
|
model = build_rendercv_model_from_commented_map(minimal_input_dict)
|
|
|
|
assert isinstance(model, RenderCVModel)
|
|
assert model.cv.name == "John Doe"
|
|
assert model._input_file_path is None
|
|
|
|
def test_with_input_file_path(self, minimal_input_dict, tmp_path):
|
|
input_file_path = tmp_path / "test.yaml"
|
|
|
|
model = build_rendercv_model_from_commented_map(
|
|
minimal_input_dict, input_file_path
|
|
)
|
|
|
|
assert isinstance(model, RenderCVModel)
|
|
assert model._input_file_path == input_file_path
|
|
|
|
def test_without_input_file_path(self, minimal_input_dict):
|
|
model = build_rendercv_model_from_commented_map(minimal_input_dict)
|
|
|
|
assert model._input_file_path is None
|
|
|
|
@pytest.mark.parametrize(
|
|
"settings",
|
|
[
|
|
{"current_date": Date(2023, 6, 15)},
|
|
None,
|
|
{},
|
|
],
|
|
)
|
|
def test_validation_context_current_date(self, minimal_input_dict, settings):
|
|
input_dict = minimal_input_dict.copy()
|
|
|
|
if settings is not None:
|
|
input_dict["settings"] = settings
|
|
|
|
model = build_rendercv_model_from_commented_map(input_dict)
|
|
|
|
assert isinstance(model, RenderCVModel)
|
|
|
|
def test_validation_context_with_input_file_path(
|
|
self, minimal_input_dict, tmp_path
|
|
):
|
|
input_file_path = tmp_path / "test.yaml"
|
|
custom_date = Date(2024, 3, 10)
|
|
|
|
input_dict = {
|
|
**minimal_input_dict,
|
|
"settings": {"current_date": custom_date},
|
|
}
|
|
|
|
model = build_rendercv_model_from_commented_map(input_dict, input_file_path) # pyright: ignore[reportArgumentType]
|
|
|
|
assert isinstance(model, RenderCVModel)
|
|
assert model._input_file_path == input_file_path
|
|
|
|
|
|
class TestBuildRendercvModel:
|
|
@pytest.mark.parametrize(
|
|
"input_type",
|
|
["string", "file"],
|
|
)
|
|
def test_basic_model_creation(
|
|
self, minimal_input_dict, create_yaml_file_fixture, input_type
|
|
):
|
|
if input_type == "string":
|
|
yaml_input = dictionary_to_yaml(minimal_input_dict)
|
|
expected_file_path = None
|
|
else: # file
|
|
yaml_input = create_yaml_file_fixture("input.yaml", minimal_input_dict)
|
|
expected_file_path = yaml_input
|
|
|
|
_, model = build_rendercv_dictionary_and_model(yaml_input)
|
|
|
|
assert isinstance(model, RenderCVModel)
|
|
assert model.cv.name == "John Doe"
|
|
assert model._input_file_path == expected_file_path
|
|
|
|
@pytest.mark.parametrize("overlay_type", ["string", "file"])
|
|
@pytest.mark.parametrize("overlay_key", ["design", "locale", "settings"])
|
|
def test_with_single_overlay(
|
|
self, minimal_input_dict, create_yaml_file_fixture, overlay_type, overlay_key
|
|
):
|
|
overlay_content = {
|
|
"design": {"design": {"theme": "sb2nov"}},
|
|
"locale": {"locale": {"language": "turkish"}},
|
|
"settings": {"settings": {"render_command": {"pdf_path": "custom.pdf"}}},
|
|
}[overlay_key]
|
|
|
|
main_yaml = dictionary_to_yaml(minimal_input_dict)
|
|
|
|
if overlay_type == "string":
|
|
overlay_input = dictionary_to_yaml(overlay_content)
|
|
else: # file
|
|
overlay_input = create_yaml_file_fixture(
|
|
f"{overlay_key}.yaml", overlay_content
|
|
)
|
|
|
|
kwargs = {f"{overlay_key}_file_path_or_contents": overlay_input}
|
|
_, model = build_rendercv_dictionary_and_model(main_yaml, **kwargs) # pyright: ignore[reportArgumentType]
|
|
|
|
assert isinstance(model, RenderCVModel)
|
|
|
|
def test_with_all_overlays(self, create_yaml_file_fixture):
|
|
main_input = {
|
|
"cv": {"name": "John Doe"},
|
|
"design": {"theme": "classic"},
|
|
"locale": {"language": "english"},
|
|
}
|
|
design_overlay = {"design": {"theme": "sb2nov"}}
|
|
locale_overlay = {"locale": {"language": "turkish"}}
|
|
settings_overlay = {
|
|
"settings": {"render_command": {"markdown_path": "custom.md"}}
|
|
}
|
|
|
|
main_file = create_yaml_file_fixture("main.yaml", main_input)
|
|
design_file = create_yaml_file_fixture("design.yaml", design_overlay)
|
|
locale_file = create_yaml_file_fixture("locale.yaml", locale_overlay)
|
|
settings_file = create_yaml_file_fixture("settings.yaml", settings_overlay)
|
|
|
|
_, model = build_rendercv_dictionary_and_model(
|
|
main_file,
|
|
design_file_path_or_contents=design_file,
|
|
locale_file_path_or_contents=locale_file,
|
|
settings_file_path_or_contents=settings_file,
|
|
)
|
|
|
|
assert isinstance(model, RenderCVModel)
|
|
assert model.cv.name == "John Doe"
|
|
assert model.design.theme == "sb2nov"
|
|
assert model._input_file_path == main_file
|
|
|
|
@pytest.mark.parametrize(
|
|
"overrides",
|
|
[
|
|
{"pdf_path": "custom.pdf"},
|
|
{"typst_path": "custom.typ", "markdown_path": "custom.md"},
|
|
{"dont_generate_html": True, "dont_generate_pdf": True},
|
|
{
|
|
"pdf_path": "all.pdf",
|
|
"typst_path": "all.typ",
|
|
"dont_generate_png": True,
|
|
},
|
|
],
|
|
)
|
|
def test_with_render_command_overrides(self, minimal_input_dict, overrides):
|
|
main_yaml = dictionary_to_yaml(minimal_input_dict)
|
|
|
|
_, model = build_rendercv_dictionary_and_model(main_yaml, **overrides)
|
|
|
|
assert isinstance(model, RenderCVModel)
|
|
for key, value in overrides.items():
|
|
model_value = getattr(model.settings.render_command, key)
|
|
if isinstance(value, str) and key.endswith("_path"):
|
|
assert model_value == pathlib.Path(value).resolve()
|
|
else:
|
|
assert model_value == value
|
|
|
|
def test_combined_overlays_and_overrides(
|
|
self, minimal_input_dict, create_yaml_file_fixture
|
|
):
|
|
locale_overlay = {"locale": {"language": "turkish"}}
|
|
|
|
main_file = create_yaml_file_fixture("main.yaml", minimal_input_dict)
|
|
locale_file = create_yaml_file_fixture("locale.yaml", locale_overlay)
|
|
|
|
_, model = build_rendercv_dictionary_and_model(
|
|
main_file,
|
|
locale_file_path_or_contents=locale_file,
|
|
pdf_path="output.pdf",
|
|
dont_generate_html=True,
|
|
)
|
|
|
|
assert isinstance(model, RenderCVModel)
|
|
assert model._input_file_path == main_file
|
|
assert model.settings.render_command.pdf_path.name == "output.pdf"
|
|
assert model.settings.render_command.dont_generate_html is True
|
|
|
|
@pytest.mark.parametrize(
|
|
("overrides", "expected_name"),
|
|
[
|
|
({"cv.name": "Jane Doe"}, "Jane Doe"),
|
|
({"cv.name": "Bob Smith"}, "Bob Smith"),
|
|
],
|
|
)
|
|
def test_with_overrides_parameter(
|
|
self, minimal_input_dict, overrides, expected_name
|
|
):
|
|
yaml_input = dictionary_to_yaml(minimal_input_dict)
|
|
|
|
_, model = build_rendercv_dictionary_and_model(yaml_input, overrides=overrides)
|
|
|
|
assert isinstance(model, RenderCVModel)
|
|
assert model.cv.name == expected_name
|
|
|
|
def test_with_fixture_input_file(self, input_file_path):
|
|
_, model = build_rendercv_dictionary_and_model(input_file_path)
|
|
assert isinstance(model, RenderCVModel)
|
|
|
|
def test_with_yaml_string_using_ruamel(self):
|
|
input_dictionary = {
|
|
"cv": {"name": "John Doe"},
|
|
"design": {"theme": "classic"},
|
|
}
|
|
|
|
yaml_object = ruamel.yaml.YAML()
|
|
yaml_object.width = 60
|
|
yaml_object.indent(mapping=2, sequence=4, offset=2)
|
|
with io.StringIO() as string_stream:
|
|
yaml_object.dump(input_dictionary, string_stream)
|
|
yaml_string = string_stream.getvalue()
|
|
|
|
_, model = build_rendercv_dictionary_and_model(yaml_string)
|
|
assert isinstance(model, RenderCVModel)
|
|
|
|
def test_invalid_file_extension_raises_error(self, tmp_path):
|
|
invalid_file_path = tmp_path / "invalid.extension"
|
|
invalid_file_path.write_text("dummy content", encoding="utf-8")
|
|
|
|
with pytest.raises(RenderCVUserError):
|
|
build_rendercv_dictionary_and_model(invalid_file_path)
|
|
|
|
def test_nonexistent_file_raises_error(self, tmp_path):
|
|
non_existent_file_path = tmp_path / "non_existent_file.yaml"
|
|
|
|
with pytest.raises(RenderCVUserError):
|
|
build_rendercv_dictionary_and_model(non_existent_file_path)
|