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])