1
0

vault backup: 2025-12-26 02:09:22
All checks were successful
Deploy Quartz site to GitHub Pages / build (push) Successful in 2m29s

This commit is contained in:
2025-12-26 02:09:22 +01:00
parent 3fddadfe50
commit 50366b9b9c
288 changed files with 58893 additions and 750 deletions

View File

View File

@@ -0,0 +1,261 @@
import pytest
import time
from pathlib import Path
from quiz.utils.importer import parse_markdown_question, import_question_file, ImportStats
from quiz.models import Question, Option
@pytest.mark.django_db
@pytest.mark.import_tests
class TestMarkdownParsing:
"""Test parsing of various Obsidian markdown question formats"""
def test_parse_single_choice_question(self):
"""Test parsing standard single choice question (SCQ)"""
content = """---
tags: [ah2, provfråga, frågetyp/scq, anatomi]
date: 2022-01-15
---
What is the correct answer?
**Välj ett alternativ:**
- A: Wrong answer
- B: Correct answer
- C: Another wrong
```spoiler-block:
B
```
"""
is_question, data = parse_markdown_question(Path("test.md"), content)
assert is_question is True
assert data['text'] == 'What is the correct answer?'
assert data['correct_answer'] == 'B'
assert data['has_answer'] is True
assert data['question_type'] == 'scq'
assert len(data['options']) == 3
assert data['options'][0] == ('A', 'Wrong answer')
assert data['options'][1] == ('B', 'Correct answer')
def test_parse_multiple_choice_question(self):
"""Test parsing multiple choice question (MCQ) with 'och' separator"""
content = """---
tags: [ah2, provfråga, frågetyp/mcq, cerebrum]
date: 2022-01-15
---
Vilka av följande räknas till storhjärnans basala kärnor?
**Välj två alternativ**
- A: Putamen
- B: Nucleus Ruber
- C: Substantia nigra
- D: Nucleus caudatus
```spoiler-block:
A och D
```
"""
is_question, data = parse_markdown_question(Path("test.md"), content)
assert is_question is True
assert 'Vilka av följande' in data['text']
assert data['correct_answer'] == 'A,D' # Normalized to comma-separated
assert data['has_answer'] is True
assert data['question_type'] == 'mcq'
assert len(data['options']) == 4
def test_parse_multiple_choice_comma_separated(self):
"""Test MCQ with comma-separated answer"""
content = """---
tags: [frågetyp/mcq]
---
Select two options:
- A: Option A
- B: Option B
- C: Option C
- D: Option D
```spoiler-block:
B, C
```
"""
is_question, data = parse_markdown_question(Path("test.md"), content)
assert data['correct_answer'] == 'B,C'
assert data['has_answer'] is True
def test_parse_matching_question(self):
"""Test parsing matching question (DND/Matching)"""
content = """---
tags: [ah2, provfråga, frågetyp/matching, anatomi, öra]
date: 2023-05-31
---
**Matcha rätt funktion med rätt lob:**
(1p för alla rätt, inga delpoäng)
- Smak
- Syn
- Somatosensorik
- Motorik
- Hörsel
**Alternativ:**
- Lobus frontalis
- Lobus Insularis
- Lobus temporalis
- Lobus parietalis
- Lobus occipitalis
```spoiler-block:
Smak: Lobus Insularis
Syn: Lobus occipitalis
Somatosensorik: Lobus parietalis
Motorik: Lobus frontalis
Hörsel: Lobus temporalis
```
"""
is_matching, data = parse_markdown_question(Path("test.md"), content)
assert is_matching is True
assert data['question_type'] == 'matching'
assert data['has_answer'] is True
assert len(data['left_items']) == 5
assert len(data['top_items']) == 5
assert len(data['correct_pairs']) == 5
def test_parse_textalternativ_question(self):
"""Test text alternative question type"""
content = """---
tags: [frågetyp/textalternativ, öga, anatomi]
---
Svara på följande frågor:
a) Bokstaven B sitter i en lob, vilken?
- Lobus temporalis
- Lobus frontalis
- Lobus parietalis
b) Vilket funktionellt centra återfinns där?
- Syncentrum
- Motorcentrum
- Somatosensoriskt centrum
```spoiler-block:
a) Lobus parietalis
b) Somatosensoriskt centrum
```
"""
is_question, data = parse_markdown_question(Path("test.md"), content)
assert is_question is True
assert data['question_type'] == 'textalternativ'
assert data['has_answer'] is True
assert 'Lobus parietalis' in data['correct_answer']
assert 'Somatosensoriskt centrum' in data['correct_answer']
def test_parse_textfalt_question(self):
"""Test text field (fill-in) question type"""
content = """---
tags: [frågetyp/textfält, öga]
---
**Fyll i rätt siffra!**
a) Vilken siffra pekar på gula fläcken?
b) Vilken siffra pekar på choroidea?
```spoiler-block:
a) 7
b) 6
```
"""
is_question, data = parse_markdown_question(Path("test.md"), content)
assert is_question is True
assert data['question_type'] == 'textfält'
assert data['has_answer'] is True
assert '7' in data['correct_answer']
assert '6' in data['correct_answer']
@pytest.mark.django_db
@pytest.mark.import_tests
class TestQuestionImport:
"""Test actual import of questions to database"""
def test_import_single_question(self, tmp_path):
"""Test importing a single question file"""
question_file = tmp_path / "question1.md"
question_file.write_text("""---
tags: [frågetyp/scq]
---
Test question?
- A: Correct
- B: Wrong
```spoiler-block:
A
```
""")
stats = ImportStats()
result = import_question_file(question_file, tmp_path, stats, force=True)
assert result in ['imported', 'updated']
assert stats.questions_with_answers == 1
# Verify in database
question = Question.objects.get(text='Test question?')
assert question.correct_answer == 'A'
assert question.options.count() == 2
def test_mtime_tracking(self, tmp_path):
"""Test that file modification time is tracked"""
question_file = tmp_path / "question4.md"
question_file.write_text("""---
tags: [frågetyp/scq]
---
What is the correct answer?
```spoiler-block:
A
```
""")
stats = ImportStats()
import_question_file(question_file, tmp_path, stats, force=True)
question = Question.objects.get(text='What is the correct answer?')
assert question.file_mtime == question_file.stat().st_mtime
def test_update_existing_question(self, tmp_path):
"""Test updating an existing question"""
question_file = tmp_path / "question5.md"
# Initial import
question_file.write_text("""---
tags: [frågetyp/scq]
---
Question to update?
```spoiler-block:
A
```
""")
import_question_file(question_file, tmp_path, ImportStats(), force=True)
# Update the file
time.sleep(0.1)
question_file.write_text("""---
tags: [frågetyp/scq]
---
Question to update?
```spoiler-block:
B
```
""")
stats = ImportStats()
result = import_question_file(question_file, tmp_path, stats, force=False)
assert result == 'updated'
assert Question.objects.get(text='Question to update?').correct_answer == 'B'

View File

@@ -0,0 +1,537 @@
"""
Comprehensive test suite for the question_parser module.
This test suite uses pytest's parametrize decorator to test multiple scenarios
with minimal code duplication. It covers:
1. Node class:
- Initialization with different token types
- Attribute handling
- Children node processing
- String representation (__repr__)
- Text extraction from nested structures
2. parse_question function:
- Metadata parsing (tags, dates, etc.)
- Raw content extraction
- Different question types (MCQ, SCQ, text field, matching)
- Questions with images
- Edge cases (empty content, missing frontmatter)
- Document structure preservation
3. ParsedQuestion dataclass:
- Default values
- Initialization with custom values
4. Real exam questions:
- Parsing actual exam questions from the content directory
- Validation of all short-named question files
Test execution:
pytest tests/test_question_parser.py -v # Verbose output
pytest tests/test_question_parser.py -k "mcq" # Run only MCQ tests
pytest tests/test_question_parser.py --collect-only # List all tests
"""
import pathlib
import tempfile
import pytest
from quiz.utils.question_parser import Node, ParsedQuestion, parse_question
@pytest.fixture
def temp_dir():
"""Create a temporary directory for test files"""
with tempfile.TemporaryDirectory() as tmpdir:
yield pathlib.Path(tmpdir)
@pytest.fixture
def create_question_file(temp_dir):
"""Factory fixture to create question files"""
def _create_file(filename: str, content: str) -> pathlib.Path:
file_path = temp_dir / filename
file_path.write_text(content, encoding="utf-8")
return file_path
return _create_file
class TestNode:
"""Test the Node class"""
@pytest.mark.parametrize("token,expected_type,expected_raw", [
({"type": "paragraph"}, "paragraph", ""),
({"type": "heading", "raw": "Test Heading"}, "heading", "Test Heading"),
({"type": "text", "raw": "Some text"}, "text", "Some text"),
({"type": "list"}, "list", ""),
])
def test_node_initialization(self, token, expected_type, expected_raw):
"""Test Node initialization with different token types"""
node = Node(token)
assert node.type == expected_type
assert node.raw == expected_raw
@pytest.mark.parametrize("token,expected_attrs", [
({"type": "block_code", "attrs": {"info": "spoiler-block:"}}, {"info": "spoiler-block:"}),
({"type": "paragraph"}, {}),
({"type": "heading", "attrs": {"level": 2}}, {"level": 2}),
])
def test_node_attributes(self, token, expected_attrs):
"""Test Node attributes handling"""
node = Node(token)
assert node.attrs == expected_attrs
def test_node_children(self):
"""Test Node children handling"""
token = {
"type": "paragraph",
"children": [
{"type": "text", "raw": "Hello "},
{"type": "text", "raw": "World"},
]
}
node = Node(token)
assert len(node.children) == 2
assert node.children[0].type == "text"
assert node.children[0].raw == "Hello "
assert node.children[1].type == "text"
assert node.children[1].raw == "World"
@pytest.mark.parametrize("token,expected_repr_contains", [
({"type": "text", "raw": "test"}, "Text(raw='test')"),
({"type": "paragraph"}, "Paragraph()"),
({"type": "block_code", "attrs": {"info": "python"}}, "BlockCode(attrs={'info': 'python'})"),
])
def test_node_repr(self, token, expected_repr_contains):
"""Test Node __repr__ method"""
node = Node(token)
assert repr(node) == expected_repr_contains
@pytest.mark.parametrize("token,expected_text", [
({"type": "text", "raw": "Simple text"}, "Simple text"),
(
{
"type": "paragraph",
"children": [
{"type": "text", "raw": "Hello "},
{"type": "text", "raw": "World"},
]
},
"Hello World"
),
(
{
"type": "paragraph",
"children": [
{"type": "text", "raw": "Nested "},
{
"type": "strong",
"children": [{"type": "text", "raw": "bold"}]
},
{"type": "text", "raw": " text"},
]
},
"Nested bold text"
),
])
def test_node_text_property(self, token, expected_text):
"""Test Node text property extraction"""
node = Node(token)
assert node.text == expected_text
class TestParseQuestion:
"""Test the parse_question function"""
@pytest.mark.parametrize("content,expected_tags", [
(
"""---
tags: [ah2, provfråga, frågetyp/mcq]
date: 2022-01-15
---
Question content""",
["ah2", "provfråga", "frågetyp/mcq"]
),
(
"""---
tags:
- ah2
- provfråga
- frågetyp/scq
date: 2023-05-31
---
Question content""",
["ah2", "provfråga", "frågetyp/scq"]
),
])
def test_parse_metadata_tags(self, create_question_file, content, expected_tags):
"""Test parsing of metadata tags in different formats"""
file_path = create_question_file("test.md", content)
question = parse_question(file_path)
assert question.metadata["tags"] == expected_tags
@pytest.mark.parametrize("content,expected_date", [
(
"""---
tags: [ah2]
date: 2022-01-15
---
Content""",
"2022-01-15"
),
(
"""---
tags: [ah2]
date: 2023-05-31
---
Content""",
"2023-05-31"
),
])
def test_parse_metadata_date(self, create_question_file, content, expected_date):
"""Test parsing of metadata date"""
file_path = create_question_file("test.md", content)
question = parse_question(file_path)
assert str(question.metadata["date"]) == expected_date
@pytest.mark.parametrize("content,expected_raw", [
(
"""---
tags: [ah2]
---
Simple question""",
"Simple question"
),
(
"""---
tags: [ah2]
---
Question with **bold** text""",
"Question with **bold** text"
),
])
def test_parse_raw_content(self, create_question_file, content, expected_raw):
"""Test parsing of raw content"""
file_path = create_question_file("test.md", content)
question = parse_question(file_path)
assert question.raw_content.strip() == expected_raw
def test_parse_mcq_question(self, create_question_file):
"""Test parsing a complete MCQ question"""
content = """---
tags: [ah2, provfråga, frågetyp/mcq, cerebrum]
date: 2022-01-15
---
Vilka av följande räknas till storhjärnans basala kärnor?
**Välj två alternativ**
- A: Putamen
- B: Nucleus Ruber
- C: Substantia nigra
- D: Nucleus caudatus
```spoiler-block:
A och D
```
"""
file_path = create_question_file("mcq.md", content)
question = parse_question(file_path)
assert question.metadata["tags"] == ["ah2", "provfråga", "frågetyp/mcq", "cerebrum"]
assert len(question.nodes) > 0
# Find paragraph nodes
paragraphs = [n for n in question.nodes if n.type == "paragraph"]
assert len(paragraphs) > 0
# Find list nodes
lists = [n for n in question.nodes if n.type == "list"]
assert len(lists) > 0
# Find spoiler block
code_blocks = [n for n in question.nodes if n.type == "block_code"]
assert len(code_blocks) > 0
spoiler = code_blocks[0]
assert spoiler.attrs.get("info") == "spoiler-block:"
assert "A och D" in spoiler.raw
def test_parse_scq_question(self, create_question_file):
"""Test parsing a single choice question"""
content = """---
tags: [ah2, provfråga, frågetyp/scq, histologi]
date: 2022-06-01
---
Vilken del av CNS syns i bild?
- A: Cerebellum
- B: Diencephalon
- C: Medulla spinalis
- D: Cerebrum
- E: Pons
```spoiler-block:
A
```
"""
file_path = create_question_file("scq.md", content)
question = parse_question(file_path)
assert "frågetyp/scq" in question.metadata["tags"]
lists = [n for n in question.nodes if n.type == "list"]
assert len(lists) > 0
def test_parse_text_field_question(self, create_question_file):
"""Test parsing a text field question"""
content = """---
tags: [ah2, provfråga, frågetyp/textfält, öga, anatomi]
date: 2022-01-15
---
![[image-2.png|301x248]]
**Fyll i rätt siffra!**
(0.5p per rätt svar, inga avdrag för fel svar):
a) Vilken siffra pekar på gula fläcken?
b) Vilken siffra pekar på choroidea?
```spoiler-block:
a) 7
b) 6
```
"""
file_path = create_question_file("textfield.md", content)
question = parse_question(file_path)
assert "frågetyp/textfält" in question.metadata["tags"]
assert len(question.nodes) > 0
def test_parse_matching_question(self, create_question_file):
"""Test parsing a matching question"""
content = """---
tags: [ah2, provfråga, frågetyp/matching, histologi]
date: 2023-05-31
---
Vilka av följande stödjeceller finns i CNS? Markera JA eller NEJ för varje angiven celltyp:
(1p för alla rätt, inga delpoäng)
- a) oligodendrocyter
- b) Astrocyter
- c) satellitceller
- d) ependymceller
- e) mikroglia
- f) Schwannceller
- JA, finn i CNS
- NEJ, finns inte i CNS
```spoiler-block:
a) JA, finn i CNS
b) JA, finn i CNS
c) NEJ, finns inte i CNS
d) JA, finn i CNS
e) JA, finn i CNS
f) NEJ, finns inte i CNS
```
"""
file_path = create_question_file("matching.md", content)
question = parse_question(file_path)
assert "frågetyp/matching" in question.metadata["tags"]
lists = [n for n in question.nodes if n.type == "list"]
assert len(lists) > 0
def test_parse_question_with_image(self, create_question_file):
"""Test parsing a question with embedded images"""
content = """---
tags: [ah2, provfråga, frågetyp/textfält, öra, anatomi, bild]
date: 2022-01-15
---
![[image-4.png|292x316]]
**Fyll i rätt siffra !**
(0.5p per rätt svar, inga avdrag för fel svar):
a) Vilken siffra pekar på incus? (1..19)
b) Vilken siffra pekar på tuba auditiva? (1..19)
```spoiler-block:
a) 7
b) 18
```
"""
file_path = create_question_file("image_q.md", content)
question = parse_question(file_path)
assert "bild" in question.metadata["tags"]
assert "![[image-4.png" in question.raw_content
embed = question.nodes[0].children[0]
assert embed.type == "embed"
assert embed.attrs == {
"filename": "image-4.png",
"width": 292,
"height": 316
}
@pytest.mark.parametrize("invalid_content", [
"", # Empty content
"No frontmatter", # No frontmatter
"---\n---\n", # Empty frontmatter
])
def test_parse_edge_cases(self, create_question_file, invalid_content):
"""Test parsing edge cases"""
file_path = create_question_file("edge.md", invalid_content)
question = parse_question(file_path)
assert isinstance(question, ParsedQuestion)
def test_parse_question_preserves_structure(self, create_question_file):
"""Test that parsing preserves the document structure"""
content = """---
tags: [ah2]
---
# Heading
Paragraph text
- List item 1
- List item 2
```spoiler-block:
Answer
```
"""
file_path = create_question_file("structure.md", content)
question = parse_question(file_path)
node_types = [n.type for n in question.nodes]
assert "heading" in node_types
assert "paragraph" in node_types
assert "list" in node_types
assert "block_code" in node_types
class TestParsedQuestionDataclass:
"""Test the ParsedQuestion dataclass"""
def test_parsed_question_defaults(self):
"""Test ParsedQuestion default values"""
question = ParsedQuestion()
assert question.metadata == {}
assert question.raw_content == ""
assert question.nodes == []
def test_parsed_question_initialization(self):
"""Test ParsedQuestion initialization with values"""
metadata = {"tags": ["test"], "date": "2022-01-15"}
content = "Test content"
nodes = [Node({"type": "paragraph"})]
question = ParsedQuestion(
metadata=metadata,
raw_content=content,
nodes=nodes
)
assert question.metadata == metadata
assert question.raw_content == content
assert question.nodes == nodes
class TestRealQuestions:
"""Test parsing real questions from the exam files"""
@pytest.fixture
def exam_dir(self):
"""Get the real exam directory"""
root = pathlib.Path(__file__).parent.parent.parent
exam_path = root / "content" / "Anatomi & Histologi 2" / "Gamla tentor"
if exam_path.exists():
return exam_path
pytest.skip("Exam directory not found")
@pytest.mark.parametrize("exam_date,question_num", [
("2022-01-15", "1"),
("2022-01-15", "2"),
("2022-01-15", "3"),
("2022-01-15", "4"),
("2022-06-01", "8"),
])
def test_parse_real_exam_questions(self, exam_dir, exam_date, question_num):
"""Test parsing real exam questions"""
file_path = exam_dir / exam_date / f"{question_num}.md"
if not file_path.exists():
pytest.skip(f"Question file {file_path} not found")
question = parse_question(file_path)
# Verify metadata exists and has required fields
assert "tags" in question.metadata
assert isinstance(question.metadata["tags"], list)
assert "ah2" in question.metadata["tags"]
assert "provfråga" in question.metadata["tags"]
# Verify content was parsed
assert len(question.raw_content) > 0
assert len(question.nodes) > 0
def test_parse_all_short_named_questions(self, exam_dir):
"""Test parsing all questions with short filenames (1-2 chars)"""
questions_found = 0
for file in sorted(exam_dir.glob("*/*.md")):
if len(file.stem) <= 2 and file.stem.isdigit():
question = parse_question(file)
assert isinstance(question, ParsedQuestion)
assert "tags" in question.metadata
questions_found += 1
# Ensure we found at least some questions
assert questions_found > 0, "No exam questions found to test"
class TestNodeTextExtraction:
"""Test text extraction from complex node structures"""
@pytest.mark.parametrize("token,expected_text", [
# Simple text
({"type": "text", "raw": "Hello"}, "Hello"),
# Paragraph with multiple text children
(
{
"type": "paragraph",
"children": [
{"type": "text", "raw": "A "},
{"type": "text", "raw": "B "},
{"type": "text", "raw": "C"},
]
},
"A B C"
),
# Nested formatting
(
{
"type": "paragraph",
"children": [
{"type": "text", "raw": "Normal "},
{
"type": "emphasis",
"children": [{"type": "text", "raw": "italic"}]
},
{"type": "text", "raw": " "},
{
"type": "strong",
"children": [{"type": "text", "raw": "bold"}]
},
]
},
"Normal italic bold"
),
# Empty node
({"type": "paragraph", "children": []}, ""),
])
def test_complex_text_extraction(self, token, expected_text):
"""Test text extraction from complex nested structures"""
node = Node(token)
assert node.text == expected_text

View File

@@ -0,0 +1,187 @@
import datetime
from quiz.utils.unified_parser import UnifiedParser, QuestionType
def test_parse_mcq_question():
content = """---
tags: [frågetyp/mcq, ah2]
date: 2024-03-21
---
Question?
- A: Yes
- B: No
- C: Maybe
- D: Never
```spoiler-block:
A och D
```"""
data = UnifiedParser(content).parse()
assert data.type == QuestionType.MCQ
assert data.question == "Question?"
assert data.answer == ["A", "D"]
assert data.num_questions == 1
assert data.is_complete is True
assert data.options == ["A: Yes", "B: No", "C: Maybe", "D: Never"]
assert data.metadata == {"tags": ["frågetyp/mcq", "ah2"], "date": datetime.date(2024, 3, 21)}
assert not data.sub_questions
def test_parse_scq_question():
content = """---
tags: [frågetyp/scq]
---
Pick one:
- A: One
- B: Two
```spoiler-block:
B
```"""
data = UnifiedParser(content).parse()
assert data.type == QuestionType.SCQ
assert data.question == "Pick one:"
assert data.answer == "B"
assert data.num_questions == 1
assert data.is_complete is True
assert data.options == ["A: One", "B: Two"]
assert not data.sub_questions
def test_parse_textfält_question():
content = """---
tags: [frågetyp/textfält]
---
Name these:
a) Part 1
b) Part 2
```spoiler-block:
a) Left
b) Right
```"""
data = UnifiedParser(content).parse()
assert data.type == QuestionType.TEXTFÄLT
assert data.question == "Name these:"
assert data.answer == ["a) Left", "b) Right"]
assert data.num_questions == 2
assert len(data.sub_questions) == 2
assert data.sub_questions[0].id == "a"
assert data.sub_questions[0].text == "Part 1"
assert data.sub_questions[0].answer == "a) Left"
assert data.sub_questions[0].options is None
def test_parse_matching_question():
content = """---
tags: [frågetyp/matching]
---
Match:
- 1
- 2
- A
- B
```spoiler-block:
1: A
2: B
```"""
data = UnifiedParser(content).parse()
assert data.type == QuestionType.MATCHING
assert data.question == "Match:"
assert data.answer == [["1", "A"], ["2", "B"]]
assert data.num_questions == 1
assert data.options == ["1", "2", "A", "B"]
assert not data.sub_questions
def test_parse_question_with_image_and_instruction():
content = """---
tags: [frågetyp/scq]
---
**Välj ett alternativ:**
![[brain.png|300]]
What is this?
- A: Brain
- B: Heart
```spoiler-block:
A
```"""
data = UnifiedParser(content).parse()
assert data.type == QuestionType.SCQ
assert data.question == "What is this?"
assert data.instruction == "Välj ett alternativ:"
assert data.image == "![[brain.png]]"
assert data.is_complete is True
def test_parse_field_question_with_ranges():
content = """---
tags: [frågetyp/sifferfält]
---
Identify the structures:
a) Arachnoidea? (1..10)
(0.5 p)
b) Cortex cerebri (1..10)
(0.5 p)
```spoiler-block:
a) 7
b) 3
```"""
data = UnifiedParser(content).parse()
assert data.type == QuestionType.SIFFERFÄLT
assert data.num_questions == 2
assert len(data.sub_questions) == 2
# Part A
assert data.sub_questions[0].id == "a"
assert data.sub_questions[0].text == "Arachnoidea?"
assert data.sub_questions[0].options == [str(x) for x in range(1, 11)]
assert data.sub_questions[0].answer == "a) 7"
# Part B
assert data.sub_questions[1].id == "b"
assert data.sub_questions[1].text == "Cortex cerebri"
assert data.sub_questions[1].options == [str(x) for x in range(1, 11)]
assert data.sub_questions[1].answer == "b) 3"
def test_parse_field_question_with_list_options():
content = """---
tags: [frågetyp/sifferfält]
---
a) First (A, B, C)
b) Second (1, 2, 3)
```spoiler-block:
a) A
b) 2
```"""
data = UnifiedParser(content).parse()
assert data.sub_questions[0].options == ["A", "B", "C"]
assert data.sub_questions[1].options == ["1", "2", "3"]
def test_parse_hotspot_question():
content = """---
tags: [frågetyp/hotspot]
---
Klicka på hippocampus!
```spoiler-block:
![[brain_atlas.png]]
Det här är hippocampus.
```"""
data = UnifiedParser(content).parse()
assert data.type == QuestionType.HOTSPOT
assert data.answer == "Det här är hippocampus."
assert data.answer_image == "![[brain_atlas.png]]"
assert data.is_complete is True
def test_completeness_missing_sub_questions():
content = """---
tags: [frågetyp/textfält]
---
a) one
b) two
```spoiler-block:
a) found
```"""
data = UnifiedParser(content).parse()
assert data.num_questions == 2
assert data.is_complete is False
assert len(data.sub_questions) == 2
assert data.sub_questions[0].answer == "a) found"
assert data.sub_questions[1].answer is None