"""funnies unit tests (test-after, Standard strategy). Covers: theme word derivation, find-a-word placement, crossword grid layout, cartoon fallback, empty FunniesResult, self-contained output. Uses stubs for the clue-drafter and cartoon source — no real network in CI. """ from __future__ import annotations from newspaper.funnies import ( FunniesBrief, FunniesBuilder, StubXkcdCartoonSource, derive_theme_words, ) from newspaper.funnies.find_a_word import build_find_a_word from newspaper.funnies.crossword import build_crossword def test_theme_words_derived_from_content(): """BR1.1: theme words extracted from content (drops stopwords/short).""" content = "Sam and Alex married by the sea on Saturday with the family" words = derive_theme_words(content, max_words=6) assert words # some theme words present assert "the" not in words # stopword dropped def test_find_a_word_places_every_word(): """BR1.3: every listed word appears in the grid.""" words = ["sam", "alex", "sea", "rings"] faw = build_find_a_word(words) grid_text = "".join("".join(row) for row in faw.grid) for word in words: assert word.upper() in grid_text def test_crossword_builds_10x10_grid_with_clues(): """BR1.2 / Q2=B: crossword is 10x10 with across/down clues.""" xw = build_crossword(["sam", "alex", "sea"], "wedding") assert len(xw.grid) == 10 assert all(len(row) == 10 for row in xw.grid) assert xw.clues_across assert xw.clues_down def test_cartoon_success_embedded_locally(): """BR2.1: xkcd source found → cartoon art_ref is a local (non-remote) path.""" result = FunniesBuilder().build(FunniesBrief(theme_words=["sam"], context="wedding")) cartoon = result.cartoons[0] assert cartoon.source == "xkcd" assert not cartoon.art_ref.startswith(("http://", "https://")) def test_cartoon_failure_falls_back_to_tasteful_strip(): """BR2.2: no cartoon found → tasteful fallback (not a remote URL).""" builder = FunniesBuilder(cartoon_source=StubXkcdCartoonSource(found=False)) result = builder.build(FunniesBrief(theme_words=["sam"], context="wedding")) assert result.cartoons[0].source == "generated" assert result.status == "fallback" assert result.is_self_contained() def test_empty_funnies_result_on_no_content(): """BR3.2 / Q5=A: no viable content → empty result + statusMessage, no throw.""" result = FunniesBuilder().build(FunniesBrief(theme_words=[], context="")) assert result.status == "empty" assert result.status_message def test_funnies_output_self_contained(): """BR4.1 / NFR4: no remote URL survives in any art_ref.""" result = FunniesBuilder().build(FunniesBrief(theme_words=["sam", "alex"], context="wedding")) assert result.is_self_contained()