extends SceneTree var failures := 0 func _initialize() -> void: _test_reference_normalization() _test_reference_url_filter() _test_correct_feedback_does_not_auto_advance() _test_correct_review_locks_answer_interactions() _test_eliminated_choice_uses_rich_text_strikeout() _test_explanation_highlighting_has_no_status_heading() _test_collapsed_followup_review_expands() quit(1 if failures > 0 else 0) func _test_reference_normalization() -> void: var service = load("res://scripts/autoload/question_bank_service.gd").new() var references: Array = service._normalize_references([{ "Title": "Clinical guideline", "Publisher": "Example Society", "Url": "https://example.org/guideline", "Supports": "Recommended management", "AccessedOn": "2026-07-14", "Reviewer": "reviewer" }]) _assert(references.size() == 1, "a structured reference should be retained") _assert(references[0].get("title", "") == "Clinical guideline", "reference title should be normalized") _assert(references[0].get("accessed_on", "") == "2026-07-14", "reference access date should be normalized") service.free() func _test_reference_url_filter() -> void: var player = load("res://scripts/ui/question_player.gd").new() _assert(player._is_safe_reference_url("https://example.org/source"), "HTTPS references should be accepted") _assert(not player._is_safe_reference_url("http://example.org/source"), "HTTP references should be rejected") _assert(not player._is_safe_reference_url("javascript:alert(1)"), "script URLs should be rejected") player.references_container = VBoxContainer.new() player._show_references([ {"title": "Clinical guideline", "publisher": "Example Society", "url": "https://example.org/source"}, {"title": "Unsafe", "publisher": "Example", "url": "http://example.org/source"} ]) _assert(player.references_container.visible, "valid references should show the references section") _assert(player.references_container.get_child_count() == 2, "the section should contain a heading and one safe link") var link := player.references_container.get_child(1) as LinkButton _assert(link != null and link.text == "Clinical guideline - Example Society", "reference link text should identify its source") player.references_container.free() player.free() func _test_correct_feedback_does_not_auto_advance() -> void: var player = load("res://scripts/ui/question_player.gd").new() player.next_button = Button.new() player.skip_button = Button.new() player.feedback_fog = ColorRect.new() player.pending_correct_advance = false player._trigger_correct_feedback() _assert(not player.pending_correct_advance, "correct feedback must not schedule automatic advancement") player.next_button.free() player.skip_button.free() player.feedback_fog.free() player.free() func _test_correct_review_locks_answer_interactions() -> void: var player = load("res://scripts/ui/question_player.gd").new() player.current_answer = {"is_correct": true} player.selected_choice = "A" player.presented_choices = [{"id": "A", "text": "First"}, {"id": "B", "text": "Second"}] player._select_choice(1) _assert(player.selected_choice == "A", "correct review should reject answer selection changes") var right_click := InputEventMouseButton.new() right_click.button_index = MOUSE_BUTTON_RIGHT right_click.pressed = true player._choice_gui_input(right_click, 1) _assert(player.eliminated.is_empty(), "correct review should reject answer strike-through changes") player.free() func _test_eliminated_choice_uses_rich_text_strikeout() -> void: var player = load("res://scripts/ui/question_player.gd").new() player.choice_buttons.append(Button.new()) player.choice_percent_labels.append(Label.new()) var strike_label := RichTextLabel.new() strike_label.bbcode_enabled = true player.choice_top_labels.append(strike_label) player.choice_followup_panels.append(PanelContainer.new()) player.eliminated = ["A"] player.presented_choices = [{"id": "A", "text": "Observation only"}] player._set_choice_button_text(0, player.presented_choices[0]) _assert(player.choice_buttons[0].text == "", "eliminated choice should not use combining characters in button text") _assert(strike_label.text == "[s]A. Observation only[/s]", "eliminated choice should use native rich-text strikeout") _assert(not "[eliminated]" in strike_label.text, "eliminated choice should not include a redundant label") player.choice_buttons[0].free() player.choice_percent_labels[0].free() strike_label.free() player.choice_followup_panels[0].free() player.free() func _test_explanation_highlighting_has_no_status_heading() -> void: var player = load("res://scripts/ui/question_player.gd").new() player.explanation_label = RichTextLabel.new() player.explanation_label.bbcode_enabled = true player.explanation_plain_text = "The key finding supports observation." player.explanation_highlights[""] = [{"start": 4, "end": 15}] player._render_explanation() _assert(not player.explanation_label.text.begins_with("[b]Correct"), "explanation should not repeat the inferred correct status") _assert("[bgcolor=yellow]" in player.explanation_label.text, "explanation text should support highlighting") player.explanation_label.free() player.free() func _test_collapsed_followup_review_expands() -> void: var player = load("res://scripts/ui/question_player.gd").new() var panel := PanelContainer.new() var content := VBoxContainer.new() panel.add_child(content) player.choice_buttons.append(Button.new()) player.choice_percent_labels.append(Label.new()) player.choice_top_labels.append(RichTextLabel.new()) player.choice_followup_panels.append(panel) player.choice_followup_contents.append(content) player.next_button = Button.new() player.presented_choices = [{"id": "B", "text": "Wrong answer"}] player.followup_states["B"] = { "choice_id": "B", "choice_text": "Wrong answer", "prompt": "Why?", "selected_option_text": "Finding", "was_correct": false, "teaching_point": "Review the finding.", "resolved": true, "collapsed": true } player._render_followup_state("B") var review_button := content.get_child(0) as Button _assert(review_button != null, "collapsed follow-up should render a review button") review_button.pressed.emit() _assert(not bool(player.followup_states["B"].get("collapsed", true)), "review button should expand the resolved follow-up") player.choice_buttons[0].free() player.choice_percent_labels[0].free() player.choice_top_labels[0].free() player.next_button.free() panel.free() player.free() func _assert(condition: bool, message: String) -> void: if condition: return failures += 1 push_error(message)