Files
step3/tests/flashcard_ownership_ui_test.gd
T
hz 7f76ca0fdc Add system (curated) vs. user-uploaded flashcards
System decks/notes are owned by a reserved sentinel user id, reusing
all existing per-user store/index plumbing unchanged. Regular users
see them under a separate "Official Decks" section, can study them
with fully independent per-user FSRS progress (lazily materialized on
first visit), but can never edit or archive them - enforced both in
the client (no Edit/Archive controls) and server-side (ownership guard
on every mutating endpoint). Includes a small idempotent seed command
(`dotnet run -- seed-flashcards`) for testing, since there's no in-app
authoring UI yet.
2026-07-22 15:52:19 -05:00

108 lines
4.7 KiB
GDScript

extends SceneTree
# Exercises the client-side "system vs. user" flashcard split added to the hub screen: deck
# summaries splitting into My/Official groups in the deck selector, Edit/Archive controls being
# absent on system-owned note rows, and "New Card" refusing to target a system deck. Mirrors
# flashcard_theme_responsive_test.gd's instantiation pattern (added to the tree so _ready() runs).
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 Hub := load("res://scripts/ui/flashcards_hub.gd")
var hub = Hub.new()
hub.size = host.size
host.add_child(hub)
await process_frame
# ---------- Deck selector splits into "My Decks" / "Official Decks" ----------
var summaries := [
{"deck": {"id": "deck-mine", "name": "My Cardio Deck", "isSystem": false}, "total": 5, "mastered": 0, "due": 1, "new": 2},
{"deck": {"id": "deck-system", "name": "Step 3 Essentials", "isSystem": true}, "total": 10, "mastered": 3, "due": 0, "new": 4},
]
hub._on_decks_changed(summaries)
_assert(hub.deck_ids.size() == hub.deck_selector.item_count,
"deck_ids should stay index-aligned with the OptionButton's items (including separators)")
var my_header_index := -1
var official_header_index := -1
var mine_item_index := -1
var system_item_index := -1
for i in hub.deck_selector.item_count:
if hub.deck_selector.is_item_separator(i):
if hub.deck_selector.get_item_text(i) == "My Decks":
my_header_index = i
elif hub.deck_selector.get_item_text(i) == "Official Decks":
official_header_index = i
elif hub.deck_selector.get_item_text(i) == "My Cardio Deck":
mine_item_index = i
elif hub.deck_selector.get_item_text(i) == "Step 3 Essentials":
system_item_index = i
_assert(my_header_index >= 0, "a 'My Decks' separator header should be present")
_assert(official_header_index >= 0, "an 'Official Decks' separator header should be present")
_assert(mine_item_index > my_header_index, "the user's own deck should be listed under the 'My Decks' header")
_assert(system_item_index > official_header_index, "the system deck should be listed under the 'Official Decks' header")
_assert(str(hub.deck_ids[mine_item_index]) == "deck-mine", "deck_ids should still resolve the correct id for the user's own deck")
_assert(str(hub.deck_ids[system_item_index]) == "deck-system", "deck_ids should still resolve the correct id for the system deck")
_assert(hub._is_system_deck("deck-system"), "_is_system_deck should report true for the seeded system deck")
_assert(not hub._is_system_deck("deck-mine"), "_is_system_deck should report false for the user's own deck")
# ---------- System note rows have no Edit/Archive controls, plus an "Official" badge ----------
hub.notes = [
{"id": "note-mine", "type": "basic", "prompt": "Mine", "answer": "A", "tags": [], "isSystem": false},
{"id": "note-system", "type": "basic", "prompt": "System", "answer": "A", "tags": [], "isSystem": true},
]
hub._render_notes()
await process_frame
var mine_row := hub.list_container.get_child(0) as BoxContainer
var mine_actions := mine_row.get_child(2) as HBoxContainer
_assert(mine_actions.get_child_count() == 2, "the user's own note row should show both Edit and Archive")
var system_row := hub.list_container.get_child(2) as BoxContainer
var system_actions := system_row.get_child(2) as HBoxContainer
_assert(system_actions.get_child_count() == 0, "a system note row should show no Edit/Archive controls at all")
var system_header_row := (system_row.get_child(1) as VBoxContainer).get_child(0) as HBoxContainer
var found_official_badge := false
for child in system_header_row.get_children():
if child is Label and (child as Label).text == "Official":
found_official_badge = true
_assert(found_official_badge, "a system note row should show an 'Official' badge")
# ---------- "New Card" against a system-selected deck falls back to the default deck ----------
hub.decks = summaries
var system_index: int = hub.deck_ids.find("deck-system")
hub.deck_selector.select(system_index)
hub._open_editor("", {})
_assert(hub.editor_target_deck_id.is_empty(),
"opening the editor for a brand-new card while a system deck is selected should fall back to the default deck instead")
hub._close_editor()
var mine_index: int = hub.deck_ids.find("deck-mine")
hub.deck_selector.select(mine_index)
hub._open_editor("", {})
_assert(hub.editor_target_deck_id == "deck-mine",
"opening the editor for a brand-new card while the user's own deck is selected should target that deck")
hub.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)