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).
174 lines
7.9 KiB
GDScript
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)
|