Files
step3/tests/flashcard_session_navigation_test.gd
T
hz 09b670d5d4 Close the gap between the flashcards study screen and the mockup
Add real session-history navigation (Previous/Next/Shuffle), compact
side panels, and a segmented progress row to match the mockup layout.
Flashcards now opens straight into Study, with library management
moved into the Options menu ("Manage Flashcards") and Export hidden
from the UI (Import stays). Shuffle is backed by a real, due-before-new
randomized pick on the backend, not a decorative toggle.
2026-07-22 00:43:52 -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.reveal_button.visible and not screen.grade_row.visible,
"returning to a live, unrevealed card should restore the reveal prompt")
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)