From 00f7aef8dcce3ffd4261b568799929dc9508b564 Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Fri, 9 Feb 2024 21:06:00 -0800 Subject: [PATCH 1/4] Adds description to media items; hooks it up to indexing/media downloading --- lib/pinchflat/media/media_item.ex | 2 ++ .../media_client/backends/yt_dlp/metadata_parser.ex | 1 + .../media_client/backends/yt_dlp/video_collection.ex | 3 ++- lib/pinchflat/tasks/source_tasks.ex | 3 ++- .../20240210045410_add_description_to_media_items.exs | 9 +++++++++ .../backends/yt_dlp/metadata_parser_test.exs | 6 ++++++ .../backends/yt_dlp/video_collection_test.exs | 2 +- test/pinchflat/media_client/source_details_test.exs | 2 +- test/pinchflat/media_client/video_downloader_test.exs | 5 +++++ test/pinchflat/tasks/source_tasks_test.exs | 1 + test/support/fixtures/media_source_fixtures.ex | 9 ++++++--- 11 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 priv/repo/migrations/20240210045410_add_description_to_media_items.exs diff --git a/lib/pinchflat/media/media_item.ex b/lib/pinchflat/media/media_item.ex index da29691a..c2ddce4b 100644 --- a/lib/pinchflat/media/media_item.ex +++ b/lib/pinchflat/media/media_item.ex @@ -13,6 +13,7 @@ defmodule Pinchflat.Media.MediaItem do @allowed_fields ~w( title media_id + description original_url livestream media_downloaded_at @@ -27,6 +28,7 @@ defmodule Pinchflat.Media.MediaItem do schema "media_items" do field :title, :string field :media_id, :string + field :description, :string field :original_url, :string field :livestream, :boolean, default: false field :media_downloaded_at, :utc_datetime diff --git a/lib/pinchflat/media_client/backends/yt_dlp/metadata_parser.ex b/lib/pinchflat/media_client/backends/yt_dlp/metadata_parser.ex index e588cdb9..757829bc 100644 --- a/lib/pinchflat/media_client/backends/yt_dlp/metadata_parser.ex +++ b/lib/pinchflat/media_client/backends/yt_dlp/metadata_parser.ex @@ -32,6 +32,7 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.MetadataParser do defp parse_media_metadata(metadata) do %{ title: metadata["title"], + description: metadata["description"], media_filepath: metadata["filepath"] } end diff --git a/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex b/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex index 66e56e15..6233c8d1 100644 --- a/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex +++ b/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex @@ -14,8 +14,9 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollection do def get_media_attributes(url, command_opts \\ []) do runner = Application.get_env(:pinchflat, :yt_dlp_runner) opts = command_opts ++ [:simulate, :skip_download] + output_template = "%(.{id,title,was_live,original_url,description})j" - case runner.run(url, opts, "%(.{id,title,was_live,original_url})j") do + case runner.run(url, opts, output_template) do {:ok, output} -> output |> String.split("\n", trim: true) diff --git a/lib/pinchflat/tasks/source_tasks.ex b/lib/pinchflat/tasks/source_tasks.ex index 98d2c0d8..714b6b32 100644 --- a/lib/pinchflat/tasks/source_tasks.ex +++ b/lib/pinchflat/tasks/source_tasks.ex @@ -52,7 +52,8 @@ defmodule Pinchflat.Tasks.SourceTasks do title: media_attrs["title"], media_id: media_attrs["id"], original_url: media_attrs["original_url"], - livestream: media_attrs["was_live"] + livestream: media_attrs["was_live"], + description: media_attrs["description"] } case Media.create_media_item(attrs) do diff --git a/priv/repo/migrations/20240210045410_add_description_to_media_items.exs b/priv/repo/migrations/20240210045410_add_description_to_media_items.exs new file mode 100644 index 00000000..f03a34b8 --- /dev/null +++ b/priv/repo/migrations/20240210045410_add_description_to_media_items.exs @@ -0,0 +1,9 @@ +defmodule Pinchflat.Repo.Migrations.AddDescriptionToMediaItems do + use Ecto.Migration + + def change do + alter table(:media_items) do + add :description, :text + end + end +end diff --git a/test/pinchflat/media_client/backends/yt_dlp/metadata_parser_test.exs b/test/pinchflat/media_client/backends/yt_dlp/metadata_parser_test.exs index e0e032f4..c29675d0 100644 --- a/test/pinchflat/media_client/backends/yt_dlp/metadata_parser_test.exs +++ b/test/pinchflat/media_client/backends/yt_dlp/metadata_parser_test.exs @@ -36,6 +36,12 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.MediaParserTest do assert result.title == "Trying to Wheelie Without the Rear Brake" end + test "it extracts the description", %{metadata: metadata} do + result = Parser.parse_for_media_item(metadata) + + assert is_binary(result.description) + end + test "it returns the metadata as a map", %{metadata: metadata} do result = Parser.parse_for_media_item(metadata) diff --git a/test/pinchflat/media_client/backends/yt_dlp/video_collection_test.exs b/test/pinchflat/media_client/backends/yt_dlp/video_collection_test.exs index 5230f5e5..0fd13dcc 100644 --- a/test/pinchflat/media_client/backends/yt_dlp/video_collection_test.exs +++ b/test/pinchflat/media_client/backends/yt_dlp/video_collection_test.exs @@ -20,7 +20,7 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do test "it passes the expected default args" do expect(YtDlpRunnerMock, :run, fn _url, opts, ot -> assert opts == [:simulate, :skip_download] - assert ot == "%(.{id,title,was_live,original_url})j" + assert ot == "%(.{id,title,was_live,original_url,description})j" {:ok, ""} end) diff --git a/test/pinchflat/media_client/source_details_test.exs b/test/pinchflat/media_client/source_details_test.exs index 2e1bd61c..8ddb517b 100644 --- a/test/pinchflat/media_client/source_details_test.exs +++ b/test/pinchflat/media_client/source_details_test.exs @@ -47,7 +47,7 @@ defmodule Pinchflat.MediaClient.SourceDetailsTest do test "it passes the expected arguments to the backend" do expect(YtDlpRunnerMock, :run, fn @channel_url, opts, ot -> assert opts == [:simulate, :skip_download] - assert ot == "%(.{id,title,was_live,original_url})j" + assert ot == "%(.{id,title,was_live,original_url,description})j" {:ok, ""} end) diff --git a/test/pinchflat/media_client/video_downloader_test.exs b/test/pinchflat/media_client/video_downloader_test.exs index cfc362a2..0d6acb31 100644 --- a/test/pinchflat/media_client/video_downloader_test.exs +++ b/test/pinchflat/media_client/video_downloader_test.exs @@ -68,6 +68,11 @@ defmodule Pinchflat.MediaClient.VideoDownloaderTest do assert updated_media_item.title == "Trying to Wheelie Without the Rear Brake" end + test "it extracts the description", %{media_item: media_item} do + assert {:ok, updated_media_item} = VideoDownloader.download_for_media_item(media_item) + assert is_binary(updated_media_item.description) + end + test "it extracts the media_filepath", %{media_item: media_item} do assert media_item.media_filepath == nil assert {:ok, updated_media_item} = VideoDownloader.download_for_media_item(media_item) diff --git a/test/pinchflat/tasks/source_tasks_test.exs b/test/pinchflat/tasks/source_tasks_test.exs index 6917401f..662193dd 100644 --- a/test/pinchflat/tasks/source_tasks_test.exs +++ b/test/pinchflat/tasks/source_tasks_test.exs @@ -63,6 +63,7 @@ defmodule Pinchflat.Tasks.SourceTasksTest do assert Enum.count(media_items) == 3 assert ["video1", "video2", "video3"] == Enum.map(media_items, & &1.media_id) assert ["Video 1", "Video 2", "Video 3"] == Enum.map(media_items, & &1.title) + assert ["desc1", "desc2", "desc3"] == Enum.map(media_items, & &1.description) assert Enum.all?(media_items, fn mi -> mi.original_url end) assert Enum.all?(media_items, fn %MediaItem{} -> true end) end diff --git a/test/support/fixtures/media_source_fixtures.ex b/test/support/fixtures/media_source_fixtures.ex index 958854eb..7784c541 100644 --- a/test/support/fixtures/media_source_fixtures.ex +++ b/test/support/fixtures/media_source_fixtures.ex @@ -36,19 +36,22 @@ defmodule Pinchflat.MediaSourceFixtures do id: "video1", title: "Video 1", original_url: "https://example.com/video1", - was_live: false + was_live: false, + description: "desc1" }, %{ id: "video2", title: "Video 2", original_url: "https://example.com/video2", - was_live: true + was_live: true, + description: "desc2" }, %{ id: "video3", title: "Video 3", original_url: "https://example.com/video3", - was_live: false + was_live: false, + description: "desc3" } ] From ed759e5e098350e2a4c0949890bca30573694528 Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Fri, 9 Feb 2024 22:22:02 -0800 Subject: [PATCH 2/4] Added a search method using postgres fulltext search --- lib/pinchflat/media.ex | 36 +++++++++++++++++++ lib/pinchflat/media/media_item.ex | 2 ++ ...051038_add_search_field_to_media_items.exs | 28 +++++++++++++++ .../media_client/video_downloader_test.exs | 2 +- test/pinchflat/media_test.exs | 31 ++++++++++++++++ test/pinchflat/tasks/source_tasks_test.exs | 2 +- 6 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 priv/repo/migrations/20240210051038_add_search_field_to_media_items.exs diff --git a/lib/pinchflat/media.ex b/lib/pinchflat/media.ex index 85529a78..b19fc6ce 100644 --- a/lib/pinchflat/media.ex +++ b/lib/pinchflat/media.ex @@ -47,6 +47,42 @@ defmodule Pinchflat.Media do |> Repo.all() end + @doc """ + Returns a list of media_items that match the search term. Adds a `matching_search_term` + virtual field to the result set. + + Returns [%MediaItem{}, ...]. + + TODO: test limit + """ + def search(search_term, opts \\ []) do + limit = Keyword.get(opts, :limit, 50) + + from(mi in MediaItem, + where: fragment("searchable @@ websearch_to_tsquery(?)", ^search_term), + select_merge: %{ + matching_search_term: + fragment( + """ + ts_headline( + 'english', + CONCAT(title, ' ', description), + websearch_to_tsquery(?), + 'StartSel=[PF_HIGHLIGHT],StopSel=[/PF_HIGHLIGHT]' + ) + """, + ^search_term + ) + }, + order_by: { + :desc, + fragment("ts_rank_cd(searchable, websearch_to_tsquery(?), 0)", ^search_term) + }, + limit: ^limit + ) + |> Repo.all() + end + @doc """ Gets a single media_item. diff --git a/lib/pinchflat/media/media_item.ex b/lib/pinchflat/media/media_item.ex index c2ddce4b..838cc14a 100644 --- a/lib/pinchflat/media/media_item.ex +++ b/lib/pinchflat/media/media_item.ex @@ -41,6 +41,8 @@ defmodule Pinchflat.Media.MediaItem do # Will very likely revisit because I can't leave well-enough alone. field :subtitle_filepaths, {:array, {:array, :string}}, default: [] + field :matching_search_term, :string, virtual: true + belongs_to :source, Source has_one :metadata, MediaMetadata, on_replace: :update diff --git a/priv/repo/migrations/20240210051038_add_search_field_to_media_items.exs b/priv/repo/migrations/20240210051038_add_search_field_to_media_items.exs new file mode 100644 index 00000000..04de1d74 --- /dev/null +++ b/priv/repo/migrations/20240210051038_add_search_field_to_media_items.exs @@ -0,0 +1,28 @@ +defmodule Pinchflat.Repo.Migrations.AddSearchFieldToMediaItems do + use Ecto.Migration + + def up do + execute """ + ALTER TABLE media_items + ADD COLUMN searchable tsvector + GENERATED ALWAYS AS ( + setweight(to_tsvector('english', coalesce(title, '')), 'A') || + setweight(to_tsvector('english', coalesce(description, '')), 'B') + ) STORED; + """ + + execute """ + CREATE INDEX media_items_searchable_idx ON media_items USING gin(searchable); + """ + end + + def down do + execute """ + DROP INDEX media_items_searchable_idx; + """ + + alter table(:media_items) do + remove :searchable + end + end +end diff --git a/test/pinchflat/media_client/video_downloader_test.exs b/test/pinchflat/media_client/video_downloader_test.exs index 0d6acb31..794f6b54 100644 --- a/test/pinchflat/media_client/video_downloader_test.exs +++ b/test/pinchflat/media_client/video_downloader_test.exs @@ -60,7 +60,7 @@ defmodule Pinchflat.MediaClient.VideoDownloaderTest do test "it sets the media_downloaded_at", %{media_item: media_item} do assert media_item.media_downloaded_at == nil assert {:ok, updated_media_item} = VideoDownloader.download_for_media_item(media_item) - assert DateTime.diff(DateTime.utc_now(), updated_media_item.media_downloaded_at) < 1 + assert DateTime.diff(DateTime.utc_now(), updated_media_item.media_downloaded_at) < 2 end test "it extracts the title", %{media_item: media_item} do diff --git a/test/pinchflat/media_test.exs b/test/pinchflat/media_test.exs index bcf758cf..d20a92b2 100644 --- a/test/pinchflat/media_test.exs +++ b/test/pinchflat/media_test.exs @@ -183,6 +183,37 @@ defmodule Pinchflat.MediaTest do end end + describe "search/1" do + setup do + media_item = + media_item_fixture(%{ + title: "The quick brown fox", + description: "jumps over the lazy dog" + }) + + {:ok, %{media_item_id: media_item.id}} + end + + test "searches based on title", %{media_item_id: media_item_id} do + assert [%{id: ^media_item_id}] = Media.search("quick") + end + + test "searches based on description", %{media_item_id: media_item_id} do + assert [%{id: ^media_item_id}] = Media.search("lazy") + end + + test "adds a matching_search_term attribute with the relevant text" do + assert [res] = Media.search("quick") + assert String.contains?(res.matching_search_term, "The [PF_HIGHLIGHT]quick[/PF_HIGHLIGHT] brown fox") + end + + test "optionall lets you specify a limit" do + media_item_fixture(%{title: "The small gray dog"}) + + assert [_] = Media.search("dog", limit: 1) + end + end + describe "get_media_item!/1" do test "it returns the media_item with given id" do media_item = media_item_fixture() diff --git a/test/pinchflat/tasks/source_tasks_test.exs b/test/pinchflat/tasks/source_tasks_test.exs index 662193dd..c2499ec8 100644 --- a/test/pinchflat/tasks/source_tasks_test.exs +++ b/test/pinchflat/tasks/source_tasks_test.exs @@ -111,7 +111,7 @@ defmodule Pinchflat.Tasks.SourceTasksTest do SourceTasks.index_media_items(source) source = Repo.reload!(source) - assert DateTime.diff(DateTime.utc_now(), source.last_indexed_at) < 1 + assert DateTime.diff(DateTime.utc_now(), source.last_indexed_at) < 2 end end From dd8940893f503335d42bb651c281e177896d4a4c Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Tue, 13 Feb 2024 11:43:43 -0800 Subject: [PATCH 3/4] Hooked up search functionality to the search form --- lib/pinchflat/media.ex | 2 -- lib/pinchflat/utils/string_utils.ex | 17 +++++++++++ lib/pinchflat_web.ex | 2 ++ .../layouts/partials/header.html.heex | 21 +++++++------- .../media_sources/source_html/show.html.heex | 10 +++---- .../controllers/searches/search_controller.ex | 12 ++++++++ .../controllers/searches/search_html.ex | 25 ++++++++++++++++ .../searches/search_html/show.html.heex | 29 +++++++++++++++++++ lib/pinchflat_web/router.ex | 1 + test/pinchflat/media_test.exs | 2 +- test/pinchflat/utils/string_utils_test.exs | 14 +++++++++ .../controllers/search_controller_test.exs | 10 +++++++ 12 files changed, 127 insertions(+), 18 deletions(-) create mode 100644 lib/pinchflat_web/controllers/searches/search_controller.ex create mode 100644 lib/pinchflat_web/controllers/searches/search_html.ex create mode 100644 lib/pinchflat_web/controllers/searches/search_html/show.html.heex create mode 100644 test/pinchflat_web/controllers/search_controller_test.exs diff --git a/lib/pinchflat/media.ex b/lib/pinchflat/media.ex index b19fc6ce..6bd21ea9 100644 --- a/lib/pinchflat/media.ex +++ b/lib/pinchflat/media.ex @@ -52,8 +52,6 @@ defmodule Pinchflat.Media do virtual field to the result set. Returns [%MediaItem{}, ...]. - - TODO: test limit """ def search(search_term, opts \\ []) do limit = Keyword.get(opts, :limit, 50) diff --git a/lib/pinchflat/utils/string_utils.ex b/lib/pinchflat/utils/string_utils.ex index e0f01e89..5f23e6fd 100644 --- a/lib/pinchflat/utils/string_utils.ex +++ b/lib/pinchflat/utils/string_utils.ex @@ -24,4 +24,21 @@ defmodule Pinchflat.Utils.StringUtils do |> Base.encode16(case: :lower) |> String.slice(0..(length - 1)) end + + @doc """ + Truncates a string to the given length and adds `...` if the string is longer than the given length. + Will break on a word boundary. Nothing happens if the string is shorter than the given length. + + Returns binary() + """ + def truncate(string, length) do + if String.length(string) > length do + string + |> String.slice(0..(length - 1)) + |> String.replace(~r/\s+\S*$/, "") + |> Kernel.<>("...") + else + string + end + end end diff --git a/lib/pinchflat_web.ex b/lib/pinchflat_web.ex index 05187a18..0ec628bf 100644 --- a/lib/pinchflat_web.ex +++ b/lib/pinchflat_web.ex @@ -89,6 +89,8 @@ defmodule PinchflatWeb do import PinchflatWeb.CustomComponents.TableComponents import PinchflatWeb.CustomComponents.ButtonComponents + alias Pinchflat.Utils.StringUtils + # Shortcut for generating JS commands alias Phoenix.LiveView.JS diff --git a/lib/pinchflat_web/components/layouts/partials/header.html.heex b/lib/pinchflat_web/components/layouts/partials/header.html.heex index a89a1626..c08b6c11 100644 --- a/lib/pinchflat_web/components/layouts/partials/header.html.heex +++ b/lib/pinchflat_web/components/layouts/partials/header.html.heex @@ -1,5 +1,5 @@
-
+
- +
- diff --git a/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex index 97332cf5..52c14f6b 100644 --- a/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex +++ b/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex @@ -16,7 +16,7 @@
-
+

Relationships

@@ -35,10 +35,10 @@ <.list_items_from_map map={Map.from_struct(@source)} />

Downloaded Media

- <%= if length(@downloaded_media) > 0 do %> + <%= if match?([_|_], @downloaded_media) do %> <.table rows={@downloaded_media} table_class="text-black dark:text-white"> <:col :let={media_item} label="Title"> - <%= String.slice(media_item.title, 0..50) %>... + <%= StringUtils.truncate(media_item.title, 50) %> <:col :let={media_item} label="" class="flex place-content-evenly"> <.link @@ -54,10 +54,10 @@ <% end %>

Pending Media

- <%= if length(@pending_media) > 0 do %> + <%= if match?([_|_], @pending_media) do %> <.table rows={@pending_media} table_class="text-black dark:text-white"> <:col :let={media_item} label="Title"> - <%= String.slice(media_item.title, 0..50) %>... + <%= StringUtils.truncate(media_item.title, 50) %> <:col :let={media_item} label="" class="flex place-content-evenly"> <.link diff --git a/lib/pinchflat_web/controllers/searches/search_controller.ex b/lib/pinchflat_web/controllers/searches/search_controller.ex new file mode 100644 index 00000000..333abea3 --- /dev/null +++ b/lib/pinchflat_web/controllers/searches/search_controller.ex @@ -0,0 +1,12 @@ +defmodule PinchflatWeb.Searches.SearchController do + use PinchflatWeb, :controller + + alias Pinchflat.Media + + def show(conn, params) do + search_term = Map.get(params, "q", "") + search_results = Media.search(search_term) + + render(conn, :show, search_term: search_term, search_results: search_results) + end +end diff --git a/lib/pinchflat_web/controllers/searches/search_html.ex b/lib/pinchflat_web/controllers/searches/search_html.ex new file mode 100644 index 00000000..1e049bea --- /dev/null +++ b/lib/pinchflat_web/controllers/searches/search_html.ex @@ -0,0 +1,25 @@ +defmodule PinchflatWeb.Searches.SearchHTML do + use PinchflatWeb, :html + + embed_templates "search_html/*" + + @doc """ + Highlight search terms in a string of text based on `[PF_HIGHLIGHT]` and `[/PF_HIGHLIGHT]` tags + """ + attr :text, :string, required: true + + def highlight_search_terms(assigns) do + split_string = String.split(assigns.text, ~r{\[PF_HIGHLIGHT\]|\[/PF_HIGHLIGHT\]}, include_captures: true) + assigns = assign(assigns, split_string: split_string) + + ~H""" + <%= for fragment <- @split_string do %> + <%= render_fragment(fragment) %> + <% end %> + """ + end + + defp render_fragment("[PF_HIGHLIGHT]"), do: raw(~s()) + defp render_fragment("[/PF_HIGHLIGHT]"), do: raw("") + defp render_fragment(text), do: text +end diff --git a/lib/pinchflat_web/controllers/searches/search_html/show.html.heex b/lib/pinchflat_web/controllers/searches/search_html/show.html.heex new file mode 100644 index 00000000..996701ea --- /dev/null +++ b/lib/pinchflat_web/controllers/searches/search_html/show.html.heex @@ -0,0 +1,29 @@ +
+

+ Results for "<%= StringUtils.truncate(@search_term, 50) %>" +

+
+ +
+
+
+ <%= if match?([_|_], @search_results) do %> + <.table rows={@search_results} table_class="text-black dark:text-white"> + <:col :let={result} label="Title"> + <%= StringUtils.truncate(result.title, 40) %> + + <:col :let={result} label="Excerpt"> + <.highlight_search_terms text={result.matching_search_term} /> + + <:col :let={result} label="" class="flex place-content-evenly"> + <.link navigate={~p"/media/#{result.id}"} class="hover:text-secondary duration-200 ease-in-out mx-0.5"> + <.icon name="hero-eye" /> + + + + <% else %> +

No results found

+ <% end %> +
+
+
diff --git a/lib/pinchflat_web/router.ex b/lib/pinchflat_web/router.ex index 55f177ae..55c9fcb4 100644 --- a/lib/pinchflat_web/router.ex +++ b/lib/pinchflat_web/router.ex @@ -21,6 +21,7 @@ defmodule PinchflatWeb.Router do resources "/media_profiles", MediaProfiles.MediaProfileController resources "/media", Media.MediaItemController, only: [:show] + resources "/search", Searches.SearchController, only: [:show], singleton: true resources "/sources", MediaSources.SourceController do resources "/media", Media.MediaItemController, only: [:show] diff --git a/test/pinchflat/media_test.exs b/test/pinchflat/media_test.exs index d20a92b2..8fbe8d70 100644 --- a/test/pinchflat/media_test.exs +++ b/test/pinchflat/media_test.exs @@ -207,7 +207,7 @@ defmodule Pinchflat.MediaTest do assert String.contains?(res.matching_search_term, "The [PF_HIGHLIGHT]quick[/PF_HIGHLIGHT] brown fox") end - test "optionall lets you specify a limit" do + test "optionally lets you specify a limit" do media_item_fixture(%{title: "The small gray dog"}) assert [_] = Media.search("dog", limit: 1) diff --git a/test/pinchflat/utils/string_utils_test.exs b/test/pinchflat/utils/string_utils_test.exs index d77e468c..51172e3b 100644 --- a/test/pinchflat/utils/string_utils_test.exs +++ b/test/pinchflat/utils/string_utils_test.exs @@ -27,4 +27,18 @@ defmodule Pinchflat.Utils.StringUtilsTest do assert String.length(StringUtils.random_string(64)) == 64 end end + + describe "truncate/2" do + test "truncates a string to the given length and adds ..." do + assert StringUtils.truncate("hello world", 5) == "hello..." + end + + test "breaks on a word boundary" do + assert StringUtils.truncate("hello world", 7) == "hello..." + end + + test "does not truncate a string shorter than the given length" do + assert StringUtils.truncate("hello", 10) == "hello" + end + end end diff --git a/test/pinchflat_web/controllers/search_controller_test.exs b/test/pinchflat_web/controllers/search_controller_test.exs new file mode 100644 index 00000000..073ebc8c --- /dev/null +++ b/test/pinchflat_web/controllers/search_controller_test.exs @@ -0,0 +1,10 @@ +defmodule PinchflatWeb.SearchControllerTest do + use PinchflatWeb.ConnCase + + describe "show search" do + test "renders the page", %{conn: conn} do + conn = get(conn, ~p"/search") + assert html_response(conn, 200) =~ "Results" + end + end +end From a85e563a0f8ca7bfb3db65836f1e2dde4023eae6 Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Tue, 13 Feb 2024 11:46:47 -0800 Subject: [PATCH 4/4] Added persistence to the search form when on search page --- lib/pinchflat_web/components/layouts/app.html.heex | 2 +- lib/pinchflat_web/components/layouts/partials/header.html.heex | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pinchflat_web/components/layouts/app.html.heex b/lib/pinchflat_web/components/layouts/app.html.heex index a582e8ca..ffb79a69 100644 --- a/lib/pinchflat_web/components/layouts/app.html.heex +++ b/lib/pinchflat_web/components/layouts/app.html.heex @@ -2,7 +2,7 @@ <.sidebar />
- <.header /> + <.header params={@conn.params} />
<.flash_group flash={@flash} /> diff --git a/lib/pinchflat_web/components/layouts/partials/header.html.heex b/lib/pinchflat_web/components/layouts/partials/header.html.heex index c08b6c11..056d30b4 100644 --- a/lib/pinchflat_web/components/layouts/partials/header.html.heex +++ b/lib/pinchflat_web/components/layouts/partials/header.html.heex @@ -20,6 +20,7 @@