Files
step3/tests/flashcard_session_navigation_test.gd
hz e8d615985b Reveal the flashcard answer by tapping the card, not a separate button
Matches the mockup: the "Show Answer" button is replaced by an in-card
"Tap card to reveal answer" hint, and clicking anywhere on the card
reveals it (guarded the same way Space already was - only while at the
live card and not yet revealed).
2026-07-22 17:21:23 -05:00

174 lines
7.9 KiB
GDScript

extends SceneTree
# Exercises the study screen's session-history navigation (skip / Previous / Next / undo-in-
# history) and the layout/footer-state fixes added alongside it. The screen is added to the
# tree (mirrors flashcard_theme_responsive_test.gd) so _input()'s tree-visibility guard behaves
# realistically; every FlashcardService call this drives (request_next_card/bury_cards/
# suspend_cards) short-circuits before touching the network because AuthState reports no
# logged-in session in this headless context, matching the "no network" style of the other
# flashcard tests.
var failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var host := Control.new()
host.size = Vector2(1280, 800)
root.add_child(host)
var Study := load("res://scripts/ui/flashcards_study.gd")
var screen = Study.new()
host.add_child(screen)
await process_frame
# ---------- Layout: side panels must hug their content, not stretch full height ----------
_assert(screen.deck_panel.size_flags_vertical == Control.SIZE_SHRINK_BEGIN,
"the current-deck panel should not stretch to the row's full height")
_assert(screen.stats_panel.size_flags_vertical == Control.SIZE_SHRINK_BEGIN,
"the spaced-repetition panel should not stretch to the row's full height")
# ---------- Footer button states at session start (no history, no live card) ----------
_assert(screen.previous_button.disabled, "Previous should be disabled with no history to step back into")
_assert(screen.next_button.disabled, "Next should be disabled with no live card to skip and no history to page through")
screen.current_note = {"type": "basic", "prompt": "P3(live)", "answer": "A3", "extra": ""}
screen.current_card = {"id": "card-3", "variant": 0, "revision": 1}
screen.rating_options = []
screen.due_remaining = 5
screen.new_remaining = 3
screen.session_total = 10
screen._update_footer_button_states()
_assert(not screen.next_button.disabled, "Next should enable once a live card is present, to allow skipping it")
_assert(screen.previous_button.disabled, "Previous should still be disabled before any history exists")
# ---------- Options menu opens even with no live card, and disables Bury/Suspend ----------
screen.current_note = {}
screen.current_card = {}
screen._toggle_options_menu()
_assert(screen.options_menu.visible, "Options should open even with no live card (e.g. deck exhausted)")
_assert(screen.options_bury_button.disabled and screen.options_suspend_button.disabled,
"Bury/Suspend should disable when there is no live card to act on")
screen._close_options_menu()
screen.current_note = {"type": "basic", "prompt": "P3(live)", "answer": "A3", "extra": ""}
screen.current_card = {"id": "card-3", "variant": 0, "revision": 1}
screen._toggle_options_menu()
_assert(not screen.options_bury_button.disabled and not screen.options_suspend_button.disabled,
"Bury/Suspend should enable once a live card is present")
screen._close_options_menu()
# ---------- Undo-in-history only surfaces for the matching most-recently-graded entry ----------
screen.session_history = [{
"note": {"type": "basic", "prompt": "P1", "answer": "A1", "extra": ""},
"card": {"id": "card-1", "variant": 0, "revision": 1},
"rating_options": [], "action": "graded", "card_id": "card-1",
"rating": "good", "client_review_id": "review-1"
}]
screen.last_card_id = "card-99"
screen.last_client_review_id = "review-1"
screen._render_history_entry(0)
_assert(not screen.undo_button.visible,
"undo should not surface when the browsed entry's card id doesn't match the last graded card")
screen.last_card_id = ""
screen.last_client_review_id = ""
screen._render_history_entry(0)
_assert(not screen.undo_button.visible,
"undo should not surface when there is no outstanding undoable review at all")
# ---------- Skip: pushes a "skipped" history entry, excludes the card, no grading occurs ----------
screen.seen_card_ids = []
screen.session_history = []
screen.history_cursor = 0
screen.current_note = {"type": "basic", "prompt": "P3(live)", "answer": "A3", "extra": ""}
screen.current_card = {"id": "card-3", "variant": 0, "revision": 1}
screen.awaiting_next = false
screen._skip_current_card()
_assert(screen.session_history.size() == 1 and screen.session_history[0]["action"] == "skipped",
"skipping the live card should push a 'skipped' history entry")
_assert(screen.seen_card_ids.has("card-3"), "skipping should still exclude the card from reappearing this session")
# ---------- Previous/Next paging through a hand-seeded history, back to live ----------
screen.seen_card_ids = []
screen.last_card_id = "card-1"
screen.last_client_review_id = "review-1"
screen.session_history = [
{
"note": {"type": "basic", "prompt": "P1", "answer": "A1", "extra": ""},
"card": {"id": "card-1", "variant": 0, "revision": 1},
"rating_options": [], "action": "graded", "card_id": "card-1",
"rating": "good", "client_review_id": "review-1"
},
{
"note": {"type": "basic", "prompt": "P2", "answer": "A2", "extra": ""},
"card": {"id": "card-2", "variant": 0, "revision": 1},
"rating_options": [], "action": "skipped", "card_id": "card-2"
}
]
screen.history_cursor = screen.session_history.size()
screen.current_note = {"type": "basic", "prompt": "P3(live)", "answer": "A3", "extra": ""}
screen.current_card = {"id": "card-3", "variant": 0, "revision": 1}
screen.rating_options = []
screen.due_remaining = 5
screen.new_remaining = 3
screen.revealed = false
screen._go_previous()
_assert(screen.front_label.text == "P2", "the first Previous press should show the most recently added history entry")
_assert(not screen.grade_row.visible, "grading buttons should stay hidden while browsing history")
_assert(not screen.undo_button.visible, "a skipped history entry should never offer 'undo this rating'")
screen._go_previous()
_assert(screen.front_label.text == "P1", "a second Previous press should step further back in history")
_assert(screen.undo_button.visible, "the specific most-recently-graded entry should surface 'undo this rating'")
screen._go_previous()
_assert(screen.history_cursor == 0, "Previous should refuse to step before the start of history")
screen._go_next()
_assert(screen.front_label.text == "P2", "Next while browsing history should step forward, not skip the live card")
screen._go_next()
_assert(screen.front_label.text == "P3(live)" and screen.history_cursor == screen.session_history.size(),
"stepping past the last history entry should return to the live card")
_assert(screen.tap_hint_label.visible and not screen.grade_row.visible,
"returning to a live, unrevealed card should restore the tap-to-reveal hint")
screen._go_next()
_assert(screen.session_history.size() == 3 and screen.session_history[2]["action"] == "skipped" and screen.session_history[2]["card_id"] == "card-3",
"Next while already at the live card should skip it (session-only, dual-meaning behavior)")
_assert(screen.seen_card_ids.has("card-3"), "skipping via Next should still exclude the card this session")
# ---------- Keyboard shortcuts are ignored while browsing history ----------
screen.session_history = [{
"note": {"type": "basic", "prompt": "P1", "answer": "A1", "extra": ""},
"card": {"id": "card-1", "variant": 0, "revision": 1},
"rating_options": [], "action": "graded", "card_id": "card-1"
}]
screen.history_cursor = 0
screen.current_note = {"type": "basic", "prompt": "P1", "answer": "A1", "extra": ""}
screen.current_card = {"id": "card-1", "variant": 0, "revision": 1}
screen.revealed = true
var key_event := InputEventKey.new()
key_event.keycode = KEY_1
key_event.pressed = true
screen._input(key_event)
_assert(screen.session_history.size() == 1,
"a grading keystroke while browsing history should be ignored instead of grading the historical card")
screen.free()
host.queue_free()
await process_frame
quit(1 if failures > 0 else 0)
func _assert(condition: bool, message: String) -> void:
if condition:
return
failures += 1
push_error(message)