diff --git a/CHANGELOG.md b/CHANGELOG.md index d995216..d80cc3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +- Fetch icons that a cached icon set lacks from the Iconify API. A set cached before an icon was added to the collection made new icons fail with "Icon not found" (#2). +- Report unknown icon names as not found instead of raising when fetching from the Iconify API. Requires `iconify` 0.3.1. +- Scan Markdown files for icon components. v0.3.5 added `content/**/*.md` to the default globs but skipped those files. +- Include Astral templates and Markdown in the default globs only when Astral is a dependency, so other projects don't scan their Markdown. + ## v0.3.5 - Add configurable scanner source globs diff --git a/README.md b/README.md index 104e2c6..7d81f54 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ config :phoenix_iconify, Options: - `:extra_icons` - icons to include even when they are not found by static discovery -- `:source_globs` - source globs to scan for literal icon usage. Defaults include Phoenix HEEx files and Astral `pages/**/*.astral`, `components/**/*.astral`, `layouts/**/*.astral`, and `content/**/*.md`. +- `:source_globs` - source globs to scan for literal icon usage. Defaults to Phoenix sources (`lib/**/*.ex`, `lib/**/*.heex`, `priv/**/*.heex`). In projects that depend on [Astral](https://hex.pm/packages/astral), the defaults also include its templates and Markdown: `pages/**/*.{astral,md}`, `components/**/*.astral`, `layouts/**/*.astral`, and `content/**/*.md`. - `:fallback` - icon to render when a requested icon is missing - `:warn_on_missing` - log missing icon warnings, enabled by default diff --git a/lib/mix/tasks/compile/phoenix_iconify.ex b/lib/mix/tasks/compile/phoenix_iconify.ex index db94b6c..9e25a7e 100644 --- a/lib/mix/tasks/compile/phoenix_iconify.ex +++ b/lib/mix/tasks/compile/phoenix_iconify.ex @@ -103,43 +103,11 @@ defmodule Mix.Tasks.Compile.PhoenixIconify do end defp fetch_prefix_icons(prefix, full_names) do - icon_names = Enum.map(full_names, &icon_name!/1) - - case Cache.fetch_set(prefix) do - {:ok, set} -> fetch_icons_from_set(prefix, icon_names, set) - {:error, _} -> fetch_icons_from_api(prefix, icon_names) - end + Cache.get_icons(prefix, Enum.map(full_names, &icon_name!/1)) end defp icon_name!(name) do {:ok, _prefix, icon_name} = Iconify.parse_name(name) icon_name end - - defp fetch_icons_from_set(prefix, icon_names, set) do - Enum.flat_map(icon_names, fn icon_name -> - case Iconify.Set.get(set, icon_name) do - {:ok, icon} -> - full_name = "#{prefix}:#{icon_name}" - [{full_name, %{icon | name: full_name}}] - - :error -> - [] - end - end) - end - - defp fetch_icons_from_api(prefix, icon_names) do - case Iconify.Fetcher.fetch_icons(prefix, icon_names) do - {:ok, icons} -> - Enum.map(icons, fn {name, icon} -> - full_name = "#{prefix}:#{name}" - {full_name, %{icon | name: full_name}} - end) - - {:error, reason} -> - Mix.shell().error("PhoenixIconify: Failed to fetch #{prefix} icons: #{inspect(reason)}") - [] - end - end end diff --git a/lib/phoenix_iconify/cache.ex b/lib/phoenix_iconify/cache.ex index 99744ae..14a1b71 100644 --- a/lib/phoenix_iconify/cache.ex +++ b/lib/phoenix_iconify/cache.ex @@ -102,6 +102,43 @@ defmodule PhoenixIconify.Cache do end end + @doc """ + Gets icons of one set, keyed by their full `prefix:name`. + + Icons come from the cached set when it has them. A cached set can predate icons + added to the collection since it was downloaded, so the rest are fetched one by + one with `fetch_icon`. Icons that exist in neither are left out. + """ + @spec get_icons(String.t(), [String.t()], (String.t(), String.t() -> + {:ok, Iconify.Icon.t()} | {:error, term()})) :: + [{String.t(), Iconify.Icon.t()}] + def get_icons(prefix, icon_names, fetch_icon \\ &Iconify.Fetcher.fetch_icon/2) do + cached = + case fetch_set(prefix) do + {:ok, set} -> + for name <- icon_names, + {:ok, icon} <- [Iconify.Set.get(set, name)], + into: %{}, + do: {name, icon} + + {:error, _} -> + %{} + end + + for name <- icon_names, + {:ok, icon} <- [cached_or_fetched(cached, prefix, name, fetch_icon)] do + full_name = "#{prefix}:#{name}" + {full_name, %{icon | name: full_name}} + end + end + + defp cached_or_fetched(cached, prefix, name, fetch_icon) do + case Map.fetch(cached, name) do + {:ok, icon} -> {:ok, icon} + :error -> fetch_icon.(prefix, name) + end + end + @doc """ Clears all cached icon sets. """ diff --git a/lib/phoenix_iconify/scanner.ex b/lib/phoenix_iconify/scanner.ex index 16ae8fc..761e25c 100644 --- a/lib/phoenix_iconify/scanner.ex +++ b/lib/phoenix_iconify/scanner.ex @@ -13,11 +13,11 @@ defmodule PhoenixIconify.Scanner do alias Phoenix.LiveView.TagEngine alias Phoenix.LiveView.Tokenizer, as: LegacyTokenizer - @default_source_globs [ - "lib/**/*.ex", - "lib/**/*.heex", - "priv/**/*.heex", - "pages/**/*.astral", + @phoenix_source_globs ["lib/**/*.ex", "lib/**/*.heex", "priv/**/*.heex"] + + # Astral renders HEEx from its templates and Markdown, so those hold icons too. + @astral_source_globs [ + "pages/**/*.{astral,md}", "components/**/*.astral", "layouts/**/*.astral", "content/**/*.md" @@ -45,16 +45,23 @@ defmodule PhoenixIconify.Scanner do defp source_paths do :phoenix_iconify - |> Application.get_env(:source_globs, @default_source_globs) + |> Application.get_env(:source_globs, default_source_globs()) |> List.wrap() |> Enum.flat_map(&Path.wildcard/1) end + defp default_source_globs do + if Code.ensure_loaded?(Astral), + do: @phoenix_source_globs ++ @astral_source_globs, + else: @phoenix_source_globs + end + defp scan_file(path) do content = File.read!(path) cond do - Path.extname(path) in [".heex", ".astral"] -> scan_heex(content, path) + # Markdown in Astral and similar tools can use HEEx components. + Path.extname(path) in [".heex", ".astral", ".md"] -> scan_heex(content, path) Path.extname(path) == ".ex" -> scan_ex(content) true -> [] end diff --git a/mix.exs b/mix.exs index 9ad26b8..571a8ed 100644 --- a/mix.exs +++ b/mix.exs @@ -42,16 +42,17 @@ defmodule PhoenixIconify.MixProject do if path = System.get_env("ICONIFY_PATH") do [path: path] else - "~> 0.3.0" + "~> 0.3.1" end end def cli do - [preferred_envs: [ci: :test]] + [preferred_envs: [ci: :test, "test.e2e": :test]] end defp aliases do [ + "test.e2e": ["test --only e2e"], ci: [ "compile --warnings-as-errors", "format --check-formatted", diff --git a/mix.lock b/mix.lock index f6aa425..2154478 100644 --- a/mix.lock +++ b/mix.lock @@ -6,16 +6,18 @@ "ex_dna": {:hex, :ex_dna, "1.5.1", "c5181067eba53bfe7509fc8a1a5c018659d2be87e4b3bf682c9ce570c7d62f62", [:mix], [{:credo, "~> 1.7", [hex: :credo, repo: "hexpm", optional: true]}, {:gen_lsp, "~> 0.11", [hex: :gen_lsp, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: true]}], "hexpm", "1b0653d3e8af5870063541a897b9f70937540053fb4555db5b81ec404428824f"}, "ex_doc": {:hex, :ex_doc, "0.40.1", "67542e4b6dde74811cfd580e2c0149b78010fd13001fda7cfeb2b2c2ffb1344d", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "bcef0e2d360d93ac19f01a85d58f91752d930c0a30e2681145feea6bd3516e00"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, - "finch": {:hex, :finch, "0.22.0", "5c48fa6f9706a78eb9036cacb67b8b996b4e66d111c543f4c29bb0f879a6806b", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b94e83c47780fc6813f746a1f1a34ee65cda42da4c5ea26a68f0acc4498e23dc"}, - "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "iconify": {:hex, :iconify, "0.3.0", "c6652941484621d9400ad119d885c3c4914d906dbd1cab8b8fbf4fe48193fc05", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "4365d7838e0092affb9c9cf2049e900e491d092256294d5c48d352b0e6f8e954"}, + "finch": {:hex, :finch, "0.24.0", "4022b6194e907b6d9b597c168001cac38e488a35fe36cef9d8297e3159782510", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "33ba40069c3587c2f99f9125b766e19dad87d6d54be3c6961db2304df04cef00"}, + "hpax": {:hex, :hpax, "1.1.0", "782931867cc23217c68fb5f68fe1a11f5e7544c7fda82c8a7019a5df5a4a1cdf", [:mix], [], "hexpm", "0b8d0f05832f55571d65ac720f79bf8994138ffbb133209dc4685eae0ad456a8"}, + "iconify": {:hex, :iconify, "0.3.1", "2332a72d42bcfaf463c6b3be6d42a5ed2ed5859b357999d03bb181a4ba2b6eb2", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:json_codec, "~> 0.3", [hex: :json_codec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "7a77f60b523ae0187a45f188f7a746703e62a13a30c141af80103ebdab5e968e"}, "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, + "json_codec": {:hex, :json_codec, "0.3.1", "81f738e212308c85344e11da00af6d8b52bd486224ee24c1f51b95df0604aeae", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:json_spec, "~> 1.2", [hex: :json_spec, repo: "hexpm", optional: false]}], "hexpm", "dffea3198d3f1fe9a39aa176ab0d6988b8c80dd85a67e913b323d4ec7c57ca9d"}, + "json_spec": {:hex, :json_spec, "1.2.0", "c67f73cdc4166e955b2f1095d3f44dd78c197337038f1a5301fb526b6c87ab04", [:mix], [], "hexpm", "3639961babad7d6bc45f3a2d96a49c18ec70614761bb84ce58b64e9fad9354de"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"}, "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, - "mint": {:hex, :mint, "1.8.0", "b964eaf4416f2dee2ba88968d52239fca5621b0402b9c95f55a08eb9d74803e9", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "f3c572c11355eccf00f22275e9b42463bc17bd28db13be1e28f8e0bb4adbc849"}, + "mint": {:hex, :mint, "1.11.0", "a713551624815c0435237b93d90ea8b9b14254690c66f732d0ef8930f76ff1d9", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "c6279ba2d6aa3a383a1d4cfbe7b59f42e6efd400f58d8e2acfeac48a438693ab"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, @@ -24,10 +26,10 @@ "phoenix_live_view": {:hex, :phoenix_live_view, "1.2.3", "e8abe7f2acb96d98695e9290b35cb5a01a696cc2086d4ff2e0b3aa8b9c74675f", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "449affd6aea24daaa2f6b43748fc1e2c6a87610df996cc1f54e7b19a7a18e638"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, - "plug": {:hex, :plug, "1.19.2", "e4950525b22c6789dfb38a3f95d47171ba159da3fc5a33be9643b43d5e8adb98", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b6fce20a56af5e60fa5dfecf3f907bb98ec981be43c79a3809a499bc3d133de0"}, - "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, + "plug": {:hex, :plug, "1.20.3", "56c480c633ec2ce10140e236e15233bf576e1d323887d7c96711bd02ab5160db", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be266aee1b8536ef6409d58cf39a3121319f0ec47cfa1b24024485aa0e76ad76"}, + "plug_crypto": {:hex, :plug_crypto, "2.2.0", "144014737daaf485407f5ed77daeaad74d651b216a28c87543f8cc7043f8efc8", [:mix], [], "hexpm", "83a95744ab1c75876542b6fab135fcc176280e0f301a111c1f757fddcec95d2c"}, "reach": {:hex, :reach, "2.5.0", "c6b2116d0bab5454597479e7b0b6bc224fb3a262b9e15bb16877b4fdd42c3ada", [:mix], [{:boxart, "~> 0.3.3", [hex: :boxart, repo: "hexpm", optional: true]}, {:ex_ast, "~> 0.12.0", [hex: :ex_ast, repo: "hexpm", optional: false]}, {:ex_dna, "~> 1.5", [hex: :ex_dna, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:libgraph, "~> 0.16.0", [hex: :libgraph, repo: "hexpm", optional: false]}, {:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: true]}, {:makeup_js, "~> 0.1", [hex: :makeup_js, repo: "hexpm", optional: true]}, {:quickbeam, "~> 0.10", [hex: :quickbeam, repo: "hexpm", optional: true]}], "hexpm", "b7fa8a1b5b8a1a55b16a0063ddbcedc63b747423b1cf4c26671919c3f56bbaf3"}, - "req": {:hex, :req, "0.5.18", "48e6431cb4135e8a7815e745177485369a9b4a9924d5fe68ca00eb09ceaed1ef", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.21.0 or ~> 0.22.0", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "fa03812c440a9754bf34355e0c5d4f3ed316458db62e3284b7a352ef8dc0b996"}, + "req": {:hex, :req, "0.7.4", "23e9ffec17de032a46a4b15ed65c09793893bf4a7c680f4bbf6227fce6bdf74d", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "4b192d63253e8dcc6221ef992ea9ebef7d3555166e8423aa5b553e86bc3c69a2"}, "sourceror": {:hex, :sourceror, "1.12.0", "da354c5f35aad3cc1132f5d5b0d8437d865e2661c263260480bab51b5eedb437", [:mix], [], "hexpm", "755703683bd014ebcd5de9acc24b68fb874a660a568d1d63f8f98cd8a6ef9cd0"}, "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, diff --git a/test/e2e/phoenix_iconify/cache_test.exs b/test/e2e/phoenix_iconify/cache_test.exs new file mode 100644 index 0000000..d0c11a4 --- /dev/null +++ b/test/e2e/phoenix_iconify/cache_test.exs @@ -0,0 +1,32 @@ +defmodule PhoenixIconify.E2E.CacheTest do + # Talks to the Iconify API. Run with `mix test.e2e`. + use ExUnit.Case, async: false + + alias PhoenixIconify.Cache + + @moduletag :e2e + @moduletag :tmp_dir + + setup %{tmp_dir: tmp_dir} do + {:ok, _} = Application.ensure_all_started(:req) + previous = Application.get_env(:phoenix_iconify, :cache_dir) + Application.put_env(:phoenix_iconify, :cache_dir, tmp_dir) + + on_exit(fn -> + if previous, + do: Application.put_env(:phoenix_iconify, :cache_dir, previous), + else: Application.delete_env(:phoenix_iconify, :cache_dir) + end) + end + + test "fetches an icon added to Lucide after its set was cached (#2)" do + Cache.save_set( + "lucide", + ~s({"prefix":"lucide","icons":{"mail":{"body":""}},"width":24,"height":24}) + ) + + assert [{"lucide:layer-arrow-up", icon}] = Cache.get_icons("lucide", ["layer-arrow-up"]) + assert icon.body =~ " + if previous, + do: Application.put_env(:phoenix_iconify, :cache_dir, previous), + else: Application.delete_env(:phoenix_iconify, :cache_dir) + end) + + # A set cached before "layer-arrow-up" was added to Lucide. + Cache.save_set( + "lucide", + ~s({"prefix":"lucide","icons":{"mail":{"body":""}},"width":24,"height":24}) + ) + end + + test "fetches icons a stale cached set lacks, and skips icons that exist nowhere" do + parent = self() + + fetch_icon = fn + "lucide", "layer-arrow-up" = name -> + send(parent, {:fetched, name}) + {:ok, %Iconify.Icon{name: name, body: "", width: 24, height: 24}} + + "lucide", name -> + send(parent, {:fetched, name}) + {:error, :not_found} + end + + icons = Cache.get_icons("lucide", ["mail", "layer-arrow-up", "no-such-icon"], fetch_icon) + + assert Enum.map(icons, &elem(&1, 0)) == ["lucide:mail", "lucide:layer-arrow-up"] + assert Enum.all?(icons, fn {full_name, icon} -> icon.name == full_name end) + + refute_received {:fetched, "mail"} + assert_received {:fetched, "layer-arrow-up"} + assert_received {:fetched, "no-such-icon"} + end + end end diff --git a/test/phoenix_iconify/scanner_test.exs b/test/phoenix_iconify/scanner_test.exs index 9aa705d..d524b73 100644 --- a/test/phoenix_iconify/scanner_test.exs +++ b/test/phoenix_iconify/scanner_test.exs @@ -28,6 +28,63 @@ defmodule PhoenixIconify.ScannerTest do end end + test "scans icon components in Markdown", %{tmp_dir: tmp} do + File.mkdir_p!(Path.join(tmp, "content")) + + File.write!(Path.join(tmp, "content/post.md"), """ + # Post + + Reach me by <.icon name="lucide:mail" /> email, or <.nav_link icon="lucide:rss">the feed. + + ```elixir + if a < b, do: :ok + ``` + """) + + previous = Application.get_env(:phoenix_iconify, :source_globs) + Application.put_env(:phoenix_iconify, :source_globs, ["content/**/*.md"]) + + try do + assert File.cd!(tmp, &Scanner.scan/0) == ["lucide:mail", "lucide:rss"] + after + if previous do + Application.put_env(:phoenix_iconify, :source_globs, previous) + else + Application.delete_env(:phoenix_iconify, :source_globs) + end + end + end + + describe "default source globs" do + setup %{tmp_dir: tmp} do + File.mkdir_p!(Path.join(tmp, "content")) + File.write!(Path.join(tmp, "content/post.md"), ~s(Mail <.icon name="lucide:mail" />.)) + + previous = Application.get_env(:phoenix_iconify, :source_globs) + Application.delete_env(:phoenix_iconify, :source_globs) + + on_exit(fn -> + if previous, do: Application.put_env(:phoenix_iconify, :source_globs, previous) + end) + end + + test "skip Markdown without Astral", %{tmp_dir: tmp} do + refute Code.ensure_loaded?(Astral) + assert File.cd!(tmp, &Scanner.scan/0) == [] + end + + test "include Astral templates and Markdown when Astral is present", %{tmp_dir: tmp} do + Code.compile_string("defmodule Astral do end") + + on_exit(fn -> + :code.purge(Astral) + :code.delete(Astral) + end) + + assert File.cd!(tmp, &Scanner.scan/0) == ["lucide:mail"] + end + end + describe "scan_heex_content/1" do test "extracts icon names from heex content" do content = """ diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e..8965455 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1 @@ -ExUnit.start() +ExUnit.start(exclude: [:e2e])