//// Tests for the prefs module's pure helpers and the `update` integration //// that persists/loads them. import at_record_web/model.{ type EditProposal, EditProposal, Grid, InboxCard, InboxLoaded, Model, ProposalFields, ProposalReviewing, Rows, } import at_record_web/msg.{GotEditInbox, IgnoreProposal, SetDisplay} import at_record_web/prefs import at_record_web/update.{update} import gleam/int import gleam/list import gleam/option.{None} import support.{empty_effect, logged_in} fn a_proposal(uri: String, cid: String) -> EditProposal { EditProposal( uri:, cid:, proposer_did: "did:plc:bob", proposer_handle: None, target_uri: "at://did:plc:alice/dev.mokkenstorm.crate.catalog.release/r1", release_title: "Spiderland", current: ProposalFields( title: None, released: None, country: None, genres: None, styles: None, ), fields: ProposalFields( title: None, released: None, country: None, genres: None, styles: None, ), created_at: "2026-02-01T00:00:00Z", ) } pub fn display_string_codec_test() { [#(Grid, "grid"), #(Rows, "rows")] |> list.each(fn(pair) { let #(display, str) = pair assert model.display_to_string(display) == str assert model.display_from_string(str) == display }) ["", "garbage"] |> list.each(fn(value) { assert model.display_from_string(value) == Grid }) } pub fn set_display_fires_a_persist_effect_test() { let #(_, effect) = update(logged_in(), SetDisplay(Rows)) assert effect != empty_effect() } pub fn ignored_proposals_round_trip_through_json_test() { let ignored = [#("at://a/edit/1", "cid-1"), #("at://a/edit/2", "cid-2")] let decoded = ignored |> prefs.encode_ignored_proposals |> prefs.decode_ignored_proposals assert decoded == ignored } pub fn ignored_proposals_decode_falls_back_to_empty_on_garbage_test() { ["not json", "{}"] |> list.each(fn(raw) { assert prefs.decode_ignored_proposals(raw) == [] }) } pub fn add_ignored_proposal_dedupes_on_uri_test() { let once = model.add_ignored_proposal([], "at://a/edit/1", "cid-1") let twice = model.add_ignored_proposal(once, "at://a/edit/1", "cid-2") assert twice == [#("at://a/edit/1", "cid-2")] } pub fn add_ignored_proposal_caps_to_the_most_recent_200_test() { let seeded = int.range(1, 201, [], fn(acc, n) { model.add_ignored_proposal(acc, "at://a/edit/" <> int.to_string(n), "cid") }) let capped = model.add_ignored_proposal(seeded, "at://a/edit/201", "cid") assert list.length(capped) == 200 // The oldest entry (edit/1) was evicted; the newest is present. assert !list.contains(capped, #("at://a/edit/1", "cid")) assert list.contains(capped, #("at://a/edit/201", "cid")) } pub fn is_ignored_proposal_matches_on_uri_and_cid_test() { let ignored = [#("at://a/edit/1", "cid-1")] assert model.is_ignored_proposal(ignored, "at://a/edit/1", "cid-1") // A re-filed proposal (same uri, new cid) is not covered by an old ignore. assert !model.is_ignored_proposal(ignored, "at://a/edit/1", "cid-2") assert !model.is_ignored_proposal(ignored, "at://a/edit/2", "cid-1") } pub fn ignore_proposal_records_the_uri_and_cid_and_persists_test() { let proposal = a_proposal("at://proposer/edit/1", "cid-1") let seeded = Model( ..logged_in(), inbox: InboxLoaded([InboxCard(proposal:, status: ProposalReviewing)]), ) let #(model_after, effect) = update(seeded, IgnoreProposal("at://proposer/edit/1")) assert model_after.ignored_proposals == [#("at://proposer/edit/1", "cid-1")] assert effect != empty_effect() } pub fn got_edit_inbox_filters_out_previously_ignored_proposals_test() { let kept = a_proposal("at://proposer/edit/2", "cid-2") let seeded = Model(..logged_in(), ignored_proposals: [#("at://proposer/edit/1", "cid-1")]) let #(model_after, _) = update( seeded, GotEditInbox(Ok([a_proposal("at://proposer/edit/1", "cid-1"), kept])), ) let assert InboxLoaded(cards) = model_after.inbox assert list.map(cards, fn(c) { c.proposal.uri }) == ["at://proposer/edit/2"] }