diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index b8fe109..899055f 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -13,10 +13,15 @@ jobs: unit-tests: name: Unit tests runs-on: ubuntu-22.04 + env: + MIX_ENV: test + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v4 - + with: + fetch-depth: 0 + - name: Set up Elixir uses: erlef/setup-beam@v1 with: @@ -32,13 +37,9 @@ jobs: - name: Install dependencies run: mix deps.get - env: - MIX_ENV: test - - - name: Run tests - run: mix test - env: - MIX_ENV: test + + - name: Run tests and post coverage to Coveralls + run: mix coveralls.github e2e-tests: name: E2E tests diff --git a/.gitignore b/.gitignore index 463d17e..344acba 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,10 @@ corex-*.tar .cursor +/.expert/ + +/installer/ + +/integration_test/ + +/my_app/ diff --git a/README.md b/README.md index 5c0f6a6..4d1e48a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ +![Hex.pm License](https://img.shields.io/hexpm/l/corex) +![Hex.pm Version](https://img.shields.io/hexpm/v/corex) +[![Coverage Status](https://coveralls.io/repos/github/corex-ui/corex/badge.svg?branch=corex-install)](https://coveralls.io/github/corex-ui/corex?branch=corex-install) +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/corex-ui/corex/elixir.yml) +![GitHub branch check runs](https://img.shields.io/github/check-runs/corex-ui/corex/main) + # Corex Corex is an accessible and unstyled UI components library written in Elixir and TypeScript that integrates [Zag.js](https://zagjs.com/) state machines into the Phoenix Framework. @@ -71,8 +77,6 @@ mix local.phx mix phx.new my_app ``` -## Dependencies - Add `corex` to your `mix.exs` dependencies: ```elixir diff --git a/design/components/layout.css b/design/components/layout.css index 06c5394..4a15e95 100644 --- a/design/components/layout.css +++ b/design/components/layout.css @@ -12,7 +12,7 @@ background: var(--color-root); overflow-y: scroll; - scrollbar-gutter: stable both-edges; + scrollbar-gutter: stable; } .layout__header { @@ -80,7 +80,7 @@ width: auto; height: calc(100vh - var(--spacing-ui-lg)); overflow-y: scroll; - scrollbar-gutter: stable both-edges; + scrollbar-gutter: stable; } @@ -95,7 +95,6 @@ flex-direction: column; flex: 1; min-width: 0; - min-height: calc(100vh - var(--spacing-ui-lg)); width: 100%; height: 100%; margin-inline: auto; diff --git a/e2e/.gitignore b/e2e/.gitignore index 9b8002b..f6fa1da 100644 --- a/e2e/.gitignore +++ b/e2e/.gitignore @@ -33,5 +33,9 @@ e2e-*.tar # In case you use Node.js/npm, you want to ignore these. npm-debug.log + /assets/node_modules/ + .env + +/.expert/ diff --git a/e2e/assets/corex/components/layout.css b/e2e/assets/corex/components/layout.css index 06c5394..4a15e95 100644 --- a/e2e/assets/corex/components/layout.css +++ b/e2e/assets/corex/components/layout.css @@ -12,7 +12,7 @@ background: var(--color-root); overflow-y: scroll; - scrollbar-gutter: stable both-edges; + scrollbar-gutter: stable; } .layout__header { @@ -80,7 +80,7 @@ width: auto; height: calc(100vh - var(--spacing-ui-lg)); overflow-y: scroll; - scrollbar-gutter: stable both-edges; + scrollbar-gutter: stable; } @@ -95,7 +95,6 @@ flex-direction: column; flex: 1; min-width: 0; - min-height: calc(100vh - var(--spacing-ui-lg)); width: 100%; height: 100%; margin-inline: auto; diff --git a/e2e/mix.exs b/e2e/mix.exs index 4a72f2c..6debd2e 100644 --- a/e2e/mix.exs +++ b/e2e/mix.exs @@ -96,7 +96,7 @@ defmodule E2e.MixProject do test: [ &clean_static_assets/1, ©_static_images/1, - "assets.deploy", + "assets.build", "ecto.drop --quiet", "ecto.create --quiet", "ecto.migrate", diff --git a/e2e/mix.lock b/e2e/mix.lock index 02f826e..d290696 100644 --- a/e2e/mix.lock +++ b/e2e/mix.lock @@ -11,7 +11,7 @@ "ecto_sql": {:hex, :ecto_sql, "3.13.4", "b6e9d07557ddba62508a9ce4a484989a5bb5e9a048ae0e695f6d93f095c25d60", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b38cf0749ca4d1c5a8bcbff79bbe15446861ca12a61f9fba604486cb6b62a14"}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"}, - "ex_cldr": {:hex, :ex_cldr, "2.47.0", "350cab41e7deac2ab65cedf71e21e055a52927543dc84570abd8c686ac00cb4d", [:mix], [{:cldr_utils, "~> 2.28", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19 or ~> 1.0", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "3e454cbe4354f042795ae0562686e5137d4cfb953f3bc54c87077ac24c17be09"}, + "ex_cldr": {:hex, :ex_cldr, "2.47.1", "2dd2f0da2d5720bf413e0320cfd0ea7f0259a888c33e727c5f0db6bab3380252", [:mix], [{:cldr_utils, "~> 2.28", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19 or ~> 1.0", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "2555d6599d16311a096d8cb2d02e9dc3011ca02abbae446817d4f445a286c758"}, "ex_cldr_territories": {:hex, :ex_cldr_territories, "2.10.0", "2ae852c43b7a6689bcf18f0325f362a71c7ab5496d1c20b5b94867eda7fd95fa", [:mix], [{:ex_cldr, "~> 2.42", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "13f084f9283f8ab1ba5bf3aead936f008341297a8291be6236efaffd1a200e95"}, "expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, @@ -45,7 +45,7 @@ "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.2", "b18b0773a1ba77f28c52decbb0f10fd1ac4d3ae5b8632399bbf6986e3b665f62", [:mix], [{:file_system, "~> 0.2.10 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "d1f89c18114c50d394721365ffb428cce24f1c13de0467ffa773e2ff4a30d5b9"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.24", "1a000a048d5971b61a9efe29a3c4144ca955afd42224998d841c5011a5354838", [: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-rc", [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", "0c724e6c65f197841cac49d73be4e0f9b93a7711eaa52d2d4d1b9f859c329267"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.25", "abc1bdf7f148d7f9a003f149834cc858b24290c433b10ef6d1cbb1d6e9a211ca", [: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-rc", [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", "b8946e474799da1f874eab7e9ce107502c96ca318ed46d19f811f847df270865"}, "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.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [: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", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, @@ -53,7 +53,7 @@ "postgrex": {:hex, :postgrex, "0.22.0", "fb027b58b6eab1f6de5396a2abcdaaeb168f9ed4eccbb594e6ac393b02078cbd", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a68c4261e299597909e03e6f8ff5a13876f5caadaddd0d23af0d0a61afcc5d84"}, "req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [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", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "swoosh": {:hex, :swoosh, "1.22.0", "0d65a95f89aedb5011af13295742294e309b4b4aaca556858d81e3b372b58abc", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c01ced23d8786d1ee1a03e4c16574290b2ccd6267beb8c81d081c4a34574ef6e"}, + "swoosh": {:hex, :swoosh, "1.22.1", "8450ac62d0a7cb82f0765592037cab2d30cbc7801acd879f77b8f672a9b49f58", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "13795cd69e137c7a6b99850b938177fa3713bd6b95e92b3bdcdb29b70e88868e"}, "tailwind": {:hex, :tailwind, "0.4.1", "e7bcc222fe96a1e55f948e76d13dd84a1a7653fb051d2a167135db3b4b08d3e9", [:mix], [], "hexpm", "6249d4f9819052911120dbdbe9e532e6bd64ea23476056adb7f730aa25c220d1"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, diff --git a/guides/installation.md b/guides/installation.md index e564589..7c4b428 100644 --- a/guides/installation.md +++ b/guides/installation.md @@ -1,5 +1,11 @@ # Installation +![Hex.pm License](https://img.shields.io/hexpm/l/corex) +![Hex.pm Version](https://img.shields.io/hexpm/v/corex) +[![Coverage Status](https://coveralls.io/repos/github/corex-ui/corex/badge.svg?branch=corex-install)](https://coveralls.io/github/corex-ui/corex?branch=corex-install) +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/corex-ui/corex/elixir.yml) +![GitHub branch check runs](https://img.shields.io/github/check-runs/corex-ui/corex/main) + ## Introduction Corex is an accessible and unstyled UI components library written in Elixir and TypeScript that integrates [Zag.js](https://zagjs.com/) state machines into the Phoenix Framework. @@ -61,8 +67,6 @@ mix local.phx mix phx.new my_app ``` -## Dependencies - Add `corex` to your `mix.exs` dependencies: ```elixir diff --git a/lib/components/accordion.ex b/lib/components/accordion.ex index 967e85b..5dc411c 100644 --- a/lib/components/accordion.ex +++ b/lib/components/accordion.ex @@ -290,8 +290,10 @@ defmodule Corex.Accordion do @doc type: :component use Phoenix.Component - alias Corex.Accordion.Anatomy.{Props, Root, Item} + alias Corex.Accordion.Anatomy.{Item, Props, Root} alias Corex.Accordion.Connect + alias Phoenix.LiveView + alias Phoenix.LiveView.JS import Corex.Helpers, only: [validate_value!: 1] @doc """ @@ -647,7 +649,7 @@ defmodule Corex.Accordion do """ def set_value(accordion_id, value) when is_binary(accordion_id) do - Phoenix.LiveView.JS.dispatch("phx:accordion:set-value", + JS.dispatch("phx:accordion:set-value", to: "##{accordion_id}", detail: %{value: validate_value!(value)}, bubbles: false @@ -667,7 +669,7 @@ defmodule Corex.Accordion do """ def set_value(socket, accordion_id, value) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(accordion_id) do - Phoenix.LiveView.push_event(socket, "accordion_set_value", %{ + LiveView.push_event(socket, "accordion_set_value", %{ accordion_id: accordion_id, value: validate_value!(value) }) diff --git a/lib/components/accordion/connect.ex b/lib/components/accordion/connect.ex index 1e53da4..5fc89b5 100644 --- a/lib/components/accordion/connect.ex +++ b/lib/components/accordion/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.Accordion.Connect do @moduledoc false - alias Corex.Accordion.Anatomy.{Props, Root, Item} + alias Corex.Accordion.Anatomy.{Item, Props, Root} import Corex.Helpers, only: [validate_value!: 1] defp data_attr(true), do: "" diff --git a/lib/components/angle_slider.ex b/lib/components/angle_slider.ex index 3b25414..ff2c892 100644 --- a/lib/components/angle_slider.ex +++ b/lib/components/angle_slider.ex @@ -112,20 +112,22 @@ defmodule Corex.AngleSlider do use Phoenix.Component alias Corex.AngleSlider.Anatomy.{ + Control, + HiddenInput, + Label, + Marker, + MarkerGroup, Props, Root, - Label, - HiddenInput, - Control, + Text, Thumb, - ValueText, Value, - Text, - MarkerGroup, - Marker + ValueText } alias Corex.AngleSlider.Connect + alias Phoenix.LiveView + alias Phoenix.LiveView.JS attr(:id, :string, required: false, doc: "The id of the angle slider") attr(:value, :float, default: 0.0, doc: "The value or controlled value in degrees") @@ -224,7 +226,7 @@ defmodule Corex.AngleSlider do """ def set_value(angle_slider_id, value) when is_binary(angle_slider_id) and is_number(value) do - Phoenix.LiveView.JS.dispatch("phx:angle-slider:set-value", + JS.dispatch("phx:angle-slider:set-value", to: "##{angle_slider_id}", detail: %{value: value}, bubbles: false @@ -254,7 +256,7 @@ defmodule Corex.AngleSlider do value end - Phoenix.LiveView.push_event(socket, "angle_slider_set_value", %{ + LiveView.push_event(socket, "angle_slider_set_value", %{ angle_slider_id: angle_slider_id, value: angle }) diff --git a/lib/components/angle_slider/connect.ex b/lib/components/angle_slider/connect.ex index 9d70126..6bb242c 100644 --- a/lib/components/angle_slider/connect.ex +++ b/lib/components/angle_slider/connect.ex @@ -1,17 +1,17 @@ defmodule Corex.AngleSlider.Connect do @moduledoc false alias Corex.AngleSlider.Anatomy.{ + Control, + HiddenInput, + Label, + Marker, + MarkerGroup, Props, Root, - Label, - HiddenInput, - Control, + Text, Thumb, - ValueText, Value, - Text, - MarkerGroup, - Marker + ValueText } defp data_attr(true), do: "" diff --git a/lib/components/avatar.ex b/lib/components/avatar.ex index fbce1b0..65b9368 100644 --- a/lib/components/avatar.ex +++ b/lib/components/avatar.ex @@ -46,7 +46,7 @@ defmodule Corex.Avatar do @doc type: :component use Phoenix.Component - alias Corex.Avatar.Anatomy.{Props, Root, Image, Fallback, Skeleton} + alias Corex.Avatar.Anatomy.{Fallback, Image, Props, Root, Skeleton} alias Corex.Avatar.Connect attr(:id, :string, required: false, doc: "The id of the avatar") diff --git a/lib/components/avatar/connect.ex b/lib/components/avatar/connect.ex index d4bad30..c602213 100644 --- a/lib/components/avatar/connect.ex +++ b/lib/components/avatar/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.Avatar.Connect do @moduledoc false - alias Corex.Avatar.Anatomy.{Props, Root, Image, Fallback, Skeleton} + alias Corex.Avatar.Anatomy.{Fallback, Image, Props, Root, Skeleton} @spec props(Props.t()) :: map() def props(assigns) do diff --git a/lib/components/carousel.ex b/lib/components/carousel.ex index b2e6cc7..10bc19f 100644 --- a/lib/components/carousel.ex +++ b/lib/components/carousel.ex @@ -57,15 +57,15 @@ defmodule Corex.Carousel do use Phoenix.Component alias Corex.Carousel.Anatomy.{ - Props, - Root, Control, - ItemGroup, + Indicator, + IndicatorGroup, Item, - PrevTrigger, + ItemGroup, NextTrigger, - IndicatorGroup, - Indicator + PrevTrigger, + Props, + Root } alias Corex.Carousel.Connect diff --git a/lib/components/carousel/connect.ex b/lib/components/carousel/connect.ex index 4d3f3f0..fe9d15e 100644 --- a/lib/components/carousel/connect.ex +++ b/lib/components/carousel/connect.ex @@ -1,15 +1,15 @@ defmodule Corex.Carousel.Connect do @moduledoc false alias Corex.Carousel.Anatomy.{ - Props, - Root, Control, - ItemGroup, + Indicator, + IndicatorGroup, Item, - PrevTrigger, + ItemGroup, NextTrigger, - IndicatorGroup, - Indicator + PrevTrigger, + Props, + Root } defp data_attr(true), do: "" diff --git a/lib/components/checkbox.ex b/lib/components/checkbox.ex index 24ea593..f12e6a1 100644 --- a/lib/components/checkbox.ex +++ b/lib/components/checkbox.ex @@ -225,8 +225,11 @@ defmodule Corex.Checkbox do @doc type: :component use Phoenix.Component - alias Corex.Checkbox.Anatomy.{Props, Root, HiddenInput, Control, Label, Indicator} + alias Corex.Checkbox.Anatomy.{Control, HiddenInput, Indicator, Label, Props, Root} alias Corex.Checkbox.Connect + alias Phoenix.HTML.Form + alias Phoenix.LiveView + alias Phoenix.LiveView.JS @doc """ Renders a checkbox component. @@ -331,7 +334,7 @@ defmodule Corex.Checkbox do |> assign(:errors, Enum.map(errors, &Corex.Gettext.translate_error(&1))) |> assign_new(:id, fn -> field.id end) |> assign_new(:name, fn -> field.name end) - |> assign(:checked, Phoenix.HTML.Form.normalize_value("checkbox", field.value)) + |> assign(:checked, Form.normalize_value("checkbox", field.value)) |> assign_new(:form, fn -> field.form.id end) checkbox(assigns) @@ -402,7 +405,7 @@ defmodule Corex.Checkbox do """ def set_checked(checkbox_id, checked) when is_binary(checkbox_id) and is_boolean(checked) do - Phoenix.LiveView.JS.dispatch("phx:checkbox:set-checked", + JS.dispatch("phx:checkbox:set-checked", to: "##{checkbox_id}", detail: %{checked: checked}, bubbles: false @@ -423,7 +426,7 @@ defmodule Corex.Checkbox do def set_checked(socket, checkbox_id, checked) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(checkbox_id) and is_boolean(checked) do - Phoenix.LiveView.push_event(socket, "checkbox_set_checked", %{ + LiveView.push_event(socket, "checkbox_set_checked", %{ checkbox_id: checkbox_id, checked: checked }) @@ -440,7 +443,7 @@ defmodule Corex.Checkbox do """ def toggle_checked(checkbox_id) when is_binary(checkbox_id) do - Phoenix.LiveView.JS.dispatch("phx:checkbox:toggle-checked", + JS.dispatch("phx:checkbox:toggle-checked", to: "##{checkbox_id}", bubbles: false ) @@ -459,7 +462,7 @@ defmodule Corex.Checkbox do """ def toggle_checked(socket, checkbox_id) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(checkbox_id) do - Phoenix.LiveView.push_event(socket, "checkbox_toggle_checked", %{ + LiveView.push_event(socket, "checkbox_toggle_checked", %{ checkbox_id: checkbox_id }) end diff --git a/lib/components/checkbox/connect.ex b/lib/components/checkbox/connect.ex index 5bba3a2..5a86f4b 100644 --- a/lib/components/checkbox/connect.ex +++ b/lib/components/checkbox/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.Checkbox.Connect do @moduledoc false - alias Corex.Checkbox.Anatomy.{Props, Root, HiddenInput, Indicator, Control, Label} + alias Corex.Checkbox.Anatomy.{Control, HiddenInput, Indicator, Label, Props, Root} defp data_attr(true), do: "" defp data_attr(false), do: nil diff --git a/lib/components/clipboard.ex b/lib/components/clipboard.ex index 0168ff1..469b2a0 100644 --- a/lib/components/clipboard.ex +++ b/lib/components/clipboard.ex @@ -97,8 +97,10 @@ defmodule Corex.Clipboard do @doc type: :component use Phoenix.Component - alias Corex.Clipboard.Anatomy.{Props, Root, Label, Control, Input, Trigger} + alias Corex.Clipboard.Anatomy.{Control, Input, Label, Props, Root, Trigger} alias Corex.Clipboard.Connect + alias Phoenix.LiveView + alias Phoenix.LiveView.JS @doc """ Renders a clipboard component. @@ -231,7 +233,7 @@ defmodule Corex.Clipboard do """ def copy(clipboard_id) when is_binary(clipboard_id) do - Phoenix.LiveView.JS.dispatch("phx:clipboard:copy", + JS.dispatch("phx:clipboard:copy", to: "##{clipboard_id}", bubbles: false ) @@ -250,7 +252,7 @@ defmodule Corex.Clipboard do """ def copy(socket, clipboard_id) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(clipboard_id) do - Phoenix.LiveView.push_event(socket, "clipboard_copy", %{ + LiveView.push_event(socket, "clipboard_copy", %{ clipboard_id: clipboard_id }) end @@ -266,7 +268,7 @@ defmodule Corex.Clipboard do """ def set_value(clipboard_id, value) when is_binary(clipboard_id) and is_binary(value) do - Phoenix.LiveView.JS.dispatch("phx:clipboard:set-value", + JS.dispatch("phx:clipboard:set-value", to: "##{clipboard_id}", detail: %{value: value}, bubbles: false @@ -287,7 +289,7 @@ defmodule Corex.Clipboard do def set_value(socket, clipboard_id, value) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(clipboard_id) and is_binary(value) do - Phoenix.LiveView.push_event(socket, "clipboard_set_value", %{ + LiveView.push_event(socket, "clipboard_set_value", %{ clipboard_id: clipboard_id, value: value }) diff --git a/lib/components/clipboard/connect.ex b/lib/components/clipboard/connect.ex index a79be68..2510e73 100644 --- a/lib/components/clipboard/connect.ex +++ b/lib/components/clipboard/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.Clipboard.Connect do @moduledoc false - alias Corex.Clipboard.Anatomy.{Props, Root, Label, Control, Input, Trigger} + alias Corex.Clipboard.Anatomy.{Control, Input, Label, Props, Root, Trigger} defp data_attr(true), do: "" defp data_attr(false), do: nil diff --git a/lib/components/code.ex b/lib/components/code.ex index a6b89b5..34fe73b 100644 --- a/lib/components/code.ex +++ b/lib/components/code.ex @@ -133,9 +133,8 @@ defmodule Corex.Code do defp lexer_for(language) do name = to_string(language) - registry = Module.concat(["Elixir", "Makeup", "Registry"]) - case apply(registry, :fetch_lexer_by_name, [name]) do + case Makeup.Registry.fetch_lexer_by_name(name) do {:ok, {lexer, _opts}} -> lexer :error -> nil end @@ -149,8 +148,7 @@ defmodule Corex.Code do |> Phoenix.HTML.safe_to_string() lexer -> - makeup = Module.concat(["Elixir", "Makeup"]) - apply(makeup, :highlight_inner_html, [assigns.code, [lexer: lexer]]) + Makeup.highlight_inner_html(assigns.code, lexer: lexer) end end end diff --git a/lib/components/collapsible.ex b/lib/components/collapsible.ex index f8b2959..afe4ce9 100644 --- a/lib/components/collapsible.ex +++ b/lib/components/collapsible.ex @@ -91,8 +91,10 @@ defmodule Corex.Collapsible do @doc type: :component use Phoenix.Component - alias Corex.Collapsible.Anatomy.{Props, Root, Trigger, Content} + alias Corex.Collapsible.Anatomy.{Content, Props, Root, Trigger} alias Corex.Collapsible.Connect + alias Phoenix.LiveView + alias Phoenix.LiveView.JS @doc """ Renders a collapsible component. @@ -189,7 +191,7 @@ defmodule Corex.Collapsible do """ def set_open(collapsible_id, open) when is_binary(collapsible_id) and is_boolean(open) do - Phoenix.LiveView.JS.dispatch("phx:collapsible:set-open", + JS.dispatch("phx:collapsible:set-open", to: "##{collapsible_id}", detail: %{open: open}, bubbles: false @@ -210,7 +212,7 @@ defmodule Corex.Collapsible do def set_open(socket, collapsible_id, open) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(collapsible_id) and is_boolean(open) do - Phoenix.LiveView.push_event(socket, "collapsible_set_open", %{ + LiveView.push_event(socket, "collapsible_set_open", %{ collapsible_id: collapsible_id, open: open }) diff --git a/lib/components/collapsible/connect.ex b/lib/components/collapsible/connect.ex index cc9be06..a622c50 100644 --- a/lib/components/collapsible/connect.ex +++ b/lib/components/collapsible/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.Collapsible.Connect do @moduledoc false - alias Corex.Collapsible.Anatomy.{Props, Root, Trigger, Content} + alias Corex.Collapsible.Anatomy.{Content, Props, Root, Trigger} defp data_attr(true), do: "" defp data_attr(false), do: nil diff --git a/lib/components/color_picker.ex b/lib/components/color_picker.ex index 5d2f1ba..a968a14 100644 --- a/lib/components/color_picker.ex +++ b/lib/components/color_picker.ex @@ -57,22 +57,24 @@ defmodule Corex.ColorPicker do use Phoenix.Component alias Corex.ColorPicker.Anatomy.{ - Props, - Root, - Label, - HiddenInput, + Content, Control, - Trigger, + HiddenInput, + Label, Positioner, - Content, - TransparencyGrid, + PresetSwatch, + Props, + Root, Swatch, SwatchTrigger, - PresetSwatch + TransparencyGrid, + Trigger } alias Corex.ColorPicker.Connect alias Corex.ColorPicker.Initial + alias Phoenix.LiveView + alias Phoenix.LiveView.JS attr(:id, :string, required: false) @@ -329,7 +331,7 @@ defmodule Corex.ColorPicker do """ def set_open(color_picker_id, open) when is_binary(color_picker_id) and is_boolean(open) do - Phoenix.LiveView.JS.dispatch("phx:color-picker:set-open", + JS.dispatch("phx:color-picker:set-open", to: "##{color_picker_id}", detail: %{open: open}, bubbles: false @@ -342,7 +344,7 @@ defmodule Corex.ColorPicker do """ def set_open(socket, color_picker_id, open) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(color_picker_id) do - Phoenix.LiveView.push_event(socket, "color_picker_set_open", %{ + LiveView.push_event(socket, "color_picker_set_open", %{ color_picker_id: color_picker_id, open: open }) @@ -354,7 +356,7 @@ defmodule Corex.ColorPicker do Value can be any color string (e.g. `"#ff0000"`, `"rgba(255, 0, 0, 1)"`). """ def set_value(color_picker_id, value) when is_binary(color_picker_id) and is_binary(value) do - Phoenix.LiveView.JS.dispatch("phx:color-picker:set-value", + JS.dispatch("phx:color-picker:set-value", to: "##{color_picker_id}", detail: %{value: value}, bubbles: false @@ -367,7 +369,7 @@ defmodule Corex.ColorPicker do """ def set_value(socket, color_picker_id, value) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(color_picker_id) do - Phoenix.LiveView.push_event(socket, "color_picker_set_value", %{ + LiveView.push_event(socket, "color_picker_set_value", %{ color_picker_id: color_picker_id, value: to_string(value) }) @@ -380,7 +382,7 @@ defmodule Corex.ColorPicker do """ def set_format(color_picker_id, format) when is_binary(color_picker_id) and format in ["rgba", "hsla", "hsba", "hex"] do - Phoenix.LiveView.JS.dispatch("phx:color-picker:set-format", + JS.dispatch("phx:color-picker:set-format", to: "##{color_picker_id}", detail: %{format: format}, bubbles: false @@ -393,7 +395,7 @@ defmodule Corex.ColorPicker do """ def set_format(socket, color_picker_id, format) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(color_picker_id) do - Phoenix.LiveView.push_event(socket, "color_picker_set_format", %{ + LiveView.push_event(socket, "color_picker_set_format", %{ color_picker_id: color_picker_id, format: to_string(format) }) diff --git a/lib/components/color_picker/connect.ex b/lib/components/color_picker/connect.ex index 27d1b43..e9760c0 100644 --- a/lib/components/color_picker/connect.ex +++ b/lib/components/color_picker/connect.ex @@ -1,18 +1,18 @@ defmodule Corex.ColorPicker.Connect do @moduledoc false alias Corex.ColorPicker.Anatomy.{ - Props, - Root, - Label, - HiddenInput, + Content, Control, - Trigger, + HiddenInput, + Label, Positioner, - Content, - TransparencyGrid, + PresetSwatch, + Props, + Root, Swatch, SwatchTrigger, - PresetSwatch + TransparencyGrid, + Trigger } import Corex.Helpers, only: [get_boolean: 1] diff --git a/lib/components/combobox.ex b/lib/components/combobox.ex index 8427018..cd48cc4 100644 --- a/lib/components/combobox.ex +++ b/lib/components/combobox.ex @@ -225,8 +225,8 @@ defmodule Corex.Combobox do @doc type: :component use Phoenix.Component + alias Corex.Combobox.Anatomy.{Content, Control, Input, Label, Positioner, Props, Root} alias Corex.Combobox.Connect - alias Corex.Combobox.Anatomy.{Props, Root, Label, Control, Input, Positioner, Content} @doc """ Renders a combobox component. diff --git a/lib/components/combobox/connect.ex b/lib/components/combobox/connect.ex index 883e457..76cb4f8 100644 --- a/lib/components/combobox/connect.ex +++ b/lib/components/combobox/connect.ex @@ -1,7 +1,6 @@ defmodule Corex.Combobox.Connect do @moduledoc false - alias Corex.Combobox.Anatomy.Control - alias Corex.Combobox.Anatomy.{Props, Root, Label, Input, Positioner, Content} + alias Corex.Combobox.Anatomy.{Content, Control, Input, Label, Positioner, Props, Root} import Corex.Helpers, only: [get_boolean: 1, get_default_boolean: 2, get_boolean: 2, validate_value!: 1] diff --git a/lib/components/date-picker.ex b/lib/components/date-picker.ex index 04855c1..b36feaa 100644 --- a/lib/components/date-picker.ex +++ b/lib/components/date-picker.ex @@ -251,6 +251,8 @@ defmodule Corex.DatePicker do use Phoenix.Component alias Corex.DatePicker.{Anatomy, Connect} + alias Phoenix.LiveView + alias Phoenix.LiveView.JS @doc """ Renders a date picker component. @@ -605,7 +607,7 @@ defmodule Corex.DatePicker do """ def set_value(date_picker_id, value) when is_binary(date_picker_id) and is_binary(value) do - Phoenix.LiveView.JS.dispatch("phx:date-picker:set-value", + JS.dispatch("phx:date-picker:set-value", to: "##{date_picker_id}", detail: %{value: value}, bubbles: false @@ -626,7 +628,7 @@ defmodule Corex.DatePicker do def set_value(socket, date_picker_id, value) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(date_picker_id) and is_binary(value) do - Phoenix.LiveView.push_event(socket, "date_picker_set_value", %{ + LiveView.push_event(socket, "date_picker_set_value", %{ date_picker_id: date_picker_id, value: value }) diff --git a/lib/components/date-picker/connect.ex b/lib/components/date-picker/connect.ex index df4f356..79b44ad 100644 --- a/lib/components/date-picker/connect.ex +++ b/lib/components/date-picker/connect.ex @@ -1,14 +1,14 @@ defmodule Corex.DatePicker.Connect do @moduledoc false alias Corex.DatePicker.Anatomy.{ - Props, - Root, - Label, + Content, Control, Input, - Trigger, + Label, Positioner, - Content + Props, + Root, + Trigger } defp data_attr(true), do: "" diff --git a/lib/components/dialog.ex b/lib/components/dialog.ex index e78c651..9f85b0b 100644 --- a/lib/components/dialog.ex +++ b/lib/components/dialog.ex @@ -127,17 +127,19 @@ defmodule Corex.Dialog do use Phoenix.Component alias Corex.Dialog.Anatomy.{ - Props, - Trigger, Backdrop, - Positioner, + CloseTrigger, Content, - Title, Description, - CloseTrigger + Positioner, + Props, + Title, + Trigger } alias Corex.Dialog.Connect + alias Phoenix.LiveView + alias Phoenix.LiveView.JS @doc """ Renders a dialog component. @@ -329,7 +331,7 @@ defmodule Corex.Dialog do """ def set_open(dialog_id, open) when is_binary(dialog_id) and is_boolean(open) do - Phoenix.LiveView.JS.dispatch("phx:dialog:set-open", + JS.dispatch("phx:dialog:set-open", to: "##{dialog_id}", detail: %{open: open}, bubbles: false @@ -350,7 +352,7 @@ defmodule Corex.Dialog do def set_open(socket, dialog_id, open) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(dialog_id) and is_boolean(open) do - Phoenix.LiveView.push_event(socket, "dialog_set_open", %{ + LiveView.push_event(socket, "dialog_set_open", %{ dialog_id: dialog_id, open: open }) diff --git a/lib/components/dialog/connect.ex b/lib/components/dialog/connect.ex index 06a3161..031d908 100644 --- a/lib/components/dialog/connect.ex +++ b/lib/components/dialog/connect.ex @@ -1,14 +1,14 @@ defmodule Corex.Dialog.Connect do @moduledoc false alias Corex.Dialog.Anatomy.{ - Props, - Trigger, Backdrop, - Positioner, + CloseTrigger, Content, - Title, Description, - CloseTrigger + Positioner, + Props, + Title, + Trigger } defp data_attr(true), do: "" diff --git a/lib/components/editable.ex b/lib/components/editable.ex index 8c019b9..c172c7f 100644 --- a/lib/components/editable.ex +++ b/lib/components/editable.ex @@ -60,16 +60,16 @@ defmodule Corex.Editable do use Phoenix.Component alias Corex.Editable.Anatomy.{ - Props, - Root, Area, - Label, + CancelTrigger, + EditTrigger, Input, + Label, Preview, - EditTrigger, - Triggers, + Props, + Root, SubmitTrigger, - CancelTrigger + Triggers } alias Corex.Editable.Connect diff --git a/lib/components/editable/connect.ex b/lib/components/editable/connect.ex index cd8ae57..3a0fbe8 100644 --- a/lib/components/editable/connect.ex +++ b/lib/components/editable/connect.ex @@ -1,17 +1,17 @@ defmodule Corex.Editable.Connect do @moduledoc false alias Corex.Editable.Anatomy.{ - Props, - Root, Area, - Label, + CancelTrigger, + Control, + EditTrigger, Input, + Label, Preview, - EditTrigger, - Control, - Triggers, + Props, + Root, SubmitTrigger, - CancelTrigger + Triggers } defp data_attr(true), do: "" diff --git a/lib/components/floating_panel.ex b/lib/components/floating_panel.ex index 588f8dc..c9a4858 100644 --- a/lib/components/floating_panel.ex +++ b/lib/components/floating_panel.ex @@ -75,19 +75,19 @@ defmodule Corex.FloatingPanel do use Phoenix.Component alias Corex.FloatingPanel.Anatomy.{ - Props, - Root, - Trigger, - Positioner, - Content, - Title, - Header, Body, - DragTrigger, - ResizeTrigger, CloseTrigger, + Content, Control, - StageTrigger + DragTrigger, + Header, + Positioner, + Props, + ResizeTrigger, + Root, + StageTrigger, + Title, + Trigger } alias Corex.FloatingPanel.Connect diff --git a/lib/components/floating_panel/connect.ex b/lib/components/floating_panel/connect.ex index b2b759a..10fb986 100644 --- a/lib/components/floating_panel/connect.ex +++ b/lib/components/floating_panel/connect.ex @@ -1,19 +1,19 @@ defmodule Corex.FloatingPanel.Connect do @moduledoc false alias Corex.FloatingPanel.Anatomy.{ - Props, - Root, - Trigger, - Positioner, - Content, - Title, - Header, Body, - DragTrigger, - ResizeTrigger, CloseTrigger, + Content, Control, - StageTrigger + DragTrigger, + Header, + Positioner, + Props, + ResizeTrigger, + Root, + StageTrigger, + Title, + Trigger } defp data_attr(true), do: "" diff --git a/lib/components/listbox.ex b/lib/components/listbox.ex index 4b6d68f..e516b39 100644 --- a/lib/components/listbox.ex +++ b/lib/components/listbox.ex @@ -149,17 +149,17 @@ defmodule Corex.Listbox do use Phoenix.Component alias Corex.Listbox.Anatomy.{ - Props, - Root, - Label, - ValueText, - Input, Content, + Input, + Item, ItemGroup, ItemGroupLabel, - Item, + ItemIndicator, ItemText, - ItemIndicator + Label, + Props, + Root, + ValueText } alias Corex.Listbox.Connect diff --git a/lib/components/listbox/connect.ex b/lib/components/listbox/connect.ex index 3d2d6a0..239b0b2 100644 --- a/lib/components/listbox/connect.ex +++ b/lib/components/listbox/connect.ex @@ -1,17 +1,17 @@ defmodule Corex.Listbox.Connect do @moduledoc false alias Corex.Listbox.Anatomy.{ - Props, - Root, - Label, - ValueText, - Input, Content, + Input, + Item, ItemGroup, ItemGroupLabel, - Item, + ItemIndicator, ItemText, - ItemIndicator + Label, + Props, + Root, + ValueText } import Corex.Helpers, only: [validate_value!: 1] diff --git a/lib/components/marquee.ex b/lib/components/marquee.ex index de1dff3..89966f3 100644 --- a/lib/components/marquee.ex +++ b/lib/components/marquee.ex @@ -76,8 +76,10 @@ defmodule Corex.Marquee do @doc type: :component use Phoenix.Component - alias Corex.Marquee.Anatomy.{Props, Root, Edge, Viewport, Content, Item} + alias Corex.Marquee.Anatomy.{Content, Edge, Item, Props, Root, Viewport} alias Corex.Marquee.Connect + alias Phoenix.LiveView + alias Phoenix.LiveView.JS attr(:id, :string, required: false) @@ -217,7 +219,7 @@ defmodule Corex.Marquee do Pauses the marquee from client-side. Returns a `Phoenix.LiveView.JS` command. """ def pause(marquee_id) when is_binary(marquee_id) do - Phoenix.LiveView.JS.dispatch("phx:marquee:pause", to: "##{marquee_id}", bubbles: false) + JS.dispatch("phx:marquee:pause", to: "##{marquee_id}", bubbles: false) end @doc """ @@ -225,14 +227,14 @@ defmodule Corex.Marquee do """ def pause(socket, marquee_id) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(marquee_id) do - Phoenix.LiveView.push_event(socket, "marquee_pause", %{marquee_id: marquee_id}) + LiveView.push_event(socket, "marquee_pause", %{marquee_id: marquee_id}) end @doc """ Resumes the marquee from client-side. Returns a `Phoenix.LiveView.JS` command. """ def resume(marquee_id) when is_binary(marquee_id) do - Phoenix.LiveView.JS.dispatch("phx:marquee:resume", to: "##{marquee_id}", bubbles: false) + JS.dispatch("phx:marquee:resume", to: "##{marquee_id}", bubbles: false) end @doc """ @@ -240,14 +242,14 @@ defmodule Corex.Marquee do """ def resume(socket, marquee_id) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(marquee_id) do - Phoenix.LiveView.push_event(socket, "marquee_resume", %{marquee_id: marquee_id}) + LiveView.push_event(socket, "marquee_resume", %{marquee_id: marquee_id}) end @doc """ Toggles the pause state from client-side. Returns a `Phoenix.LiveView.JS` command. """ def toggle_pause(marquee_id) when is_binary(marquee_id) do - Phoenix.LiveView.JS.dispatch("phx:marquee:toggle-pause", to: "##{marquee_id}", bubbles: false) + JS.dispatch("phx:marquee:toggle-pause", to: "##{marquee_id}", bubbles: false) end @doc """ @@ -255,6 +257,6 @@ defmodule Corex.Marquee do """ def toggle_pause(socket, marquee_id) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(marquee_id) do - Phoenix.LiveView.push_event(socket, "marquee_toggle_pause", %{marquee_id: marquee_id}) + LiveView.push_event(socket, "marquee_toggle_pause", %{marquee_id: marquee_id}) end end diff --git a/lib/components/marquee/connect.ex b/lib/components/marquee/connect.ex index 71ef9c8..c2e840e 100644 --- a/lib/components/marquee/connect.ex +++ b/lib/components/marquee/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.Marquee.Connect do @moduledoc false - alias Corex.Marquee.Anatomy.{Props, Root, Edge, Viewport, Content, Item} + alias Corex.Marquee.Anatomy.{Content, Edge, Item, Props, Root, Viewport} defp data_attr(true), do: "" defp data_attr(false), do: nil diff --git a/lib/components/menu.ex b/lib/components/menu.ex index 59721da..036c02e 100644 --- a/lib/components/menu.ex +++ b/lib/components/menu.ex @@ -305,8 +305,10 @@ defmodule Corex.Menu do @doc type: :component use Phoenix.Component - alias Corex.Menu.Anatomy.{Props, Root, Trigger, Item, Group} + alias Corex.Menu.Anatomy.{Group, Item, Props, Root, Trigger} alias Corex.Menu.Connect + alias Phoenix.LiveView + alias Phoenix.LiveView.JS @doc """ Renders a menu component. @@ -655,7 +657,7 @@ defmodule Corex.Menu do """ def set_open(menu_id, open) when is_binary(menu_id) do - Phoenix.LiveView.JS.dispatch("phx:menu:set-open", + JS.dispatch("phx:menu:set-open", to: "[id=\"menu:#{menu_id}\"]", detail: %{open: open}, bubbles: false @@ -675,7 +677,7 @@ defmodule Corex.Menu do """ def set_open(socket, menu_id, open) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(menu_id) do - Phoenix.LiveView.push_event(socket, "menu_set_open", %{ + LiveView.push_event(socket, "menu_set_open", %{ menu_id: menu_id, open: open }) diff --git a/lib/components/menu/connect.ex b/lib/components/menu/connect.ex index 597b9ab..6e86fd0 100644 --- a/lib/components/menu/connect.ex +++ b/lib/components/menu/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.Menu.Connect do @moduledoc false - alias Corex.Menu.Anatomy.{Props, Root, Trigger, Item, Group} + alias Corex.Menu.Anatomy.{Group, Item, Props, Root, Trigger} defp data_attr(true), do: "" defp data_attr(false), do: nil diff --git a/lib/components/native_input.ex b/lib/components/native_input.ex index 88e0b2d..64130c1 100644 --- a/lib/components/native_input.ex +++ b/lib/components/native_input.ex @@ -86,6 +86,7 @@ defmodule Corex.NativeInput do @doc type: :component use Phoenix.Component + alias Phoenix.HTML.Form @types ~w(text textarea date datetime-local time month week email url tel search color number password checkbox radio select) @@ -149,7 +150,7 @@ defmodule Corex.NativeInput do assigns |> assign_new(:id, fn -> "native-input-#{System.unique_integer([:positive])}" end) |> assign_new(:checked, fn -> - Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value]) + Form.normalize_value("checkbox", assigns[:value]) end) ~H""" diff --git a/lib/components/number_input.ex b/lib/components/number_input.ex index 0624fd1..b4311c2 100644 --- a/lib/components/number_input.ex +++ b/lib/components/number_input.ex @@ -73,15 +73,15 @@ defmodule Corex.NumberInput do use Phoenix.Component alias Corex.NumberInput.Anatomy.{ - Props, - Root, - Label, Control, - Input, - TriggerGroup, DecrementTrigger, IncrementTrigger, - Scrubber + Input, + Label, + Props, + Root, + Scrubber, + TriggerGroup } alias Corex.NumberInput.Connect diff --git a/lib/components/number_input/connect.ex b/lib/components/number_input/connect.ex index ae35c52..4a1f9a4 100644 --- a/lib/components/number_input/connect.ex +++ b/lib/components/number_input/connect.ex @@ -1,15 +1,15 @@ defmodule Corex.NumberInput.Connect do @moduledoc false alias Corex.NumberInput.Anatomy.{ - Props, - Root, - Label, Control, - Input, - TriggerGroup, DecrementTrigger, IncrementTrigger, - Scrubber + Input, + Label, + Props, + Root, + Scrubber, + TriggerGroup } defp data_attr(true), do: "" diff --git a/lib/components/password_input.ex b/lib/components/password_input.ex index 30cc98d..4fbc116 100644 --- a/lib/components/password_input.ex +++ b/lib/components/password_input.ex @@ -189,13 +189,13 @@ defmodule Corex.PasswordInput do use Phoenix.Component alias Corex.PasswordInput.Anatomy.{ - Props, - Root, - Label, Control, + Indicator, Input, - VisibilityTrigger, - Indicator + Label, + Props, + Root, + VisibilityTrigger } alias Corex.PasswordInput.Connect diff --git a/lib/components/password_input/connect.ex b/lib/components/password_input/connect.ex index e9f986a..e71bf3f 100644 --- a/lib/components/password_input/connect.ex +++ b/lib/components/password_input/connect.ex @@ -1,13 +1,13 @@ defmodule Corex.PasswordInput.Connect do @moduledoc false alias Corex.PasswordInput.Anatomy.{ - Props, - Root, - Label, Control, + Indicator, Input, - VisibilityTrigger, - Indicator + Label, + Props, + Root, + VisibilityTrigger } defp data_attr(true), do: "" diff --git a/lib/components/pin_input.ex b/lib/components/pin_input.ex index 5f313ba..4d0c9a5 100644 --- a/lib/components/pin_input.ex +++ b/lib/components/pin_input.ex @@ -46,7 +46,7 @@ defmodule Corex.PinInput do @doc type: :component use Phoenix.Component - alias Corex.PinInput.Anatomy.{Props, Root, Label, HiddenInput, Control, Input} + alias Corex.PinInput.Anatomy.{Control, HiddenInput, Input, Label, Props, Root} alias Corex.PinInput.Connect import Corex.Helpers, only: [validate_value!: 1] diff --git a/lib/components/pin_input/connect.ex b/lib/components/pin_input/connect.ex index 598e1dc..6b0c1be 100644 --- a/lib/components/pin_input/connect.ex +++ b/lib/components/pin_input/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.PinInput.Connect do @moduledoc false - alias Corex.PinInput.Anatomy.{Props, Root, Label, HiddenInput, Control, Input} + alias Corex.PinInput.Anatomy.{Control, HiddenInput, Input, Label, Props, Root} import Corex.Helpers, only: [validate_value!: 1] defp data_attr(true), do: "" diff --git a/lib/components/radio_group.ex b/lib/components/radio_group.ex index 5f33e8f..4b76072 100644 --- a/lib/components/radio_group.ex +++ b/lib/components/radio_group.ex @@ -60,14 +60,14 @@ defmodule Corex.RadioGroup do use Phoenix.Component alias Corex.RadioGroup.Anatomy.{ - Props, - Root, - Label, Indicator, Item, - ItemText, ItemControl, - ItemHiddenInput + ItemHiddenInput, + ItemText, + Label, + Props, + Root } alias Corex.RadioGroup.Connect diff --git a/lib/components/radio_group/connect.ex b/lib/components/radio_group/connect.ex index 3d47955..6df850f 100644 --- a/lib/components/radio_group/connect.ex +++ b/lib/components/radio_group/connect.ex @@ -1,14 +1,14 @@ defmodule Corex.RadioGroup.Connect do @moduledoc false alias Corex.RadioGroup.Anatomy.{ - Props, - Root, - Label, Indicator, Item, - ItemText, ItemControl, - ItemHiddenInput + ItemHiddenInput, + ItemText, + Label, + Props, + Root } defp data_attr(true), do: "" diff --git a/lib/components/select.ex b/lib/components/select.ex index 81c5f05..1d303ca 100644 --- a/lib/components/select.ex +++ b/lib/components/select.ex @@ -426,8 +426,8 @@ defmodule Corex.Select do ''' use Phoenix.Component + alias Corex.Select.Anatomy.{Content, Control, Label, Positioner, Props, Root} alias Corex.Select.Connect - alias Corex.Select.Anatomy.{Props, Root, Label, Control, Positioner, Content} attr(:id, :string, required: false) attr(:collection, :list, default: []) diff --git a/lib/components/select/connect.ex b/lib/components/select/connect.ex index e92ec78..a4a0bd4 100644 --- a/lib/components/select/connect.ex +++ b/lib/components/select/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.Select.Connect do @moduledoc false - alias Corex.Select.Anatomy.{Props, Root, Label, Control, Positioner, Content} + alias Corex.Select.Anatomy.{Content, Control, Label, Positioner, Props, Root} import Corex.Helpers, only: [get_boolean: 1, validate_value!: 1] diff --git a/lib/components/signature_pad.ex b/lib/components/signature_pad.ex index c98db5c..e31c48f 100644 --- a/lib/components/signature_pad.ex +++ b/lib/components/signature_pad.ex @@ -242,17 +242,19 @@ defmodule Corex.SignaturePad do use Phoenix.Component alias Corex.SignaturePad.Anatomy.{ - Props, - Root, - Label, + ClearTrigger, Control, - Segment, Guide, - ClearTrigger, - HiddenInput + HiddenInput, + Label, + Props, + Root, + Segment } alias Corex.SignaturePad.Connect + alias Phoenix.LiveView + alias Phoenix.LiveView.JS @doc """ Renders a signature pad component. @@ -457,7 +459,7 @@ defmodule Corex.SignaturePad do """ def clear(signature_pad_id) when is_binary(signature_pad_id) do - Phoenix.LiveView.JS.dispatch("phx:signature-pad:clear", + JS.dispatch("phx:signature-pad:clear", to: "##{signature_pad_id}", detail: %{id: signature_pad_id}, bubbles: false @@ -476,7 +478,7 @@ defmodule Corex.SignaturePad do """ def clear(socket, signature_pad_id) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(signature_pad_id) do - Phoenix.LiveView.push_event(socket, "signature_pad_clear", %{ + LiveView.push_event(socket, "signature_pad_clear", %{ signature_pad_id: signature_pad_id }) end diff --git a/lib/components/switch.ex b/lib/components/switch.ex index 6431c87..31d593f 100644 --- a/lib/components/switch.ex +++ b/lib/components/switch.ex @@ -204,8 +204,11 @@ defmodule Corex.Switch do @doc type: :component use Phoenix.Component - alias Corex.Switch.Anatomy.{Props, Root, HiddenInput, Control, Thumb, Label} + alias Corex.Switch.Anatomy.{Control, HiddenInput, Label, Props, Root, Thumb} alias Corex.Switch.Connect + alias Phoenix.HTML.Form + alias Phoenix.LiveView + alias Phoenix.LiveView.JS @doc """ Renders a switch component. @@ -306,7 +309,7 @@ defmodule Corex.Switch do |> assign(:errors, Enum.map(errors, &Corex.Gettext.translate_error(&1))) |> assign_new(:id, fn -> field.id end) |> assign_new(:name, fn -> field.name end) - |> assign(:checked, Phoenix.HTML.Form.normalize_value("checkbox", field.value)) + |> assign(:checked, Form.normalize_value("checkbox", field.value)) |> assign_new(:form, fn -> field.form.id end) switch(assigns) @@ -376,7 +379,7 @@ defmodule Corex.Switch do """ def set_checked(switch_id, checked) when is_binary(switch_id) and is_boolean(checked) do - Phoenix.LiveView.JS.dispatch("phx:switch:set-checked", + JS.dispatch("phx:switch:set-checked", to: "##{switch_id}", detail: %{checked: checked}, bubbles: false @@ -397,7 +400,7 @@ defmodule Corex.Switch do def set_checked(socket, switch_id, checked) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(switch_id) and is_boolean(checked) do - Phoenix.LiveView.push_event(socket, "switch_set_checked", %{ + LiveView.push_event(socket, "switch_set_checked", %{ switch_id: switch_id, checked: checked }) @@ -414,7 +417,7 @@ defmodule Corex.Switch do """ def toggle_checked(switch_id) when is_binary(switch_id) do - Phoenix.LiveView.JS.dispatch("phx:switch:toggle-checked", + JS.dispatch("phx:switch:toggle-checked", to: "##{switch_id}", bubbles: false ) @@ -433,7 +436,7 @@ defmodule Corex.Switch do """ def toggle_checked(socket, switch_id) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(switch_id) do - Phoenix.LiveView.push_event(socket, "switch_toggle_checked", %{ + LiveView.push_event(socket, "switch_toggle_checked", %{ switch_id: switch_id }) end diff --git a/lib/components/switch/connect.ex b/lib/components/switch/connect.ex index 5273114..2d301bb 100644 --- a/lib/components/switch/connect.ex +++ b/lib/components/switch/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.Switch.Connect do @moduledoc false - alias Corex.Switch.Anatomy.{Props, Root, HiddenInput, Control, Thumb, Label} + alias Corex.Switch.Anatomy.{Control, HiddenInput, Label, Props, Root, Thumb} defp data_attr(true), do: "" defp data_attr(false), do: nil diff --git a/lib/components/tabs.ex b/lib/components/tabs.ex index f7683b4..a4c47b0 100644 --- a/lib/components/tabs.ex +++ b/lib/components/tabs.ex @@ -236,8 +236,10 @@ defmodule Corex.Tabs do @doc type: :component use Phoenix.Component - alias Corex.Tabs.Anatomy.{Props, Root, List, Trigger, Content} + alias Corex.Tabs.Anatomy.{Content, List, Props, Root, Trigger} alias Corex.Tabs.Connect + alias Phoenix.LiveView + alias Phoenix.LiveView.JS @doc """ Renders a tabs component. @@ -595,7 +597,7 @@ defmodule Corex.Tabs do """ def set_value(tabs_id, value) when is_binary(tabs_id) do - Phoenix.LiveView.JS.dispatch("phx:tabs:set-value", + JS.dispatch("phx:tabs:set-value", to: "##{tabs_id}", detail: %{value: validate_tabs_value!(value)}, bubbles: false @@ -620,7 +622,7 @@ defmodule Corex.Tabs do """ def set_value(socket, tabs_id, value) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(tabs_id) do - Phoenix.LiveView.push_event(socket, "tabs_set_value", %{ + LiveView.push_event(socket, "tabs_set_value", %{ tabs_id: tabs_id, value: validate_tabs_value!(value) }) diff --git a/lib/components/tabs/connect.ex b/lib/components/tabs/connect.ex index 0326f50..94aa5a0 100644 --- a/lib/components/tabs/connect.ex +++ b/lib/components/tabs/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.Tabs.Connect do @moduledoc false - alias Corex.Tabs.Anatomy.{Props, Root, List, Trigger, Content} + alias Corex.Tabs.Anatomy.{Content, List, Props, Root, Trigger} defp data_attr(true), do: "" defp data_attr(false), do: nil diff --git a/lib/components/timer.ex b/lib/components/timer.ex index 5404a9f..f9383d6 100644 --- a/lib/components/timer.ex +++ b/lib/components/timer.ex @@ -63,7 +63,7 @@ defmodule Corex.Timer do @doc type: :component use Phoenix.Component - alias Corex.Timer.Anatomy.{Props, Root, Area, Control, Item, Separator, ActionTrigger} + alias Corex.Timer.Anatomy.{ActionTrigger, Area, Control, Item, Props, Root, Separator} alias Corex.Timer.Connect attr(:id, :string, required: false) diff --git a/lib/components/timer/connect.ex b/lib/components/timer/connect.ex index 30d6468..027ecc5 100644 --- a/lib/components/timer/connect.ex +++ b/lib/components/timer/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.Timer.Connect do @moduledoc false - alias Corex.Timer.Anatomy.{Props, Root, Area, Control, Item, Separator, ActionTrigger} + alias Corex.Timer.Anatomy.{ActionTrigger, Area, Control, Item, Props, Root, Separator} defp data_attr(true), do: "" defp data_attr(false), do: nil diff --git a/lib/components/toast.ex b/lib/components/toast.ex index 2eb12d1..c6589ec 100644 --- a/lib/components/toast.ex +++ b/lib/components/toast.ex @@ -472,7 +472,7 @@ defmodule Corex.Toast do _ -> "info" end - Phoenix.LiveView.JS.dispatch("toast:create", + JS.dispatch("toast:create", to: "##{toast_group_id}", detail: %{ title: title, diff --git a/lib/components/toggle-group/connect.ex b/lib/components/toggle-group/connect.ex index 7965e2e..41200f9 100644 --- a/lib/components/toggle-group/connect.ex +++ b/lib/components/toggle-group/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.ToggleGroup.Connect do @moduledoc false - alias Corex.ToggleGroup.Anatomy.{Props, Root, Item} + alias Corex.ToggleGroup.Anatomy.{Item, Props, Root} defp data_attr(true), do: "" defp data_attr(false), do: nil diff --git a/lib/components/toggle_group.ex b/lib/components/toggle_group.ex index 61281ec..2378aab 100644 --- a/lib/components/toggle_group.ex +++ b/lib/components/toggle_group.ex @@ -131,8 +131,10 @@ defmodule Corex.ToggleGroup do @doc type: :component use Phoenix.Component - alias Corex.ToggleGroup.Anatomy.{Props, Root, Item} + alias Corex.ToggleGroup.Anatomy.{Item, Props, Root} alias Corex.ToggleGroup.Connect + alias Phoenix.LiveView + alias Phoenix.LiveView.JS @doc """ Renders a toggle group component. @@ -248,7 +250,7 @@ defmodule Corex.ToggleGroup do """ def set_value(toggle_group_id, value) when is_binary(toggle_group_id) do - Phoenix.LiveView.JS.dispatch("phx:toggle-group:set-value", + JS.dispatch("phx:toggle-group:set-value", to: "##{toggle_group_id}", detail: %{value: Connect.validate_value!(value)} ) @@ -267,7 +269,7 @@ defmodule Corex.ToggleGroup do """ def set_value(socket, toggle_group_id, value) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(toggle_group_id) do - Phoenix.LiveView.push_event(socket, "toggle-group_set_value", %{ + LiveView.push_event(socket, "toggle-group_set_value", %{ id: toggle_group_id, value: Connect.validate_value!(value) }) diff --git a/lib/components/tree_view.ex b/lib/components/tree_view.ex index 52f3037..eaef8fb 100644 --- a/lib/components/tree_view.ex +++ b/lib/components/tree_view.ex @@ -117,8 +117,10 @@ defmodule Corex.TreeView do @doc type: :component use Phoenix.Component - alias Corex.TreeView.Anatomy.{Props, Root, Label, Branch, Item} + alias Corex.TreeView.Anatomy.{Branch, Item, Label, Props, Root} alias Corex.TreeView.Connect + alias Phoenix.LiveView + alias Phoenix.LiveView.JS import Corex.Helpers, only: [validate_value!: 1] @doc """ @@ -356,7 +358,7 @@ defmodule Corex.TreeView do @doc type: :api @doc "Sets the tree expanded value from client-side." def set_expanded_value(tree_view_id, value) when is_binary(tree_view_id) do - Phoenix.LiveView.JS.dispatch("phx:tree-view:set-expanded-value", + JS.dispatch("phx:tree-view:set-expanded-value", to: "##{tree_view_id}", detail: %{value: validate_value!(value)}, bubbles: false @@ -366,7 +368,7 @@ defmodule Corex.TreeView do @doc type: :api @doc "Sets the tree selected value from client-side." def set_selected_value(tree_view_id, value) when is_binary(tree_view_id) do - Phoenix.LiveView.JS.dispatch("phx:tree-view:set-selected-value", + JS.dispatch("phx:tree-view:set-selected-value", to: "##{tree_view_id}", detail: %{value: validate_value!(value)}, bubbles: false @@ -377,7 +379,7 @@ defmodule Corex.TreeView do @doc "Sets the tree expanded value from server-side." def set_expanded_value(socket, tree_view_id, value) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(tree_view_id) do - Phoenix.LiveView.push_event(socket, "tree_view_set_expanded_value", %{ + LiveView.push_event(socket, "tree_view_set_expanded_value", %{ tree_view_id: tree_view_id, value: validate_value!(value) }) @@ -387,7 +389,7 @@ defmodule Corex.TreeView do @doc "Sets the tree selected value from server-side." def set_selected_value(socket, tree_view_id, value) when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(tree_view_id) do - Phoenix.LiveView.push_event(socket, "tree_view_set_selected_value", %{ + LiveView.push_event(socket, "tree_view_set_selected_value", %{ tree_view_id: tree_view_id, value: validate_value!(value) }) diff --git a/lib/components/tree_view/connect.ex b/lib/components/tree_view/connect.ex index b109bf0..9d76868 100644 --- a/lib/components/tree_view/connect.ex +++ b/lib/components/tree_view/connect.ex @@ -1,6 +1,6 @@ defmodule Corex.TreeView.Connect do @moduledoc false - alias Corex.TreeView.Anatomy.{Props, Root, Label, Item, Branch} + alias Corex.TreeView.Anatomy.{Branch, Item, Label, Props, Root} import Corex.Helpers, only: [validate_value!: 1] defp data_attr(true), do: "" diff --git a/lib/content.ex b/lib/corex/content.ex similarity index 100% rename from lib/content.ex rename to lib/corex/content.ex diff --git a/lib/flash.ex b/lib/corex/flash.ex similarity index 100% rename from lib/flash.ex rename to lib/corex/flash.ex diff --git a/lib/form.ex b/lib/corex/form.ex similarity index 100% rename from lib/form.ex rename to lib/corex/form.ex index a90e1b2..e6c4dee 100644 --- a/lib/form.ex +++ b/lib/corex/form.ex @@ -149,9 +149,9 @@ defmodule Corex.Form do ''' + alias Ecto.Changeset alias Phoenix.Component alias Phoenix.HTML.Form - alias Ecto.Changeset @doc """ Returns the form id. diff --git a/lib/gettext.ex b/lib/corex/gettext.ex similarity index 100% rename from lib/gettext.ex rename to lib/corex/gettext.ex diff --git a/lib/helpers.ex b/lib/corex/helpers.ex similarity index 100% rename from lib/helpers.ex rename to lib/corex/helpers.ex diff --git a/lib/json.ex b/lib/corex/json.ex similarity index 100% rename from lib/json.ex rename to lib/corex/json.ex diff --git a/lib/list.ex b/lib/corex/list.ex similarity index 100% rename from lib/list.ex rename to lib/corex/list.ex diff --git a/lib/positoning.ex b/lib/corex/positoning.ex similarity index 100% rename from lib/positoning.ex rename to lib/corex/positoning.ex diff --git a/lib/tree.ex b/lib/corex/tree.ex similarity index 100% rename from lib/tree.ex rename to lib/corex/tree.ex diff --git a/lib/mix/tasks/corex.code.ex b/lib/mix/tasks/corex.code.ex index a7199d3..03aa6e1 100644 --- a/lib/mix/tasks/corex.code.ex +++ b/lib/mix/tasks/corex.code.ex @@ -47,8 +47,6 @@ defmodule Mix.Tasks.Corex.Code do validate_makeup!() validate_path!(full_path, force) generate!(full_path) - - Mix.shell().info("Makeup stylesheet written to: #{path}") end defp validate_makeup! do @@ -79,8 +77,7 @@ defmodule Mix.Tasks.Corex.Code do end defp generate!(full_path) do - makeup = Module.concat(["Elixir", "Makeup"]) - stylesheet = apply(makeup, :stylesheet, [:default_style]) + stylesheet = Makeup.stylesheet(:default_style) full_path |> Path.dirname() diff --git a/lib/mix/tasks/corex.design.ex b/lib/mix/tasks/corex.design.ex index 938d1cb..3b3a633 100644 --- a/lib/mix/tasks/corex.design.ex +++ b/lib/mix/tasks/corex.design.ex @@ -103,7 +103,9 @@ defmodule Mix.Tasks.Corex.Design do copy_design_files(target, designex) - Mix.shell().info("Corex design copied to: #{target}") + unless Mix.env() == :test do + Mix.shell().info("Corex design copied to: #{target}") + end end defp parse_args(args) do @@ -135,7 +137,7 @@ defmodule Mix.Tasks.Corex.Design do end defp maybe_show_designex_info(designex) do - if designex and not designex_installed?() do + if designex and not designex_installed?() and Mix.env() != :test do Mix.shell().info(""" To enable token builds, add to mix.exs: diff --git a/mix.exs b/mix.exs index 4c8bcde..f06bbf7 100644 --- a/mix.exs +++ b/mix.exs @@ -9,6 +9,7 @@ defmodule Corex.MixProject do app: :corex, version: @version, elixir: @elixir_requirement, + elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, deps: deps(), aliases: aliases(), @@ -18,7 +19,11 @@ defmodule Corex.MixProject do package: package(), source_url: "https://github.com/corex-ui/corex", homepage_url: "https://corex.gigalixirapp.com/en", - docs: &docs/0 + docs: &docs/0, + test_coverage: [ + tool: ExCoveralls, + threshold: 85 + ] ] end @@ -28,19 +33,29 @@ defmodule Corex.MixProject do ] end + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + defp deps do [ + {:jason, "~> 1.0"}, {:phoenix, "~> 1.8.1"}, {:phoenix_live_view, "~> 1.1.0"}, {:gettext, "~> 1.0"}, {:ecto, "~> 3.10"}, {:esbuild, "~> 0.8", only: :dev}, {:ex_doc, "~> 0.40", only: :dev, runtime: false, warn_if_outdated: true}, - {:makeup, "~> 1.2", only: :dev}, - {:makeup_elixir, "~> 1.0.1 or ~> 1.1", only: :dev}, - {:makeup_eex, "~> 2.0", only: :dev}, - {:makeup_syntect, "~> 0.1.0", only: :dev}, - {:credo, "~> 1.7", only: [:dev, :test], runtime: false} + {:makeup, "~> 1.2", only: [:dev, :test]}, + {:makeup_elixir, "~> 1.0.1 or ~> 1.1", only: [:dev, :test]}, + {:makeup_eex, "~> 2.0", only: [:dev, :test]}, + {:makeup_syntect, "~> 0.1.0", only: [:dev, :test]}, + {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, + {:floki, "~> 0.38.0", only: :test}, + {:phoenix_ecto, "~> 4.0", only: :test}, + {:excoveralls, "~> 0.18", only: :test}, + {:tidewave, "~> 0.5.5", only: :dev}, + {:bandit, "~> 1.0", only: :dev}, + {:sobelow, "~> 0.13", only: [:dev, :test], runtime: false} ] end @@ -56,7 +71,14 @@ defmodule Corex.MixProject do "esbuild main", "esbuild hooks" ], - "assets.watch": "esbuild module --watch" + "assets.watch": "esbuild module --watch", + tidewave: + "run --no-halt -e 'Agent.start(fn -> Bandit.start_link(plug: Tidewave, port: 4004) end)'", + "pre.publish": [ + "format --check-formatted", + "credo --strict", + "sobelow --exit" + ] ] end diff --git a/mix.lock b/mix.lock index afdedaf..ef6731f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,15 +1,21 @@ %{ + "bandit": {:hex, :bandit, "1.10.3", "1e5d168fa79ec8de2860d1b4d878d97d4fbbe2fdbe7b0a7d9315a4359d1d4bb9", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "99a52d909c48db65ca598e1962797659e3c0f1d06e825a50c3d75b74a5e2db18"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.17", "4f9770d2d45fbd91dcf6bd404cf64e7e58fed04fadda0923dc32acca0badffa2", [:mix], [], "hexpm", "12d24b9d80b910dd3953e165636d68f147a31db945d2dcb9365e441f8b5351e5"}, + "circular_buffer": {:hex, :circular_buffer, "1.0.0", "25c004da0cba7bd8bc1bdabded4f9a902d095e20600fd15faf1f2ffbaea18a07", [:mix], [], "hexpm", "c829ec31c13c7bafd1f546677263dff5bfb006e929f25635878ac3cfba8749e5"}, "credo": {:hex, :credo, "1.7.16", "a9f1389d13d19c631cb123c77a813dbf16449a2aebf602f590defa08953309d4", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0562af33756b21f248f066a9119e3890722031b6d199f22e3cf95550e4f1579"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"}, "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"}, + "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"}, "expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, + "finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [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", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"}, + "floki": {:hex, :floki, "0.38.0", "62b642386fa3f2f90713f6e231da0fa3256e41ef1089f83b6ceac7a3fd3abf33", [:mix], [], "hexpm", "a5943ee91e93fb2d635b612caf5508e36d37548e84928463ef9dd986f0d1abd9"}, "gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_eex": {:hex, :makeup_eex, "2.0.2", "88983b72aadb2e8408b06f7c9413804ce7eae2ca2a5a35cb738c6a9cb393c155", [:mix], [{:makeup, "~> 1.2.1 or ~> 1.3", [hex: :makeup, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_html, "~> 0.2.0 or ~> 1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "30ac121dda580298ff3378324ffaec94aad5a5b67e0cc6af177c67d5f45629b9"}, @@ -17,16 +23,24 @@ "makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"}, "makeup_syntect": {:hex, :makeup_syntect, "0.1.4", "e1230c9e0513c667b226b21c83eb182e1ab581f65af9441edab1f9ac626acba6", [:mix], [{:makeup, "~> 1.2", [hex: :makeup, repo: "hexpm", optional: false]}, {:rustler, "~> 0.37.1", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8.2", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "5b624a434d9665786b9a352a5f3502b6c98e1996ede9936b20035ec140daef70"}, "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [: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", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, - "phoenix": {:hex, :phoenix, "1.8.3", "49ac5e485083cb1495a905e47eb554277bdd9c65ccb4fc5100306b350151aa95", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, 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.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "36169f95cc2e155b78be93d9590acc3f462f1e5438db06e6248613f27c80caec"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "phoenix": {:hex, :phoenix, "1.8.4", "0387f84f00071cba8d71d930b9121b2fb3645197a9206c31b908d2e7902a4851", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, 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.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c988b1cd3b084eebb13e6676d572597d387fa607dab258526637b4e6c4c08543"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"}, "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.24", "1a000a048d5971b61a9efe29a3c4144ca955afd42224998d841c5011a5354838", [: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-rc", [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", "0c724e6c65f197841cac49d73be4e0f9b93a7711eaa52d2d4d1b9f859c329267"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.25", "abc1bdf7f148d7f9a003f149834cc858b24290c433b10ef6d1cbb1d6e9a211ca", [: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-rc", [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", "b8946e474799da1f874eab7e9ce107502c96ca318ed46d19f811f847df270865"}, "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.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [: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", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, + "req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [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", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"}, "rustler_precompiled": {:hex, :rustler_precompiled, "0.8.4", "700a878312acfac79fb6c572bb8b57f5aae05fe1cf70d34b5974850bbf2c05bf", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "3b33d99b540b15f142ba47944f7a163a25069f6d608783c321029bc1ffb09514"}, + "sobelow": {:hex, :sobelow, "0.14.1", "2f81e8632f15574cba2402bcddff5497b413c01e6f094bc0ab94e83c2f74db81", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8fac9a2bd90fdc4b15d6fca6e1608efb7f7c600fa75800813b794ee9364c87f2"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"}, + "tidewave": {:hex, :tidewave, "0.5.5", "a125dfc87f99daf0e2280b3a9719b874c616ead5926cdf9cdfe4fcc19a020eff", [:mix], [{:circular_buffer, "~> 0.4 or ~> 1.0", [hex: :circular_buffer, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_reload, ">= 1.6.1", [hex: :phoenix_live_reload, repo: "hexpm", optional: true]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "825ebb4fa20de005785efa21e5a88c04d81c3f57552638d12ff3def2f203dbf7"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, } diff --git a/priv/design/components/layout.css b/priv/design/components/layout.css index 06c5394..4a15e95 100644 --- a/priv/design/components/layout.css +++ b/priv/design/components/layout.css @@ -12,7 +12,7 @@ background: var(--color-root); overflow-y: scroll; - scrollbar-gutter: stable both-edges; + scrollbar-gutter: stable; } .layout__header { @@ -80,7 +80,7 @@ width: auto; height: calc(100vh - var(--spacing-ui-lg)); overflow-y: scroll; - scrollbar-gutter: stable both-edges; + scrollbar-gutter: stable; } @@ -95,7 +95,6 @@ flex-direction: column; flex: 1; min-width: 0; - min-height: calc(100vh - var(--spacing-ui-lg)); width: 100%; height: 100%; margin-inline: auto; diff --git a/test/components/accordion_test.exs b/test/components/accordion_test.exs index 035cb36..7e93b92 100644 --- a/test/components/accordion_test.exs +++ b/test/components/accordion_test.exs @@ -1,9 +1,68 @@ defmodule Corex.AccordionTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Accordion alias Corex.Accordion.Connect + describe "accordion/1" do + test "renders with items" do + items = Corex.Content.new([[trigger: "T1", content: "C1"]]) + html = render_component(&Accordion.accordion/1, items: items) + assert html =~ ~r/data-scope="accordion"/ + assert html =~ ~r/data-part="root"/ + assert html =~ ~r/T1/ + assert html =~ ~r/C1/ + end + + test "renders with horizontal orientation" do + html = + render_component(&CorexTest.ComponentHelpers.render_accordion/1, + orientation: "horizontal" + ) + + assert html =~ ~r/data-scope="accordion"/ + assert html =~ ~r/data-orientation="horizontal"/ + end + + test "renders with collapsible false" do + html = render_component(&CorexTest.ComponentHelpers.render_accordion/1, collapsible: false) + assert html =~ ~r/data-scope="accordion"/ + end + + test "renders with multiple false" do + html = render_component(&CorexTest.ComponentHelpers.render_accordion/1, multiple: false) + assert html =~ ~r/data-scope="accordion"/ + end + + test "renders with dir rtl" do + html = render_component(&CorexTest.ComponentHelpers.render_accordion/1, dir: "rtl") + assert html =~ ~r/data-scope="accordion"/ + assert html =~ ~r/dir="rtl"/ + end + + test "renders with indicator slot" do + html = render_component(&CorexTest.ComponentHelpers.render_accordion_with_indicator/1, []) + assert html =~ ~r/data-scope="accordion"/ + assert html =~ ~r/data-part="item-indicator"/ + end + + test "renders with custom trigger and content slots" do + html = + render_component(&CorexTest.ComponentHelpers.render_accordion_with_custom_slots/1, []) + + assert html =~ ~r/data-scope="accordion"/ + assert html =~ ~r/data-trigger/ + assert html =~ ~r/data-content/ + assert html =~ ~r/Custom/ + end + + test "renders accordion_skeleton" do + html = render_component(&Accordion.accordion_skeleton/1, []) + assert html =~ ~r/data-scope="accordion"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "set_value/2" do test "returns JS command with list of values" do js = Accordion.set_value("my-accordion", ["item-1", "item-2"]) diff --git a/test/components/action_test.exs b/test/components/action_test.exs new file mode 100644 index 0000000..0d3e1f1 --- /dev/null +++ b/test/components/action_test.exs @@ -0,0 +1,45 @@ +defmodule Corex.ActionTest do + use CorexTest.ComponentCase, async: true + + describe "action/1" do + test "renders button with default type" do + result = render_component(&CorexTest.ComponentHelpers.render_action/1, %{}) + assert [_] = find_in_html(result, "button") + assert text_in_html(result) =~ "Click" + end + + test "renders button with type submit" do + result = + render_component(&CorexTest.ComponentHelpers.render_action_with_opts/1, + type: "submit", + aria_label: nil, + disabled: false + ) + + assert [_] = find_in_html(result, "button[type=submit]") + assert text_in_html(result) =~ "Save" + end + + test "renders button with aria_label" do + result = + render_component(&CorexTest.ComponentHelpers.render_action_with_opts/1, + type: "button", + aria_label: "Close dialog", + disabled: false + ) + + assert [_] = find_in_html(result, ~s(button[aria-label="Close dialog"])) + end + + test "renders disabled button" do + result = + render_component(&CorexTest.ComponentHelpers.render_action_with_opts/1, + type: "button", + aria_label: nil, + disabled: true + ) + + assert [_] = find_in_html(result, "button[disabled]") + end + end +end diff --git a/test/components/angle_slider_test.exs b/test/components/angle_slider_test.exs index 1b472ed..2aad70c 100644 --- a/test/components/angle_slider_test.exs +++ b/test/components/angle_slider_test.exs @@ -1,9 +1,17 @@ defmodule Corex.AngleSliderTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.AngleSlider alias Corex.AngleSlider.Connect + describe "angle_slider/1" do + test "renders" do + html = render_component(&AngleSlider.angle_slider/1, value: 0, name: "angle") + assert html =~ ~r/data-scope="angle-slider"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "set_value/2" do test "returns JS command" do js = AngleSlider.set_value("my-slider", 45) diff --git a/test/components/avatar_test.exs b/test/components/avatar_test.exs index b285347..9e24826 100644 --- a/test/components/avatar_test.exs +++ b/test/components/avatar_test.exs @@ -1,8 +1,17 @@ defmodule Corex.AvatarTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Avatar.Connect + describe "avatar/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_avatar/1, []) + assert html =~ ~r/data-scope="avatar"/ + assert html =~ ~r/data-part="root"/ + assert html =~ ~r/JD/ + end + end + describe "Connect.root/1" do test "returns root attributes" do assigns = %{id: "test-avatar"} diff --git a/test/components/carousel_test.exs b/test/components/carousel_test.exs index c422762..590fa4b 100644 --- a/test/components/carousel_test.exs +++ b/test/components/carousel_test.exs @@ -1,8 +1,16 @@ defmodule Corex.CarouselTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Carousel.Connect + describe "carousel/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_carousel/1, []) + assert html =~ ~r/data-scope="carousel"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "Connect.root/1" do test "returns root attributes" do assigns = %{ diff --git a/test/components/checkbox_test.exs b/test/components/checkbox_test.exs index e27dca1..2e771c3 100644 --- a/test/components/checkbox_test.exs +++ b/test/components/checkbox_test.exs @@ -1,9 +1,17 @@ defmodule Corex.CheckboxTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Checkbox alias Corex.Checkbox.Connect + describe "checkbox/1" do + test "renders" do + html = render_component(&Checkbox.checkbox/1, checked: false, name: "cb") + assert html =~ ~r/data-scope="checkbox"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "set_checked/2" do test "returns JS command when checked is true" do js = Checkbox.set_checked("my-checkbox", true) diff --git a/test/components/clipboard_test.exs b/test/components/clipboard_test.exs index 2d41457..242f231 100644 --- a/test/components/clipboard_test.exs +++ b/test/components/clipboard_test.exs @@ -1,9 +1,17 @@ defmodule Corex.ClipboardTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Clipboard alias Corex.Clipboard.Connect + describe "clipboard/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_clipboard/1, []) + assert html =~ ~r/data-scope="clipboard"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "set_value/2" do test "returns JS command" do js = Clipboard.set_value("my-clipboard", "hello") diff --git a/test/components/code_test.exs b/test/components/code_test.exs new file mode 100644 index 0000000..f5b3ea6 --- /dev/null +++ b/test/components/code_test.exs @@ -0,0 +1,33 @@ +defmodule Corex.CodeTest do + use CorexTest.ComponentCase, async: true + + describe "code/1" do + test "renders plain code" do + result = render_component(&Corex.Code.code/1, code: "def hello, do: :world") + assert [_] = find_in_html(result, ~s([data-scope="code"][data-part="root"])) + assert [_] = find_in_html(result, ~s([data-part="content"])) + assert [_] = find_in_html(result, "pre") + assert [_] = find_in_html(result, "code") + end + + test "renders code with elixir syntax highlighting" do + result = + render_component(&Corex.Code.code/1, + code: "defmodule Hello do\n def world, do: :ok\nend", + language: :elixir + ) + + assert find_in_html(result, ~s([data-scope="code"])) != [] + end + + test "escapes code when language has no lexer" do + result = + render_component(&Corex.Code.code/1, + code: "", + language: :unknown + ) + + assert result =~ "<script>" + end + end +end diff --git a/test/components/collapsible_test.exs b/test/components/collapsible_test.exs index a5466b0..16fd5c8 100644 --- a/test/components/collapsible_test.exs +++ b/test/components/collapsible_test.exs @@ -1,9 +1,17 @@ defmodule Corex.CollapsibleTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Collapsible alias Corex.Collapsible.Connect + describe "collapsible/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_collapsible/1, []) + assert html =~ ~r/data-scope="collapsible"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "set_open/2" do test "returns JS command when open is true" do js = Collapsible.set_open("my-collapsible", true) diff --git a/test/components/color_picker_test.exs b/test/components/color_picker_test.exs index da2e40b..c6a5e7e 100644 --- a/test/components/color_picker_test.exs +++ b/test/components/color_picker_test.exs @@ -1,9 +1,83 @@ defmodule Corex.ColorPickerTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true + alias Corex.ColorPicker alias Corex.ColorPicker.Connect alias Corex.ColorPicker.Initial + describe "color_picker/1" do + test "renders" do + html = render_component(&ColorPicker.color_picker/1, []) + assert html =~ ~r/data-scope="color-picker"/ + assert html =~ ~r/data-part="root"/ + end + end + + describe "set_open/2" do + test "returns JS command when open is true" do + js = ColorPicker.set_open("my-color-picker", true) + assert %Phoenix.LiveView.JS{} = js + end + + test "returns JS command when open is false" do + js = ColorPicker.set_open("my-color-picker", false) + assert %Phoenix.LiveView.JS{} = js + end + end + + describe "set_open/3" do + test "pushes event to socket" do + socket = %Phoenix.LiveView.Socket{} + result = ColorPicker.set_open(socket, "my-color-picker", true) + assert %Phoenix.LiveView.Socket{} = result + end + end + + describe "set_value/2" do + test "returns JS command for hex string" do + js = ColorPicker.set_value("my-color-picker", "#ff0000") + assert %Phoenix.LiveView.JS{} = js + end + + test "returns JS command for rgba string" do + js = ColorPicker.set_value("my-color-picker", "rgba(255, 0, 0, 1)") + assert %Phoenix.LiveView.JS{} = js + end + end + + describe "set_value/3" do + test "pushes event to socket" do + socket = %Phoenix.LiveView.Socket{} + result = ColorPicker.set_value(socket, "my-color-picker", "#00ff00") + assert %Phoenix.LiveView.Socket{} = result + end + end + + describe "set_format/2" do + test "returns JS command for rgba format" do + js = ColorPicker.set_format("my-color-picker", "rgba") + assert %Phoenix.LiveView.JS{} = js + end + + test "returns JS command for hex format" do + js = ColorPicker.set_format("my-color-picker", "hex") + assert %Phoenix.LiveView.JS{} = js + end + + test "returns JS command for hsla and hsba formats" do + assert %Phoenix.LiveView.JS{} = ColorPicker.set_format("my-color-picker", "hsla") + assert %Phoenix.LiveView.JS{} = ColorPicker.set_format("my-color-picker", "hsba") + end + end + + describe "set_format/3" do + test "pushes event to socket" do + socket = %Phoenix.LiveView.Socket{} + result = ColorPicker.set_format(socket, "my-color-picker", "hex") + assert %Phoenix.LiveView.Socket{} = result + end + end + describe "Initial.parse/1" do test "returns empty map for nil" do result = Initial.parse(nil) diff --git a/test/components/combobox_test.exs b/test/components/combobox_test.exs index 75412d8..be3e78c 100644 --- a/test/components/combobox_test.exs +++ b/test/components/combobox_test.exs @@ -1,8 +1,81 @@ defmodule Corex.ComboboxTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Combobox.Connect + describe "combobox/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_combobox/1, []) + assert html =~ ~r/data-scope="combobox"/ + assert html =~ ~r/data-part="root"/ + end + + test "renders with collection and empty slot" do + html = render_component(&CorexTest.ComponentHelpers.render_combobox_with_items/1, []) + assert html =~ ~r/data-part="empty"/ + assert html =~ ~r/data-part="item".*data-value="a"/ + assert html =~ ~r/A/ + end + + test "renders with custom item slot" do + html = render_component(&CorexTest.ComponentHelpers.render_combobox_with_item_slot/1, []) + assert html =~ ~r/data-part="item-text"/ + assert html =~ ~r/X!/ + end + + test "renders with clear_trigger and item_indicator slots" do + html = + render_component( + &CorexTest.ComponentHelpers.render_combobox_with_clear_and_indicator/1, + [] + ) + + assert html =~ ~r/data-part="clear-trigger"/ + assert html =~ ~r/data-part="item-indicator"/ + end + + test "renders with grouped collection" do + html = render_component(&CorexTest.ComponentHelpers.render_combobox_grouped/1, []) + assert html =~ ~r/data-part="item-group"/ + assert html =~ ~r/item-group-label/ + end + + test "renders with filter false" do + html = render_component(&CorexTest.ComponentHelpers.render_combobox_filter_false/1, []) + assert html =~ ~r/data-scope="combobox"/ + assert html =~ ~r/data-value="c"/ + end + + test "renders with multiple and controlled" do + result = + render_component(&CorexTest.ComponentHelpers.render_combobox_controlled_multiple/1, []) + + assert result =~ ~r/data-value/ + assert result =~ ~r/data-multiple/ + end + + test "renders with errors" do + html = render_component(&CorexTest.ComponentHelpers.render_combobox_with_errors/1, []) + assert html =~ ~r/data-part="error"/ + assert html =~ ~r/Required/ + end + + test "Connect.props with filter false sets data-filter to nil" do + assigns = + Map.merge(default_combobox_props(), %{ + id: "test", + collection: [%{id: "a", label: "A"}], + controlled: false, + value: [], + dir: "ltr", + filter: false + }) + + result = Connect.props(assigns) + assert result["data-filter"] == nil + end + end + describe "Connect.root/1" do test "returns root attributes" do assigns = %{id: "test-combobox", invalid: false, read_only: false} @@ -36,4 +109,90 @@ defmodule Corex.ComboboxTest do assert result["id"] == "combobox:test-combobox:control" end end + + describe "Connect.input/1" do + test "returns input attributes" do + assigns = %{ + id: "test-combobox", + dir: "ltr", + disabled: false, + invalid: false, + placeholder: nil, + auto_focus: false + } + + result = Connect.input(assigns) + assert result["data-part"] == "input" + end + end + + describe "Connect.positioner/1" do + test "returns positioner attributes" do + assigns = %{id: "test-combobox", dir: "ltr"} + result = Connect.positioner(assigns) + assert result["data-part"] == "positioner" + end + end + + describe "Connect.content/1" do + test "returns content attributes" do + assigns = %{id: "test-combobox", dir: "ltr"} + result = Connect.content(assigns) + assert result["data-part"] == "content" + end + end + + describe "Connect.props/1" do + test "returns props when uncontrolled" do + assigns = %{ + id: "test-combobox", + collection: [%{id: "a", label: "A"}], + controlled: false, + value: [], + dir: "ltr" + } + + result = Connect.props(Map.merge(default_combobox_props(), assigns)) + assert result["id"] == "test-combobox" + end + + test "returns props when controlled" do + assigns = %{ + id: "test-combobox", + collection: [%{id: "a", label: "A"}], + controlled: true, + value: ["a"], + dir: "ltr" + } + + result = Connect.props(Map.merge(default_combobox_props(), assigns)) + assert result["data-value"] == "a" + end + end + + defp default_combobox_props do + %{ + invalid: false, + read_only: false, + disabled: false, + name: nil, + open: false, + placeholder: nil, + always_submit_on_enter: false, + auto_focus: false, + close_on_select: false, + input_behavior: "autohighlight", + loop_focus: false, + multiple: false, + form: nil, + required: false, + positioning: nil, + on_open_change: nil, + on_open_change_client: nil, + on_input_value_change: nil, + on_value_change: nil, + bubble: false, + filter: true + } + end end diff --git a/test/components/date_picker_test.exs b/test/components/date_picker_test.exs index 3edaefb..f4ec393 100644 --- a/test/components/date_picker_test.exs +++ b/test/components/date_picker_test.exs @@ -1,7 +1,16 @@ defmodule Corex.DatePickerTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.DatePicker + alias Corex.DatePicker.Connect + + describe "date_picker/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_date_picker/1, []) + assert html =~ ~r/data-scope="date-picker"/ + assert html =~ ~r/data-part="root"/ + end + end describe "set_value/2" do test "returns JS command" do @@ -17,4 +26,105 @@ defmodule Corex.DatePickerTest do assert %Phoenix.LiveView.Socket{} = result end end + + describe "Connect" do + test "root/1 returns root attributes" do + result = Connect.root(%{id: "test-dp", dir: "ltr"}) + assert result["id"] == "date-picker:test-dp" + assert result["data-part"] == "root" + end + + test "label/1 returns label attributes" do + result = Connect.label(%{id: "test-dp", dir: "ltr"}) + assert result["data-part"] == "label" + assert result["htmlFor"] == "date-picker:test-dp:input:0" + end + + test "control/1 returns control attributes" do + result = Connect.control(%{id: "test-dp", dir: "ltr"}) + assert result["data-part"] == "control" + end + + test "input/1 returns input attributes" do + result = Connect.input(%{id: "test-dp", dir: "ltr"}) + assert result["data-part"] == "input" + end + + test "trigger/1 returns trigger attributes" do + result = Connect.trigger(%{id: "test-dp", dir: "ltr"}) + assert result["data-part"] == "trigger" + end + + test "positioner/1 returns positioner attributes" do + result = Connect.positioner(%{id: "test-dp", dir: "ltr"}) + assert result["data-part"] == "positioner" + end + + test "content/1 returns content attributes" do + result = Connect.content(%{id: "test-dp", dir: "ltr"}) + assert result["data-part"] == "content" + assert result["hidden"] == true + end + + test "props/1 returns props when uncontrolled" do + assigns = %{ + id: "test-dp", + controlled: false, + value: "2025-02-22", + locale: "en", + time_zone: "UTC", + dir: "ltr" + } + + result = Connect.props(Map.merge(default_props(), assigns)) + assert result["data-default-value"] == "2025-02-22" + assert result["data-value"] == nil + end + + test "props/1 returns props when controlled" do + assigns = %{ + id: "test-dp", + controlled: true, + value: "2025-02-22", + locale: "en", + time_zone: "UTC", + dir: "ltr" + } + + result = Connect.props(Map.merge(default_props(), assigns)) + assert result["data-default-value"] == nil + assert result["data-value"] == "2025-02-22" + end + end + + defp default_props do + %{ + name: nil, + disabled: false, + read_only: false, + required: false, + invalid: false, + outside_day_selectable: false, + close_on_select: nil, + min: nil, + max: nil, + focused_value: nil, + num_of_months: nil, + start_of_week: nil, + fixed_weeks: nil, + selection_mode: nil, + placeholder: nil, + default_view: nil, + min_view: nil, + max_view: nil, + positioning: nil, + on_value_change: nil, + on_focus_change: nil, + on_view_change: nil, + on_visible_range_change: nil, + on_open_change: nil, + trigger_aria_label: nil, + input_aria_label: nil + } + end end diff --git a/test/components/dialog_test.exs b/test/components/dialog_test.exs index ce1c27d..01fe392 100644 --- a/test/components/dialog_test.exs +++ b/test/components/dialog_test.exs @@ -1,9 +1,19 @@ defmodule Corex.DialogTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Dialog alias Corex.Dialog.Connect + describe "dialog/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_dialog/1, []) + assert html =~ ~r/data-scope="dialog"/ + assert html =~ ~r/data-part="content"/ + assert html =~ ~r/Open/ + assert html =~ ~r/Dialog content/ + end + end + describe "set_open/2" do test "returns JS command when open is true" do js = Dialog.set_open("my-dialog", true) @@ -114,4 +124,21 @@ defmodule Corex.DialogTest do assert result["aria-label"] == "Close" end end + + describe "dialog/1 with options" do + test "renders with nested dialog_title, dialog_description, dialog_close_trigger" do + html = render_component(&CorexTest.ComponentHelpers.render_dialog_nested_slots/1, []) + assert html =~ ~r/Nested Title/ + assert html =~ ~r/Nested desc/ + assert html =~ ~r/Body/ + end + + test "renders with controlled and title/description slots" do + html = render_component(&CorexTest.ComponentHelpers.render_dialog_controlled/1, []) + assert html =~ ~r/data-scope="dialog"/ + assert html =~ ~r/Title/ + assert html =~ ~r/Description/ + assert html =~ ~r/Content/ + end + end end diff --git a/test/components/editable_test.exs b/test/components/editable_test.exs index 0bae112..2bd7a82 100644 --- a/test/components/editable_test.exs +++ b/test/components/editable_test.exs @@ -1,8 +1,16 @@ defmodule Corex.EditableTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Editable.Connect + describe "editable/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_editable/1, []) + assert html =~ ~r/data-scope="editable"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "Connect.root/1" do test "returns root attributes" do assigns = %{id: "test-editable", dir: "ltr"} diff --git a/test/components/floating_panel_test.exs b/test/components/floating_panel_test.exs index 7192a63..2a219d2 100644 --- a/test/components/floating_panel_test.exs +++ b/test/components/floating_panel_test.exs @@ -1,8 +1,16 @@ defmodule Corex.FloatingPanelTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.FloatingPanel.Connect + describe "floating_panel/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_floating_panel/1, []) + assert html =~ ~r/data-scope="floating-panel"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "Connect.root/1" do test "returns root attributes" do assigns = %{id: "test-panel", dir: "ltr"} diff --git a/test/components/hidden_input_test.exs b/test/components/hidden_input_test.exs new file mode 100644 index 0000000..a2c2448 --- /dev/null +++ b/test/components/hidden_input_test.exs @@ -0,0 +1,35 @@ +defmodule Corex.HiddenInputTest do + use CorexTest.ComponentCase, async: true + + describe "hidden_input/1" do + test "renders hidden input with id, name, value" do + result = + render_component(&Corex.HiddenInput.hidden_input/1, + id: "user-id", + name: "user[id]", + value: "123" + ) + + elements = find_in_html(result, "input[type=hidden]#user-id") + assert [_] = elements + assert Floki.attribute(elements, "name") == ["user[id]"] + assert Floki.attribute(elements, "value") == ["123"] + end + + test "renders hidden input without field generates unique id" do + result = + render_component(&Corex.HiddenInput.hidden_input/1, name: "user[token]", value: "abc") + + elements = find_in_html(result, "input[type=hidden][name='user[token]']") + assert [_] = elements + assert Floki.attribute(elements, "value") == ["abc"] + end + + test "renders hidden input with form field" do + form = Phoenix.Component.to_form(%{"id" => "42"}, as: :user) + field = form[:id] + result = render_component(&Corex.HiddenInput.hidden_input/1, field: field) + assert [_] = find_in_html(result, ~s(input[type=hidden][name="user[id]"])) + end + end +end diff --git a/test/components/listbox_test.exs b/test/components/listbox_test.exs index 9ce4875..6f48948 100644 --- a/test/components/listbox_test.exs +++ b/test/components/listbox_test.exs @@ -1,8 +1,16 @@ defmodule Corex.ListboxTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Listbox.Connect + describe "listbox/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_listbox/1, []) + assert html =~ ~r/data-scope="listbox"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "Connect.root/1" do test "returns root attributes" do assigns = %{id: "test-listbox", dir: "ltr"} @@ -28,4 +36,143 @@ defmodule Corex.ListboxTest do assert result["id"] == "listbox:test-listbox:content" end end + + describe "Connect.value_text/1" do + test "returns value text attributes" do + assigns = %{id: "test-listbox"} + result = Connect.value_text(assigns) + assert result["id"] == "listbox:test-listbox:value-text" + assert result["data-part"] == "value-text" + end + end + + describe "Connect.input/1" do + test "returns input attributes" do + assigns = %{id: "test-listbox"} + result = Connect.input(assigns) + assert result["id"] == "listbox:test-listbox:input" + assert result["data-part"] == "input" + end + end + + describe "Connect.item_group/1" do + test "returns item group attributes" do + assigns = %{id: "test-listbox", group_id: "g1"} + result = Connect.item_group(assigns) + assert result["id"] == "listbox:test-listbox:item-group:g1" + assert result["data-part"] == "item-group" + assert result["data-id"] == "g1" + end + end + + describe "Connect.item_group_label/1" do + test "returns item group label attributes" do + assigns = %{id: "test-listbox", html_for: "g1"} + result = Connect.item_group_label(assigns) + assert result["id"] == "listbox:test-listbox:item-group-label:g1" + assert result["data-part"] == "item-group-label" + end + end + + describe "Connect.item/1" do + test "returns item attributes" do + assigns = %{id: "test-listbox", value: "opt1"} + result = Connect.item(assigns) + assert result["id"] == "listbox:test-listbox:item:opt1" + assert result["data-part"] == "item" + assert result["data-value"] == "opt1" + end + end + + describe "Connect.item_text/1" do + test "returns item text attributes" do + assigns = %{id: "test-listbox", item: %{id: "x", label: "X"}} + result = Connect.item_text(assigns) + assert result["id"] == "listbox:test-listbox:item-text:x" + assert result["data-part"] == "item-text" + end + + test "uses value when item has value key" do + assigns = %{id: "test-listbox", item: %{value: "v1", label: "L"}} + result = Connect.item_text(assigns) + assert result["id"] == "listbox:test-listbox:item-text:v1" + end + end + + describe "Connect.item_indicator/1" do + test "returns item indicator attributes" do + assigns = %{id: "test-listbox", item: %{id: "y", label: "Y"}} + result = Connect.item_indicator(assigns) + assert result["id"] == "listbox:test-listbox:item-indicator:y" + assert result["data-part"] == "item-indicator" + end + end + + describe "Connect.props/1" do + test "returns props when controlled" do + assigns = %{ + id: "test-listbox", + collection: [%{id: "a", label: "A"}], + controlled: true, + value: ["a"], + dir: "ltr", + orientation: "vertical", + loop_focus: false, + selection_mode: "single", + select_on_highlight: false, + deselectable: false, + typeahead: false, + disabled: false, + on_value_change: nil, + on_value_change_client: nil + } + + result = Connect.props(assigns) + assert result["data-value"] == "a" + assert result["data-controlled"] == "" + end + + test "returns props when uncontrolled" do + assigns = %{ + id: "test-listbox", + collection: [%{id: "a", label: "A"}], + controlled: false, + value: ["a"], + dir: "ltr", + orientation: "vertical", + loop_focus: false, + selection_mode: "single", + select_on_highlight: false, + deselectable: false, + typeahead: false, + disabled: false, + on_value_change: nil, + on_value_change_client: nil + } + + result = Connect.props(assigns) + assert result["data-default-value"] == "a" + assert result["data-value"] == nil + end + end + + describe "listbox/1 with options" do + test "renders with grouped collection" do + html = render_component(&CorexTest.ComponentHelpers.render_listbox_grouped/1, []) + assert html =~ ~r/data-scope="listbox"/ + end + + test "renders with controlled" do + html = render_component(&CorexTest.ComponentHelpers.render_listbox_controlled/1, []) + assert html =~ ~r/data-scope="listbox"/ + assert html =~ ~r/data-controlled/ + end + + test "renders with Corex.List.Item collection" do + html = render_component(&CorexTest.ComponentHelpers.render_listbox_list_items/1, []) + assert html =~ ~r/data-scope="listbox"/ + assert html =~ ~r/Item 1/ + assert html =~ ~r/Item 2/ + end + end end diff --git a/test/components/marquee_test.exs b/test/components/marquee_test.exs index 383df98..9840ba1 100644 --- a/test/components/marquee_test.exs +++ b/test/components/marquee_test.exs @@ -1,9 +1,17 @@ defmodule Corex.MarqueeTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Marquee alias Corex.Marquee.Connect + describe "marquee/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_marquee/1, []) + assert html =~ ~r/data-scope="marquee"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "pause/1" do test "returns JS command" do js = Marquee.pause("my-marquee") diff --git a/test/components/menu_test.exs b/test/components/menu_test.exs index ee73161..22e7b0d 100644 --- a/test/components/menu_test.exs +++ b/test/components/menu_test.exs @@ -1,9 +1,36 @@ defmodule Corex.MenuTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Menu alias Corex.Menu.Connect + describe "menu/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_menu/1, []) + assert html =~ ~r/data-scope="menu"/ + assert html =~ ~r/menu:/ + end + + test "renders with grouped items" do + html = render_component(&CorexTest.ComponentHelpers.render_menu_grouped/1, []) + assert html =~ ~r/data-part="item-group"/ + assert html =~ ~r/data-part="item-group-label"/ + end + + test "renders with nested items and custom nested_indicator" do + html = render_component(&CorexTest.ComponentHelpers.render_menu_nested/1, []) + assert html =~ ~r/data-scope="menu"/ + assert html =~ ~r/Share/ + assert html =~ ~r/Messages/ + end + + test "renders with controlled" do + html = render_component(&CorexTest.ComponentHelpers.render_menu_controlled/1, []) + assert html =~ ~r/data-scope="menu"/ + assert html =~ ~r/data-controlled/ + end + end + describe "set_open/2" do test "returns JS command when open is true" do js = Menu.set_open("my-menu", true) @@ -118,4 +145,77 @@ defmodule Corex.MenuTest do assert result["role"] == "separator" end end + + describe "Connect.props/1" do + test "returns props when uncontrolled" do + assigns = %{ + id: "test-menu", + controlled: false, + open: false, + dir: "ltr", + close_on_select: true, + loop_focus: false, + typeahead: true, + composite: false, + value: nil, + aria_label: nil, + on_select: nil, + on_select_client: nil, + redirect: false, + on_open_change: nil, + on_open_change_client: nil + } + + result = Connect.props(assigns) + assert result["id"] == "test-menu" + end + + test "returns props when controlled" do + assigns = %{ + id: "test-menu", + controlled: true, + open: true, + dir: "ltr", + close_on_select: true, + loop_focus: false, + typeahead: true, + composite: false, + value: nil, + aria_label: nil, + on_select: nil, + on_select_client: nil, + redirect: false, + on_open_change: nil, + on_open_change_client: nil + } + + result = Connect.props(assigns) + assert result["data-open"] == "" + end + end + + describe "Connect.item_group_label/1" do + test "returns item group label attributes" do + assigns = %{id: "test-menu", group_id: "group-1", dir: "ltr"} + result = Connect.item_group_label(assigns) + assert result["data-part"] == "item-group-label" + end + end + + describe "Connect.item_group/1" do + test "returns item group attributes" do + assigns = %{id: "test-menu", group_id: "group-1", dir: "ltr"} + result = Connect.item_group(assigns) + assert result["data-part"] == "item-group" + end + end + + describe "Connect.nested_menu/1" do + test "returns nested menu attributes" do + assigns = %{id: "test-menu", dir: "ltr"} + result = Connect.nested_menu(assigns) + assert result["data-nested"] == "menu" + assert result["data-scope"] == "menu" + end + end end diff --git a/test/components/native_input_test.exs b/test/components/native_input_test.exs new file mode 100644 index 0000000..9693fc3 --- /dev/null +++ b/test/components/native_input_test.exs @@ -0,0 +1,67 @@ +defmodule Corex.NativeInputTest do + use CorexTest.ComponentCase, async: true + + describe "native_input/1" do + test "renders text input" do + result = + render_component(&Corex.NativeInput.native_input/1, + type: "text", + id: "name", + name: "user[name]", + value: "John" + ) + + elements = + find_in_html(result, ~s([data-scope="native-input"] input[type=text][name="user[name]"])) + + assert [_] = elements + assert Floki.attribute(elements, "value") == ["John"] + end + + test "renders textarea" do + result = + render_component(&Corex.NativeInput.native_input/1, + type: "textarea", + name: "user[bio]", + value: "Hello" + ) + + assert [_] = find_in_html(result, ~s(textarea[name="user[bio]"])) + end + + test "renders checkbox" do + result = + render_component(&Corex.NativeInput.native_input/1, + type: "checkbox", + name: "user[agree]", + value: true + ) + + assert [_] = find_in_html(result, ~s(input[type=checkbox][name="user[agree]"])) + end + + test "renders select with options" do + result = + render_component(&Corex.NativeInput.native_input/1, + type: "select", + name: "user[role]", + options: [Admin: "admin", User: "user"], + prompt: "Choose..." + ) + + assert [_] = find_in_html(result, ~s(select[name="user[role]"])) + end + + test "renders radio with options" do + result = + render_component(&Corex.NativeInput.native_input/1, + type: "radio", + name: "user[size]", + options: [Small: "s", Medium: "m", Large: "l"], + value: "m" + ) + + assert find_in_html(result, ~s(input[type=radio][name="user[size]"])) != [] + end + end +end diff --git a/test/components/navigate_test.exs b/test/components/navigate_test.exs new file mode 100644 index 0000000..ceb54f0 --- /dev/null +++ b/test/components/navigate_test.exs @@ -0,0 +1,140 @@ +defmodule Corex.NavigateTest do + use CorexTest.ComponentCase, async: true + + defp render_with_captured_stderr(fun) do + parent = self() + ref = make_ref() + + _ = + ExUnit.CaptureIO.capture_io(:stderr, fn -> + send(parent, {ref, fun.()}) + end) + + receive do + {^ref, result} -> result + after + 1000 -> flunk("timeout") + end + end + + describe "navigate/1" do + test "renders href link by default" do + result = + render_component(&CorexTest.ComponentHelpers.render_navigate/1, + to: "/about", + type: "href", + external: false, + download: nil, + aria_label: nil + ) + + assert [_] = find_in_html(result, ~s(a[href="/about"])) + assert text_in_html(result) =~ "Link text" + end + + test "renders navigate link" do + result = + render_component(&CorexTest.ComponentHelpers.render_navigate/1, + to: "/dashboard", + type: "navigate", + external: false, + download: nil, + aria_label: nil + ) + + assert [_] = find_in_html(result, "[data-phx-link]") + end + + test "renders external link with target and rel" do + result = + render_component(&CorexTest.ComponentHelpers.render_navigate/1, + to: "https://example.com", + type: "href", + external: true, + download: nil, + aria_label: nil + ) + + assert [_] = find_in_html(result, ~s(a[target="_blank"][rel="noopener noreferrer"])) + end + + test "renders link with download attribute" do + result = + render_component(&CorexTest.ComponentHelpers.render_navigate/1, + to: "/file.pdf", + type: "href", + external: false, + download: "report.pdf", + aria_label: nil + ) + + assert [_] = find_in_html(result, ~s(a[download="report.pdf"])) + end + + test "renders link with aria_label" do + result = + render_component(&CorexTest.ComponentHelpers.render_navigate/1, + to: "/profile", + type: "href", + external: false, + download: nil, + aria_label: "View profile" + ) + + assert [_] = find_in_html(result, ~s(a[aria-label="View profile"])) + end + + test "renders patch link" do + result = + render_component(&CorexTest.ComponentHelpers.render_navigate/1, + to: "/items", + type: "patch", + external: false, + download: nil, + aria_label: nil + ) + + assert [_] = find_in_html(result, "[data-phx-link]") + end + + test "renders link with download boolean" do + result = + render_component(&CorexTest.ComponentHelpers.render_navigate/1, + to: "/file.pdf", + type: "href", + external: false, + download: true, + aria_label: nil + ) + + assert [_] = find_in_html(result, "a[download]") + end + + test "drops replace when type href" do + result = + render_with_captured_stderr(fn -> + render_component(&CorexTest.ComponentHelpers.render_navigate_replace/1, to: "/") + end) + + assert [_] = find_in_html(result, "a[href='/']") + end + + test "drops method when type navigate" do + result = + render_with_captured_stderr(fn -> + render_component(&CorexTest.ComponentHelpers.render_navigate_method/1, to: "/") + end) + + assert [_] = find_in_html(result, "[data-phx-link]") + end + + test "drops external when type patch" do + result = + render_with_captured_stderr(fn -> + render_component(&CorexTest.ComponentHelpers.render_navigate_external_patch/1, to: "/") + end) + + assert [_] = find_in_html(result, "[data-phx-link]") + end + end +end diff --git a/test/components/number_input_test.exs b/test/components/number_input_test.exs index 3962195..56df216 100644 --- a/test/components/number_input_test.exs +++ b/test/components/number_input_test.exs @@ -1,8 +1,17 @@ defmodule Corex.NumberInputTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true + alias Corex.NumberInput alias Corex.NumberInput.Connect + describe "number_input/1" do + test "renders" do + html = render_component(&NumberInput.number_input/1, []) + assert html =~ ~r/data-scope="number-input"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "Connect.root/1" do test "returns root attributes" do assigns = %{id: "test-number"} diff --git a/test/components/password_input_test.exs b/test/components/password_input_test.exs index 5267dff..703666b 100644 --- a/test/components/password_input_test.exs +++ b/test/components/password_input_test.exs @@ -1,8 +1,30 @@ defmodule Corex.PasswordInputTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true + alias Corex.PasswordInput alias Corex.PasswordInput.Connect + describe "password_input/1" do + test "renders" do + html = render_component(&PasswordInput.password_input/1, name: "pass") + assert html =~ ~r/data-scope="password-input"/ + assert html =~ ~r/data-part="root"/ + end + + test "renders with all slots" do + html = render_component(&CorexTest.ComponentHelpers.render_password_input_full/1, []) + assert html =~ ~r/data-scope="password-input"/ + assert html =~ ~r/Password/ + assert html =~ ~r/Show|Hide/ + end + + test "renders with field" do + html = render_component(&CorexTest.ComponentHelpers.render_password_input_with_field/1, []) + assert html =~ ~r/data-scope="password-input"/ + assert html =~ ~r/name="user\[password\]"/ + end + end + describe "Connect.root/1" do test "returns root attributes" do assigns = %{id: "test-password", dir: "ltr"} @@ -22,4 +44,89 @@ defmodule Corex.PasswordInputTest do assert result["for"] == "p-input-test-password-input" end end + + describe "Connect.control/1" do + test "returns control attributes" do + assigns = %{id: "test-password", dir: "ltr"} + result = Connect.control(assigns) + assert result["id"] == "password-input:test-password:control" + assert result["data-part"] == "control" + end + end + + describe "Connect.input/1" do + test "returns input attributes with name" do + assigns = %{id: "test-password", dir: "ltr", name: "pass", disabled: false} + result = Connect.input(assigns) + assert result["id"] == "p-input-test-password-input" + assert result["name"] == "pass" + assert result["data-part"] == "input" + end + end + + describe "Connect.visibility_trigger/1" do + test "returns visibility trigger attributes" do + assigns = %{id: "test-password", dir: "ltr"} + result = Connect.visibility_trigger(assigns) + assert result["data-part"] == "visibility-trigger" + assert result["aria-label"] == "Toggle password visibility" + end + end + + describe "Connect.indicator/1" do + test "returns indicator attributes" do + assigns = %{id: "test-password", dir: "ltr"} + result = Connect.indicator(assigns) + assert result["data-part"] == "indicator" + assert result["aria-hidden"] == "true" + end + end + + describe "Connect.props/1" do + test "returns props when controlled visible" do + assigns = %{ + id: "test-password", + controlled_visible: true, + visible: true, + disabled: false, + invalid: false, + read_only: false, + required: false, + ignore_password_managers: false, + name: "pass", + form: nil, + dir: "ltr", + auto_complete: nil, + on_visibility_change: nil, + on_visibility_change_client: nil + } + + result = Connect.props(assigns) + assert result["data-visible"] == "" + assert result["data-default-visible"] == nil + end + + test "returns props when uncontrolled visible" do + assigns = %{ + id: "test-password", + controlled_visible: false, + visible: true, + disabled: false, + invalid: false, + read_only: false, + required: false, + ignore_password_managers: false, + name: nil, + form: nil, + dir: "ltr", + auto_complete: nil, + on_visibility_change: nil, + on_visibility_change_client: nil + } + + result = Connect.props(assigns) + assert result["data-default-visible"] != nil + assert result["data-visible"] == nil + end + end end diff --git a/test/components/pin_input_test.exs b/test/components/pin_input_test.exs index d9162f9..36b714e 100644 --- a/test/components/pin_input_test.exs +++ b/test/components/pin_input_test.exs @@ -1,8 +1,17 @@ defmodule Corex.PinInputTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true + alias Corex.PinInput alias Corex.PinInput.Connect + describe "pin_input/1" do + test "renders" do + html = render_component(&PinInput.pin_input/1, name: "pin", length: 4) + assert html =~ ~r/data-scope="pin-input"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "Connect.root/1" do test "returns root attributes" do assigns = %{id: "test-pin", dir: "ltr"} diff --git a/test/components/radio_group_test.exs b/test/components/radio_group_test.exs index d15590c..81ab491 100644 --- a/test/components/radio_group_test.exs +++ b/test/components/radio_group_test.exs @@ -1,8 +1,53 @@ defmodule Corex.RadioGroupTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true + alias Corex.RadioGroup alias Corex.RadioGroup.Connect + describe "radio_group/1" do + test "renders" do + html = render_component(&RadioGroup.radio_group/1, items: [["a", "Option A"]]) + assert html =~ ~r/data-scope="radio-group"/ + assert html =~ ~r/data-part="root"/ + end + + test "renders with items as maps" do + html = + render_component(&RadioGroup.radio_group/1, + items: [%{value: "a", label: "A"}, %{value: "b", label: "B", disabled: true}] + ) + + assert html =~ ~r/data-scope="radio-group"/ + assert html =~ ~r/Option A|A/ + end + + test "renders with item_control slot" do + html = render_component(&CorexTest.ComponentHelpers.render_radio_group_with_indicator/1, []) + assert html =~ ~r/data-scope="radio-group"/ + assert html =~ ~r/Option A/ + assert html =~ ~r/Option B/ + end + + test "renders with controlled" do + html = render_component(&CorexTest.ComponentHelpers.render_radio_group_controlled/1, []) + assert html =~ ~r/data-scope="radio-group"/ + assert html =~ ~r/data-controlled/ + end + + test "renders with custom item slot" do + html = render_component(&CorexTest.ComponentHelpers.render_radio_group_with_item_slot/1, []) + assert html =~ ~r/data-scope="radio-group"/ + assert html =~ ~r/data-value="x"/ + assert html =~ ~r/X/ + end + + test "renders with form" do + html = render_component(&CorexTest.ComponentHelpers.render_radio_group_with_form/1, []) + assert html =~ ~r/data-scope="radio-group"/ + assert html =~ ~r/data-form/ + end + end + describe "Connect.root/1" do test "returns root attributes without label" do assigns = %{id: "test-radio", dir: "ltr", orientation: "vertical", has_label: false} @@ -93,6 +138,22 @@ defmodule Corex.RadioGroupTest do end end + describe "Connect.item_text/1" do + test "returns item text attributes" do + assigns = %{ + id: "test-radio", + value: "opt-1", + disabled: false, + invalid: false + } + + result = Connect.item_text(assigns) + assert result["id"] == "radio-group:test-radio:item-text:opt-1" + assert result["data-part"] == "item-text" + assert result["data-value"] == "opt-1" + end + end + describe "Connect.item_hidden_input/1" do test "returns item hidden input attributes" do assigns = %{ @@ -112,4 +173,48 @@ defmodule Corex.RadioGroupTest do assert result["name"] == "choice" end end + + describe "Connect.props/1" do + test "returns props when controlled" do + assigns = %{ + id: "test-radio", + value: "opt-1", + controlled: true, + dir: "ltr", + orientation: "vertical", + disabled: false, + invalid: false, + read_only: false, + name: nil, + form: nil, + required: false, + on_value_change: nil, + on_value_change_client: nil + } + + result = Connect.props(assigns) + assert result["data-value"] == "opt-1" + end + + test "returns props when uncontrolled" do + assigns = %{ + id: "test-radio", + value: "opt-1", + controlled: false, + dir: "ltr", + orientation: "vertical", + disabled: false, + invalid: false, + read_only: false, + name: nil, + form: nil, + required: false, + on_value_change: nil, + on_value_change_client: nil + } + + result = Connect.props(assigns) + assert result["data-default-value"] == "opt-1" + end + end end diff --git a/test/components/select_test.exs b/test/components/select_test.exs index 17b64c7..0b282b8 100644 --- a/test/components/select_test.exs +++ b/test/components/select_test.exs @@ -1,8 +1,16 @@ defmodule Corex.SelectTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Select.Connect + describe "select/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_select/1, []) + assert html =~ ~r/data-scope="select"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "Connect.root/1" do test "returns root attributes" do assigns = %{id: "test-select", invalid: false, read_only: false} @@ -38,4 +46,137 @@ defmodule Corex.SelectTest do assert result["data-scope"] == "select" end end + + describe "Connect.positioner/1" do + test "returns positioner attributes" do + assigns = %{id: "test-select", dir: "ltr"} + result = Connect.positioner(assigns) + assert result["data-part"] == "positioner" + end + end + + describe "Connect.content/1" do + test "returns content attributes" do + assigns = %{id: "test-select", dir: "ltr"} + result = Connect.content(assigns) + assert result["data-part"] == "content" + end + end + + describe "Connect.props/1" do + test "returns props when uncontrolled" do + assigns = %{ + id: "test-select", + collection: [%{id: "a", label: "A"}], + controlled: false, + value: [], + dir: "ltr" + } + + result = Connect.props(Map.merge(default_select_props(), assigns)) + assert result["id"] == "test-select" + end + + test "returns props when controlled" do + assigns = %{ + id: "test-select", + collection: [%{id: "a", label: "A"}], + controlled: true, + value: ["a"], + dir: "ltr" + } + + result = Connect.props(Map.merge(default_select_props(), assigns)) + assert result["data-value"] == "a" + end + + test "returns props with redirect" do + assigns = %{ + id: "test-select", + collection: [%{id: "a", label: "A"}], + controlled: false, + value: [], + dir: "ltr", + redirect: true + } + + result = Connect.props(Map.merge(default_select_props(), assigns)) + assert result["data-redirect"] != nil + end + + test "returns props with positioning" do + assigns = %{ + id: "test-select", + collection: [%{id: "a", label: "A"}], + controlled: false, + value: [], + dir: "ltr", + positioning: %{placement: "bottom"} + } + + result = Connect.props(Map.merge(default_select_props(), assigns)) + assert result["data-positioning"] =~ "placement" + end + + test "returns props with on_value_change" do + assigns = %{ + id: "test-select", + collection: [%{id: "a", label: "A"}], + controlled: false, + value: [], + dir: "ltr", + on_value_change: "phx-value-change" + } + + result = Connect.props(Map.merge(default_select_props(), assigns)) + assert result["data-on-value-change"] == "phx-value-change" + end + + test "returns props with on_value_change_client" do + assigns = %{ + id: "test-select", + collection: [%{id: "a", label: "A"}], + controlled: false, + value: [], + dir: "ltr", + on_value_change_client: "on-change-client" + } + + result = Connect.props(Map.merge(default_select_props(), assigns)) + assert result["data-on-value-change-client"] == "on-change-client" + end + end + + describe "select/1 with options" do + test "renders with controlled and multiple" do + html = render_component(&CorexTest.ComponentHelpers.render_select_controlled_multiple/1, []) + assert html =~ ~r/data-scope="select"/ + assert html =~ ~r/data-part="root"/ + end + + test "renders with grouped collection" do + html = render_component(&CorexTest.ComponentHelpers.render_select_grouped/1, []) + assert html =~ ~r/data-scope="select"/ + end + end + + defp default_select_props do + %{ + invalid: false, + read_only: false, + disabled: false, + name: nil, + placeholder: nil, + close_on_select: true, + loop_focus: false, + multiple: false, + form: nil, + required: false, + positioning: nil, + on_value_change: nil, + on_value_change_client: nil, + redirect: false, + redirect_new_tab: false + } + end end diff --git a/test/components/signature_pad_test.exs b/test/components/signature_pad_test.exs index f109a70..706d776 100644 --- a/test/components/signature_pad_test.exs +++ b/test/components/signature_pad_test.exs @@ -1,8 +1,16 @@ defmodule Corex.SignaturePadTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.SignaturePad.Connect + describe "signature_pad/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_signature_pad/1, []) + assert html =~ ~r/data-scope="signature-pad"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "Connect.root/1" do test "returns root attributes" do assigns = %{id: "test-signature", dir: "ltr"} @@ -20,4 +28,83 @@ defmodule Corex.SignaturePadTest do assert result["id"] == "signature-pad:test-signature:control" end end + + describe "Connect.clear_trigger/1" do + test "returns clear trigger attributes without hidden when has_paths" do + assigns = %{id: "test-signature", dir: "ltr", has_paths: true, aria_label: nil} + result = Connect.clear_trigger(assigns) + assert result["data-part"] == "clear-trigger" + refute Map.has_key?(result, "hidden") + end + + test "returns clear trigger with hidden when no paths" do + assigns = %{id: "test-signature", dir: "ltr", has_paths: false, aria_label: nil} + result = Connect.clear_trigger(assigns) + assert result["hidden"] == "true" + end + end + + describe "signature_pad/1 with options" do + test "renders with controlled" do + html = render_component(&CorexTest.ComponentHelpers.render_signature_pad_controlled/1, []) + assert html =~ ~r/data-controlled/ + end + + test "renders with drawing options" do + html = render_component(&CorexTest.ComponentHelpers.render_signature_pad_drawing_opts/1, []) + assert html =~ ~r/data-scope="signature-pad"/ + end + + test "renders with on_draw_end" do + html = render_component(&CorexTest.ComponentHelpers.render_signature_pad_on_draw_end/1, []) + assert html =~ ~r/data-on-draw-end/ + end + + test "renders with field" do + html = render_component(&CorexTest.ComponentHelpers.render_signature_pad_with_field/1, []) + assert html =~ ~r/data-scope="signature-pad"/ + assert html =~ ~r/name="user\[signature\]"/ + end + + test "renders with field value as list" do + html = + render_component(&CorexTest.ComponentHelpers.render_signature_pad_with_field/1, + params: %{"signature" => ["M0 0 L5 5"]} + ) + + assert html =~ ~r/data-scope="signature-pad"/ + end + + test "renders with paths as list" do + html = + render_component(&CorexTest.ComponentHelpers.render_signature_pad_with_paths/1, + paths: ["M0 0 L10 10"] + ) + + assert html =~ ~r/data-scope="signature-pad"/ + assert html =~ ~r/M0 0 L10 10/ + end + + test "renders with paths as JSON string" do + html = + render_component(&CorexTest.ComponentHelpers.render_signature_pad_with_paths/1, + paths: ~s(["M0 0 L10 10"]) + ) + + assert html =~ ~r/data-scope="signature-pad"/ + end + + test "renders with errors slot" do + html = render_component(&CorexTest.ComponentHelpers.render_signature_pad_with_errors/1, []) + assert html =~ ~r/data-scope="signature-pad"/ + assert html =~ ~r/Required/ + end + end + + describe "clear/1" do + test "returns JS command for client-side clear" do + js = Corex.SignaturePad.clear("my-pad") + assert %Phoenix.LiveView.JS{} = js + end + end end diff --git a/test/components/switch_test.exs b/test/components/switch_test.exs index afcb53f..f3fa4df 100644 --- a/test/components/switch_test.exs +++ b/test/components/switch_test.exs @@ -1,9 +1,17 @@ defmodule Corex.SwitchTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Switch alias Corex.Switch.Connect + describe "switch/1" do + test "renders" do + html = render_component(&Switch.switch/1, checked: false, name: "sw") + assert html =~ ~r/data-scope="switch"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "set_checked/2" do test "returns JS command when checked is true" do js = Switch.set_checked("my-switch", true) diff --git a/test/components/tabs_test.exs b/test/components/tabs_test.exs index f8021ba..d075cf0 100644 --- a/test/components/tabs_test.exs +++ b/test/components/tabs_test.exs @@ -1,14 +1,74 @@ defmodule Corex.TabsTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Tabs alias Corex.Tabs.Connect + describe "tabs/1" do + test "renders with items" do + html = render_component(&CorexTest.ComponentHelpers.render_tabs/1, []) + assert html =~ ~r/data-scope="tabs"/ + assert html =~ ~r/data-part="root"/ + assert html =~ ~r/Tab1/ + end + + test "renders with horizontal orientation" do + html = + render_component(&CorexTest.ComponentHelpers.render_tabs/1, orientation: "horizontal") + + assert html =~ ~r/data-scope="tabs"/ + assert html =~ ~r/data-orientation="horizontal"/ + end + + test "renders with custom slots only" do + html = render_component(&CorexTest.ComponentHelpers.render_tabs_custom_slots_only/1, []) + assert html =~ ~r/data-scope="tabs"/ + assert html =~ ~r/Tab 1/ + assert html =~ ~r/Tab 2/ + assert html =~ ~r/Content 1/ + assert html =~ ~r/Content 2/ + end + + test "renders with items and custom trigger/content slots" do + html = + render_component(&CorexTest.ComponentHelpers.render_tabs_items_with_custom_slots/1, []) + + assert html =~ ~r/data-scope="tabs"/ + assert html =~ ~r/A/ + assert html =~ ~r/B/ + assert html =~ ~r/A content/ + assert html =~ ~r/B content/ + end + end + + describe "tabs_trigger/1" do + test "renders trigger" do + html = render_component(&CorexTest.ComponentHelpers.render_tabs_trigger/1, []) + assert html =~ ~r/data-scope="tabs"/ + assert html =~ ~r/data-part="trigger"/ + assert html =~ ~r/Tab 1/ + end + end + + describe "tabs_content/1" do + test "renders content" do + html = render_component(&CorexTest.ComponentHelpers.render_tabs_content/1, []) + assert html =~ ~r/data-scope="tabs"/ + assert html =~ ~r/data-part="content"/ + assert html =~ ~r/Content 1/ + end + end + describe "set_value/2" do test "returns JS command" do js = Tabs.set_value("my-tabs", "tab-1") assert %Phoenix.LiveView.JS{} = js end + + test "returns JS command for nil to close all tabs" do + js = Tabs.set_value("my-tabs", nil) + assert %Phoenix.LiveView.JS{} = js + end end describe "set_value/3" do @@ -17,6 +77,12 @@ defmodule Corex.TabsTest do result = Tabs.set_value(socket, "my-tabs", "tab-1") assert %Phoenix.LiveView.Socket{} = result end + + test "pushes event to socket with nil to close all tabs" do + socket = %Phoenix.LiveView.Socket{} + result = Tabs.set_value(socket, "my-tabs", nil) + assert %Phoenix.LiveView.Socket{} = result + end end describe "Connect.root/1" do @@ -139,4 +205,42 @@ defmodule Corex.TabsTest do assert result["data-state"] == "closed" end end + + describe "Connect.props/1" do + test "returns props when uncontrolled" do + assigns = %{ + id: "test-tabs", + controlled: false, + value: "tab-1", + orientation: "vertical", + dir: "ltr", + on_value_change: nil, + on_value_change_client: nil, + on_focus_change: nil, + on_focus_change_client: nil + } + + result = Connect.props(assigns) + assert result["data-default-value"] == "tab-1" + assert result["data-value"] == nil + end + + test "returns props when controlled" do + assigns = %{ + id: "test-tabs", + controlled: true, + value: "tab-1", + orientation: "vertical", + dir: "ltr", + on_value_change: nil, + on_value_change_client: nil, + on_focus_change: nil, + on_focus_change_client: nil + } + + result = Connect.props(assigns) + assert result["data-default-value"] == nil + assert result["data-value"] == "tab-1" + end + end end diff --git a/test/components/timer_test.exs b/test/components/timer_test.exs index 82076f4..301e59d 100644 --- a/test/components/timer_test.exs +++ b/test/components/timer_test.exs @@ -1,8 +1,16 @@ defmodule Corex.TimerTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.Timer.Connect + describe "timer/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_timer/1, []) + assert html =~ ~r/data-scope="timer"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "Connect.root/1" do test "returns root attributes" do assigns = %{id: "test-timer"} diff --git a/test/components/toast_test.exs b/test/components/toast_test.exs new file mode 100644 index 0000000..6675c96 --- /dev/null +++ b/test/components/toast_test.exs @@ -0,0 +1,115 @@ +defmodule Corex.ToastTest do + use CorexTest.ComponentCase, async: true + + describe "create_toast/5" do + test "returns JS command for info type" do + js = Corex.Toast.create_toast("layout-toast", "Title", "Description", :info, []) + assert %Phoenix.LiveView.JS{} = js + end + + test "returns JS command for success type" do + js = Corex.Toast.create_toast("layout-toast", "Saved!", "Done", :success, []) + assert %Phoenix.LiveView.JS{} = js + end + + test "returns JS command for error type" do + js = Corex.Toast.create_toast("layout-toast", "Error", "Failed", :error, []) + assert %Phoenix.LiveView.JS{} = js + end + + test "returns JS command for loading type with infinite duration" do + js = + Corex.Toast.create_toast("layout-toast", "Loading", nil, :loading, duration: :infinity) + + assert %Phoenix.LiveView.JS{} = js + end + + test "returns JS command with custom duration" do + js = Corex.Toast.create_toast("layout-toast", "Title", nil, :info, duration: 3000) + assert %Phoenix.LiveView.JS{} = js + end + end + + describe "push_toast/6" do + test "returns modified socket" do + socket = %Phoenix.LiveView.Socket{} + result = Corex.Toast.push_toast(socket, "layout-toast", "Title", "Desc", :success, 5000) + assert %Phoenix.LiveView.Socket{} = result + end + + test "accepts infinite duration" do + socket = %Phoenix.LiveView.Socket{} + result = Corex.Toast.push_toast(socket, "layout-toast", "Loading", nil, :loading, :infinity) + assert %Phoenix.LiveView.Socket{} = result + end + end + + describe "toast_group/1" do + test "renders toast group" do + result = render_component(&Corex.Toast.toast_group/1, id: "layout-toast") + assert [_] = find_in_html(result, ~s([data-scope="toast"][data-part="group"])) + end + + test "renders toast group with flash" do + result = + render_component(&Corex.Toast.toast_group/1, + id: "layout-toast", + flash: %{info: "Hello", error: "Oops"} + ) + + assert [_] = find_in_html(result, ~s([data-scope="toast"])) + end + end + + describe "toast_client_error/1" do + test "renders with phx-disconnected" do + result = + render_component(&Corex.Toast.toast_client_error/1, + toast_group_id: "layout-toast", + title: "Client Error", + description: "Reconnecting" + ) + + assert [_] = find_in_html(result, "[phx-disconnected]") + end + end + + describe "toast_server_error/1" do + test "renders with phx-disconnected" do + result = + render_component(&Corex.Toast.toast_server_error/1, + toast_group_id: "layout-toast", + title: "Server Error", + description: "Retrying" + ) + + assert [_] = find_in_html(result, "[phx-disconnected]") + end + end + + describe "toast_connected/1" do + test "renders with phx-connected" do + result = + render_component(&Corex.Toast.toast_connected/1, + toast_group_id: "layout-toast", + title: "Connected", + description: "Back online" + ) + + assert [_] = find_in_html(result, "[phx-connected]") + end + end + + describe "toast_disconnected/1" do + test "renders with phx-disconnected" do + result = + render_component(&Corex.Toast.toast_disconnected/1, + toast_group_id: "layout-toast", + title: "Disconnected", + description: "Lost connection" + ) + + assert [_] = find_in_html(result, "[phx-disconnected]") + end + end +end diff --git a/test/components/toggle_group_test.exs b/test/components/toggle_group_test.exs index ff3fb38..a09a42f 100644 --- a/test/components/toggle_group_test.exs +++ b/test/components/toggle_group_test.exs @@ -1,9 +1,17 @@ defmodule Corex.ToggleGroupTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.ToggleGroup alias Corex.ToggleGroup.Connect + describe "toggle_group/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_toggle_group/1, []) + assert html =~ ~r/data-scope="toggle-group"/ + assert html =~ ~r/data-part="root"/ + end + end + describe "set_value/2" do test "returns JS command with single value" do js = ToggleGroup.set_value("my-toggle-group", ["item-1"]) diff --git a/test/components/tree_view_test.exs b/test/components/tree_view_test.exs index 8565383..36bd186 100644 --- a/test/components/tree_view_test.exs +++ b/test/components/tree_view_test.exs @@ -1,9 +1,18 @@ defmodule Corex.TreeViewTest do - use ExUnit.Case, async: true + use CorexTest.ComponentCase, async: true alias Corex.TreeView alias Corex.TreeView.Connect + describe "tree_view/1" do + test "renders" do + html = render_component(&CorexTest.ComponentHelpers.render_tree_view/1, []) + assert html =~ ~r/data-scope="tree-view"/ + assert html =~ ~r/data-part="root"/ + assert html =~ ~r/Item/ + end + end + describe "set_expanded_value/2" do test "returns JS command with list" do js = TreeView.set_expanded_value("my-tree", ["node-1"]) @@ -72,4 +81,374 @@ defmodule Corex.TreeViewTest do assert result["data-part"] == "tree" end end + + describe "Connect.item/1" do + test "returns item attributes" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [0], + disabled: false, + redirect: true, + new_tab: false, + dir: "ltr" + } + + result = Connect.item(assigns) + assert result["id"] == "tree-view:test-tree:item:node-1" + assert result["data-value"] == "node-1" + assert result["style"] == "--depth: 1" + end + + test "adds data-name when name present" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + disabled: false, + redirect: true, + new_tab: false, + dir: "ltr", + name: "my-link" + } + + result = Connect.item(assigns) + assert result["data-name"] == "my-link" + end + + test "adds data-redirect when redirect false" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + disabled: false, + redirect: false, + new_tab: false, + dir: "ltr" + } + + result = Connect.item(assigns) + assert result["data-redirect"] == "false" + end + + test "adds data-new-tab when new_tab true" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + disabled: false, + redirect: true, + new_tab: true, + dir: "ltr" + } + + result = Connect.item(assigns) + assert Map.has_key?(result, "data-new-tab") + end + + test "adds data-selected when selected true" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + disabled: false, + redirect: true, + new_tab: false, + dir: "ltr", + selected: true + } + + result = Connect.item(assigns) + assert Map.has_key?(result, "data-selected") + end + + test "adds data-focus when focused true" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + disabled: false, + redirect: true, + new_tab: false, + dir: "ltr", + focused: true + } + + result = Connect.item(assigns) + assert Map.has_key?(result, "data-focus") + end + + test "uses depth 0 when index_path is not list" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: nil, + disabled: false, + redirect: true, + new_tab: false, + dir: "ltr" + } + + result = Connect.item(assigns) + assert result["style"] == "--depth: 0" + end + end + + describe "Connect.branch/1" do + test "returns branch attributes when expanded" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [0], + expanded: true, + disabled: false, + dir: "ltr" + } + + result = Connect.branch(assigns) + assert result["data-state"] == "open" + assert result["data-part"] == "branch" + end + + test "returns branch attributes when collapsed" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + expanded: false, + disabled: false, + dir: "ltr" + } + + result = Connect.branch(assigns) + assert result["data-state"] == "closed" + end + + test "adds data-name when name present" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + expanded: false, + disabled: false, + dir: "ltr", + name: "branch-link" + } + + result = Connect.branch(assigns) + assert result["data-name"] == "branch-link" + end + + test "adds data-selected when selected true" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + expanded: false, + disabled: false, + dir: "ltr", + selected: true + } + + result = Connect.branch(assigns) + assert Map.has_key?(result, "data-selected") + end + + test "adds data-focus when focused true" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + expanded: false, + disabled: false, + dir: "ltr", + focused: true + } + + result = Connect.branch(assigns) + assert Map.has_key?(result, "data-focus") + end + end + + describe "Connect.branch_trigger/1" do + test "returns branch trigger attributes" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [0], + expanded: true, + disabled: false, + dir: "ltr" + } + + result = Connect.branch_trigger(assigns) + assert result["data-part"] == "branch-control" + assert result["data-state"] == "open" + end + + test "adds data-selected when selected true" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + expanded: false, + disabled: false, + dir: "ltr", + selected: true + } + + result = Connect.branch_trigger(assigns) + assert Map.has_key?(result, "data-selected") + end + + test "adds data-focus when focused true" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + expanded: false, + disabled: false, + dir: "ltr", + focused: true + } + + result = Connect.branch_trigger(assigns) + assert Map.has_key?(result, "data-focus") + end + end + + describe "Connect.branch_content/1" do + test "returns branch content when expanded" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + expanded: true, + dir: "ltr" + } + + result = Connect.branch_content(assigns) + refute Map.has_key?(result, "hidden") + end + + test "adds hidden when collapsed" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + expanded: false, + dir: "ltr" + } + + result = Connect.branch_content(assigns) + assert Map.has_key?(result, "hidden") + end + end + + describe "Connect.branch_indicator/1" do + test "returns branch indicator attributes" do + assigns = %{ + id: "test-tree", + value: "node-1", + index_path: [], + expanded: false, + disabled: false, + dir: "ltr" + } + + result = Connect.branch_indicator(assigns) + assert result["data-part"] == "branch-indicator" + assert result["data-state"] == "closed" + end + end + + describe "Connect.branch_text/1" do + test "returns branch text attributes" do + assigns = %{id: "test-tree", value: "node-1", index_path: [], dir: "ltr"} + result = Connect.branch_text(assigns) + assert result["data-part"] == "branch-text" + end + end + + describe "Connect.branch_indent_guide/1" do + test "returns branch indent guide attributes" do + assigns = %{id: "test-tree", value: "node-1", index_path: [0], dir: "ltr"} + result = Connect.branch_indent_guide(assigns) + assert result["data-part"] == "branch-indent-guide" + end + end + + describe "Connect.props/1" do + test "returns props when uncontrolled" do + assigns = %{ + id: "test-tree", + controlled: false, + expanded_value: ["node-1"], + value: ["node-2"], + selection_mode: "single", + dir: "ltr", + on_selection_change: nil, + on_expanded_change: nil, + redirect: true + } + + result = Connect.props(assigns) + assert result["data-default-expanded-value"] == "node-1" + assert result["data-default-selected-value"] == "node-2" + assert result["data-expanded-value"] == nil + assert result["data-selected-value"] == nil + end + + test "returns props when controlled" do + assigns = %{ + id: "test-tree", + controlled: true, + expanded_value: ["node-1"], + value: ["node-2"], + selection_mode: "single", + dir: "ltr", + on_selection_change: nil, + on_expanded_change: nil, + redirect: true + } + + result = Connect.props(assigns) + assert result["data-default-expanded-value"] == nil + assert result["data-default-selected-value"] == nil + assert result["data-expanded-value"] == "node-1" + assert result["data-selected-value"] == "node-2" + end + + test "returns props with redirect false" do + assigns = %{ + id: "test-tree", + controlled: false, + expanded_value: [], + value: [], + selection_mode: "single", + dir: "ltr", + on_selection_change: nil, + on_expanded_change: nil, + redirect: false + } + + result = Connect.props(assigns) + assert result["data-redirect"] == nil + end + end + + describe "tree_view/1 with options" do + test "renders with branch" do + html = render_component(&CorexTest.ComponentHelpers.render_tree_view_with_branch/1, []) + assert html =~ ~r/data-scope="tree-view"/ + assert html =~ ~r/Parent/ + assert html =~ ~r/Child/ + end + + test "renders with controlled" do + html = render_component(&CorexTest.ComponentHelpers.render_tree_view_controlled/1, []) + assert html =~ ~r/data-scope="tree-view"/ + assert html =~ ~r/data-controlled/ + end + end end diff --git a/test/content_test.exs b/test/content_test.exs new file mode 100644 index 0000000..6a4fbc8 --- /dev/null +++ b/test/content_test.exs @@ -0,0 +1,121 @@ +defmodule Corex.ContentTest do + use ExUnit.Case, async: true + + alias Corex.Content + alias Corex.Content.Item + + describe "Content.new/1" do + test "returns empty list for empty input" do + assert Content.new([]) == [] + end + + test "creates list of items from keyword lists" do + items = + Content.new([ + [trigger: "T1", content: "C1"], + [trigger: "T2", content: "C2"] + ]) + + assert length(items) == 2 + assert Enum.all?(items, &is_struct(&1, Item)) + assert Enum.at(items, 0).trigger == "T1" + assert Enum.at(items, 0).content == "C1" + assert Enum.at(items, 1).trigger == "T2" + assert Enum.at(items, 1).content == "C2" + end + + test "creates list of items from maps" do + items = + Content.new([ + %{trigger: "T1", content: "C1"}, + %{trigger: "T2", content: "C2"} + ]) + + assert length(items) == 2 + assert Enum.at(items, 0).trigger == "T1" + assert Enum.at(items, 1).trigger == "T2" + end + + test "accepts id, disabled, meta on items" do + items = + Content.new([ + [id: "custom-id", trigger: "T1", content: "C1", disabled: true, meta: %{x: 1}] + ]) + + assert length(items) == 1 + assert Enum.at(items, 0).id == "custom-id" + assert Enum.at(items, 0).disabled == true + assert Enum.at(items, 0).meta == %{x: 1} + end + + test "raises for invalid list format" do + assert_raise ArgumentError, ~r/invalid item format/, fn -> + Content.new(["not", "keyword"]) + end + end + + test "raises for non-list input" do + assert_raise ArgumentError, ~r/Expected a list/, fn -> + Content.new("not a list") + end + + assert_raise ArgumentError, ~r/Expected a list/, fn -> + Content.new(%{}) + end + end + end + + describe "Content.Item.new/1" do + test "creates item with required fields" do + item = Item.new(trigger: "Lorem", content: "Consectetur") + assert item.trigger == "Lorem" + assert item.content == "Consectetur" + assert is_binary(item.id) + assert String.starts_with?(item.id, "content-") + assert item.disabled == false + end + + test "creates item from map" do + item = Item.new(%{trigger: "T", content: "C"}) + assert item.trigger == "T" + assert item.content == "C" + end + + test "accepts explicit id" do + item = Item.new(id: "my-id", trigger: "T", content: "C") + assert item.id == "my-id" + end + + test "raises when trigger missing" do + assert_raise ArgumentError, ~r/Required fields/, fn -> + Item.new(content: "C only") + end + end + + test "raises when content missing" do + assert_raise ArgumentError, ~r/Required fields/, fn -> + Item.new(trigger: "T only") + end + end + + test "raises for non-keyword non-map input" do + assert_raise ArgumentError, ~r/Expected a keyword list or map/, fn -> + Item.new("string") + end + + assert_raise ArgumentError, ~r/Expected a keyword list or map/, fn -> + Item.new(123) + end + end + end + + describe "Content.generate_id/0" do + test "returns unique id string" do + id1 = Content.generate_id() + id2 = Content.generate_id() + assert is_binary(id1) + assert String.starts_with?(id1, "content-") + refute id1 == id2 + end + end +end diff --git a/test/corex/gettext_test.exs b/test/corex/gettext_test.exs new file mode 100644 index 0000000..0a01c3e --- /dev/null +++ b/test/corex/gettext_test.exs @@ -0,0 +1,45 @@ +defmodule Corex.GettextTest do + use ExUnit.Case, async: false + + describe "backend/1" do + test "returns nil when not configured" do + Application.delete_env(:corex, :gettext_backend) + assert Corex.Gettext.backend() == nil + end + end + + describe "gettext/2" do + test "returns msg when backend is nil" do + Application.delete_env(:corex, :gettext_backend) + assert Corex.Gettext.gettext("Hello") == "Hello" + assert Corex.Gettext.gettext("World", []) == "World" + end + + test "translates when backend is configured" do + Application.put_env(:corex, :gettext_backend, CorexTest.Gettext) + assert Corex.Gettext.gettext("Hello") == "Hello" + Application.delete_env(:corex, :gettext_backend) + end + end + + describe "translate_error/1" do + test "returns msg when backend is nil" do + Application.delete_env(:corex, :gettext_backend) + assert Corex.Gettext.translate_error({"Error", []}) == "Error" + end + + test "uses dngettext when count present and backend configured" do + Application.put_env(:corex, :gettext_backend, CorexTest.Gettext) + result = Corex.Gettext.translate_error({"1 file", [count: 2]}) + assert is_binary(result) + Application.delete_env(:corex, :gettext_backend) + end + + test "uses dgettext when count absent and backend configured" do + Application.put_env(:corex, :gettext_backend, CorexTest.Gettext) + result = Corex.Gettext.translate_error({"Invalid", []}) + assert result == "Invalid" + Application.delete_env(:corex, :gettext_backend) + end + end +end diff --git a/test/corex/helpers_test.exs b/test/corex/helpers_test.exs new file mode 100644 index 0000000..247aad9 --- /dev/null +++ b/test/corex/helpers_test.exs @@ -0,0 +1,88 @@ +defmodule Corex.HelpersTest do + use ExUnit.Case, async: true + + alias Corex.Helpers + + describe "get_boolean/1" do + test "returns empty string for true" do + assert Helpers.get_boolean(true) == "" + end + + test "returns nil for false" do + assert Helpers.get_boolean(false) == nil + end + + test "returns nil for nil" do + assert Helpers.get_boolean(nil) == nil + end + end + + describe "get_boolean/2" do + test "returns empty string when controlled and value is truthy" do + assert Helpers.get_boolean(true, true) == "" + assert Helpers.get_boolean(true, "x") == "" + end + + test "returns nil when controlled and value is falsy" do + assert Helpers.get_boolean(true, false) == nil + assert Helpers.get_boolean(true, nil) == nil + end + + test "returns nil when uncontrolled" do + assert Helpers.get_boolean(false, true) == nil + assert Helpers.get_boolean(false, false) == nil + assert Helpers.get_boolean(nil, true) == nil + end + end + + describe "get_default_boolean/2" do + test "returns empty string when uncontrolled and value is truthy" do + assert Helpers.get_default_boolean(false, true) == "" + end + + test "returns nil when uncontrolled and value is falsy" do + assert Helpers.get_default_boolean(false, false) == nil + assert Helpers.get_default_boolean(false, nil) == nil + end + + test "returns nil when controlled" do + assert Helpers.get_default_boolean(true, true) == nil + assert Helpers.get_default_boolean(true, false) == nil + end + end + + describe "validate_value!/1" do + test "returns empty list for empty input" do + assert Helpers.validate_value!([]) == [] + end + + test "returns list when all elements are strings" do + assert Helpers.validate_value!(["a", "b"]) == ["a", "b"] + assert Helpers.validate_value!(["x"]) == ["x"] + end + + test "raises when list contains non-strings" do + assert_raise ArgumentError, ~r/value must be a list of strings/, fn -> + Helpers.validate_value!([1, 2, 3]) + end + + assert_raise ArgumentError, ~r/value must be a list of strings/, fn -> + Helpers.validate_value!(["a", :atom]) + end + end + + test "raises for non-list input" do + assert_raise ArgumentError, ~r/value must be a list of strings/, fn -> + Helpers.validate_value!("string") + end + + assert_raise ArgumentError, ~r/value must be a list of strings/, fn -> + Helpers.validate_value!(%{}) + end + + assert_raise ArgumentError, ~r/value must be a list of strings/, fn -> + Helpers.validate_value!(123) + end + end + end +end diff --git a/test/corex/json_test.exs b/test/corex/json_test.exs new file mode 100644 index 0000000..4f25c93 --- /dev/null +++ b/test/corex/json_test.exs @@ -0,0 +1,34 @@ +defmodule Corex.JsonTest do + use ExUnit.Case, async: true + + alias Corex.Json + + describe "encoder/0" do + test "returns default Jason when not configured" do + Application.delete_env(:corex, :json_library) + assert Json.encoder() == Jason + end + + test "returns configured library when set" do + Application.put_env(:corex, :json_library, Jason) + assert Json.encoder() == Jason + Application.delete_env(:corex, :json_library) + end + end + + describe "encode!/1" do + test "encodes map to JSON string" do + encoded = Json.encode!(%{a: 1, b: 2}) + assert is_binary(encoded) + assert Jason.decode!(encoded) == %{"a" => 1, "b" => 2} + end + + test "encodes list to JSON string" do + assert Json.encode!([1, 2, 3]) == "[1,2,3]" + end + + test "encodes string" do + assert Json.encode!("hello") == "\"hello\"" + end + end +end diff --git a/test/corex_test.exs b/test/corex_test.exs new file mode 100644 index 0000000..2533bd4 --- /dev/null +++ b/test/corex_test.exs @@ -0,0 +1,61 @@ +defmodule CorexTest do + use CorexTest.ComponentCase, async: true + + describe "__using__/1" do + test "with default opts imports all components" do + defmodule AllComponents do + use Corex + + def try_code(assigns), do: code(assigns) + end + + result = render_component(&AllComponents.try_code/1, code: "def x, do: 1") + assert is_binary(to_string(result)) + end + + test "with only: [code] imports only specified component" do + defmodule OnlyCode do + use Corex, only: [:code] + + def try_code(assigns), do: code(assigns) + end + + result = render_component(&OnlyCode.try_code/1, code: "x") + assert is_binary(to_string(result)) + + assert_raise UndefinedFunctionError, fn -> + OnlyCode.action(%{}) + end + end + + test "with except: [code] excludes specified component" do + defmodule ExceptCode do + use Corex, except: [:code] + + def try_hidden_input(assigns), do: hidden_input(assigns) + end + + result = render_component(&ExceptCode.try_hidden_input/1, id: "x", name: "x", value: "x") + assert is_binary(to_string(result)) + + assert_raise UndefinedFunctionError, fn -> + ExceptCode.code(%{code: "x"}) + end + end + + test "with prefix generates prefixed functions" do + defmodule PrefixedComponents do + use Corex, prefix: "corex", only: [:code] + + def try_code(assigns), do: corex_code(assigns) + end + + result = render_component(&PrefixedComponents.try_code/1, code: "x") + assert is_binary(to_string(result)) + + assert_raise UndefinedFunctionError, fn -> + PrefixedComponents.code(%{code: "x"}) + end + end + end +end diff --git a/test/form_test.exs b/test/form_test.exs new file mode 100644 index 0000000..46c9524 --- /dev/null +++ b/test/form_test.exs @@ -0,0 +1,40 @@ +defmodule Corex.FormTest.Schema do + use Ecto.Schema + + embedded_schema do + field(:name, :string) + end +end + +defmodule Corex.FormTest do + use ExUnit.Case, async: true + + alias Corex.Form + alias Corex.FormTest.Schema + import Phoenix.Component + + describe "get_form_id/1" do + test "returns id from Ecto.Changeset" do + changeset = Ecto.Changeset.change(%Schema{}) + id = Form.get_form_id(changeset) + assert is_binary(id) + assert id == Phoenix.Component.to_form(changeset).id + end + + test "returns id from Phoenix.HTML.Form" do + form = to_form(%{}, as: :user) + assert Form.get_form_id(form) == form.id + assert is_binary(Form.get_form_id(form)) + end + + test "raises for invalid input" do + assert_raise ArgumentError, ~r/expected Ecto.Changeset or Phoenix.HTML.Form/, fn -> + Form.get_form_id(%{}) + end + + assert_raise ArgumentError, ~r/expected Ecto.Changeset or Phoenix.HTML.Form/, fn -> + Form.get_form_id("not a form") + end + end + end +end diff --git a/test/list_test.exs b/test/list_test.exs new file mode 100644 index 0000000..1b41dab --- /dev/null +++ b/test/list_test.exs @@ -0,0 +1,76 @@ +defmodule Corex.ListTest do + use ExUnit.Case, async: true + + alias Corex.List + alias Corex.List.Item + + describe "List.new/1" do + test "returns empty list for empty input" do + assert List.new([]) == [] + end + + test "creates list of items from keyword lists" do + items = + List.new([ + [label: "A", id: "a"], + [label: "B"] + ]) + + assert length(items) == 2 + assert Enum.all?(items, &is_struct(&1, Item)) + assert Enum.at(items, 0).label == "A" + assert Enum.at(items, 0).id == "a" + assert Enum.at(items, 1).label == "B" + assert is_binary(Enum.at(items, 1).id) + end + + test "creates list from maps" do + items = List.new([%{label: "X"}, %{label: "Y"}]) + assert length(items) == 2 + assert Enum.at(items, 0).label == "X" + end + + test "raises for invalid list format" do + assert_raise ArgumentError, ~r/invalid item format/, fn -> + List.new(["a", "b"]) + end + end + + test "raises for non-list input" do + assert_raise ArgumentError, ~r/Expected a list/, fn -> + List.new("not a list") + end + end + end + + describe "List.Item.new/1" do + test "creates item with required label" do + item = Item.new(label: "Foo") + assert item.label == "Foo" + assert is_binary(item.id) + assert String.starts_with?(item.id, "list-") + end + + test "raises when label missing" do + assert_raise ArgumentError, ~r/Required fields/, fn -> + Item.new(id: "x") + end + end + + test "raises for non-keyword non-map input" do + assert_raise ArgumentError, ~r/Expected a keyword list or map/, fn -> + Item.new("string") + end + end + end + + describe "List.generate_id/0" do + test "returns unique id" do + id1 = List.generate_id() + id2 = List.generate_id() + assert is_binary(id1) + assert String.starts_with?(id1, "list-") + refute id1 == id2 + end + end +end diff --git a/test/mix/tasks/corex.code_test.exs b/test/mix/tasks/corex.code_test.exs new file mode 100644 index 0000000..561f52c --- /dev/null +++ b/test/mix/tasks/corex.code_test.exs @@ -0,0 +1,48 @@ +defmodule Mix.Tasks.Corex.CodeTest do + use ExUnit.Case, async: false + + @tag :tmp_dir + test "generates stylesheet at given path", %{tmp_dir: tmp_dir} do + path = Path.join(tmp_dir, "assets/css/code_highlight.css") + Mix.Task.reenable("corex.code") + Mix.Task.run("corex.code", [path]) + assert File.exists?(path) + content = File.read!(path) + assert content =~ ~r/\.highlight|\.token|pre/ + end + + @tag :tmp_dir + test "generates stylesheet at custom path", %{tmp_dir: tmp_dir} do + path = Path.join(tmp_dir, "custom/syntax.css") + Mix.Task.reenable("corex.code") + Mix.Task.run("corex.code", [path]) + assert File.exists?(path) + content = File.read!(path) + assert is_binary(content) + assert byte_size(content) > 0 + end + + @tag :tmp_dir + test "overwrites with --force", %{tmp_dir: tmp_dir} do + path = Path.join(tmp_dir, "code_highlight.css") + File.mkdir_p!(tmp_dir) + File.write!(path, "old content") + Mix.Task.reenable("corex.code") + Mix.Task.run("corex.code", [path, "--force"]) + content = File.read!(path) + refute content == "old content" + assert byte_size(content) > 0 + end + + @tag :tmp_dir + test "raises when file exists without --force", %{tmp_dir: tmp_dir} do + path = Path.join(tmp_dir, "existing.css") + File.mkdir_p!(tmp_dir) + File.write!(path, "existing") + Mix.Task.reenable("corex.code") + + assert_raise Mix.Error, ~r/already exists/, fn -> + Mix.Task.run("corex.code", [path]) + end + end +end diff --git a/test/mix/tasks/corex.design_test.exs b/test/mix/tasks/corex.design_test.exs new file mode 100644 index 0000000..dd4ea24 --- /dev/null +++ b/test/mix/tasks/corex.design_test.exs @@ -0,0 +1,64 @@ +defmodule Mix.Tasks.Corex.DesignTest do + use ExUnit.Case, async: false + + @tag :tmp_dir + test "copies design files to given path", %{tmp_dir: tmp_dir} do + target = Path.join(tmp_dir, "assets/corex") + Mix.Task.reenable("corex.design") + Mix.Task.run("corex.design", [target]) + assert File.exists?(target) + assert File.exists?(Path.join(target, "components")) + assert File.exists?(Path.join(target, "main.css")) + end + + @tag :tmp_dir + test "copies design files to custom path", %{tmp_dir: tmp_dir} do + target = Path.join(tmp_dir, "custom/design") + Mix.Task.reenable("corex.design") + Mix.Task.run("corex.design", [target]) + assert File.exists?(target) + assert File.exists?(Path.join(target, "main.css")) + end + + @tag :tmp_dir + test "overwrites existing target with --force", %{tmp_dir: tmp_dir} do + target = Path.join(tmp_dir, "design") + File.mkdir_p!(target) + File.write!(Path.join(target, "marker"), "before") + Mix.Task.reenable("corex.design") + Mix.Task.run("corex.design", [target, "--force"]) + assert File.exists?(target) + assert File.exists?(Path.join(target, "main.css")) + end + + @tag :tmp_dir + test "raises when target exists without --force", %{tmp_dir: tmp_dir} do + target = Path.join(tmp_dir, "existing") + File.mkdir_p!(target) + Mix.Task.reenable("corex.design") + + assert_raise Mix.Error, ~r/already exists/, fn -> + Mix.Task.run("corex.design", [target]) + end + end + + @tag :tmp_dir + test "accepts --designex option", %{tmp_dir: tmp_dir} do + target = Path.join(tmp_dir, "design_with_tokens") + Mix.Task.reenable("corex.design") + Mix.Task.run("corex.design", [target, "--designex"]) + assert File.exists?(target) + assert File.exists?(Path.join(target, "design")) + end + + @tag :tmp_dir + test "excludes design tokens when run without --designex", %{tmp_dir: tmp_dir} do + target = Path.join(tmp_dir, "design_no_tokens") + Mix.Task.reenable("corex.design") + Mix.Task.run("corex.design", [target]) + assert File.exists?(target) + assert File.exists?(Path.join(target, "main.css")) + design_path = Path.join(target, "design") + refute File.dir?(design_path) + end +end diff --git a/test/support/component_case.ex b/test/support/component_case.ex new file mode 100644 index 0000000..db26051 --- /dev/null +++ b/test/support/component_case.ex @@ -0,0 +1,26 @@ +defmodule CorexTest.ComponentCase do + @moduledoc false + + use ExUnit.CaseTemplate + + using do + quote do + @endpoint CorexTest.Endpoint + import Phoenix.LiveViewTest + + defp find_in_html(html, selector) do + {:ok, doc} = Floki.parse_fragment(to_string(html)) + Floki.find(doc, selector) + end + + defp text_in_html(html) do + {:ok, doc} = Floki.parse_fragment(to_string(html)) + Floki.text(doc) + end + end + end + + setup _tags do + :ok + end +end diff --git a/test/support/component_helpers.ex b/test/support/component_helpers.ex new file mode 100644 index 0000000..d93bd36 --- /dev/null +++ b/test/support/component_helpers.ex @@ -0,0 +1,609 @@ +defmodule CorexTest.ComponentHelpers do + @moduledoc false + + use Phoenix.Component + + import Corex.Accordion + import Corex.Action + import Corex.Avatar + import Corex.Carousel + import Corex.Clipboard + import Corex.Collapsible + import Corex.Combobox + import Corex.DatePicker + import Corex.Dialog + import Corex.Editable + import Corex.FloatingPanel + import Corex.Listbox + import Corex.Marquee + import Corex.Menu + import Corex.Navigate + import Corex.PasswordInput + import Corex.RadioGroup + import Corex.Select + import Corex.SignaturePad + import Corex.Tabs + import Corex.Timer + import Corex.ToggleGroup + import Corex.TreeView + + def render_accordion(assigns) do + assigns = + assigns + |> assign_new(:items, fn -> Corex.Content.new([[trigger: "T1", content: "C1"]]) end) + |> assign_new(:orientation, fn -> "vertical" end) + |> assign_new(:collapsible, fn -> true end) + |> assign_new(:multiple, fn -> true end) + |> assign_new(:dir, fn -> nil end) + + ~H""" + <.accordion items={@items} orientation={@orientation} collapsible={@collapsible} multiple={@multiple} dir={@dir} /> + """ + end + + def render_accordion_with_indicator(assigns) do + assigns = + assign_new(assigns, :items, fn -> Corex.Content.new([[trigger: "T1", content: "C1"]]) end) + + ~H""" + <.accordion items={@items}> + <:indicator :let={_item}>! + + """ + end + + def render_accordion_with_custom_slots(assigns) do + assigns = + assign_new(assigns, :items, fn -> Corex.Content.new([[trigger: "T1", content: "C1"]]) end) + + ~H""" + <.accordion items={@items}> + <:trigger :let={_item}>Custom + <:content :let={_item}>Custom content + + """ + end + + def render_avatar(assigns) do + ~H""" + <.avatar><:fallback>JD + """ + end + + def render_carousel(assigns) do + ~H""" + <.carousel items={["/img1.jpg"]}> + <:prev_trigger>Prev + <:next_trigger>Next + + """ + end + + def render_clipboard(assigns) do + ~H""" + <.clipboard value={Map.get(assigns, :value, "text")}> + <:label>Copy + <:trigger>Copy + + """ + end + + def render_collapsible(assigns) do + ~H""" + <.collapsible open={true}> + <:trigger>Toggle + <:content>Content + + """ + end + + def render_combobox(assigns) do + ~H""" + <.combobox collection={[]}> + <:empty>No items + <:trigger>Select + + """ + end + + def render_combobox_with_items(assigns) do + ~H""" + <.combobox collection={[%{id: "a", label: "A"}]}> + <:empty>No results + <:trigger>Select + + """ + end + + def render_combobox_with_item_slot(assigns) do + ~H""" + <.combobox collection={[%{id: "x", label: "X"}]}> + <:empty>None + <:item :let={item}>{item.label}! + <:trigger>Open + + """ + end + + def render_combobox_with_clear_and_indicator(assigns) do + ~H""" + <.combobox collection={[%{id: "b", label: "B"}]}> + <:empty>None + <:trigger>Open + <:clear_trigger>Clear + <:item_indicator>Check + + """ + end + + def render_combobox_grouped(assigns) do + ~H""" + <.combobox collection={[%{id: "e1", label: "E1", group: "Europe"}, %{id: "a1", label: "A1", group: "Asia"}]}> + <:empty>None + <:trigger>Open + + """ + end + + def render_combobox_filter_false(assigns) do + ~H""" + <.combobox collection={[%{id: "c", label: "C"}]} filter={false}> + <:empty>None + <:trigger>Open + + """ + end + + def render_combobox_controlled_multiple(assigns) do + ~H""" + <.combobox collection={[%{id: "m1", label: "M1"}, %{id: "m2", label: "M2"}]} controlled value={["m1"]} multiple> + <:empty>None + <:trigger>Open + + """ + end + + def render_combobox_with_errors(assigns) do + ~H""" + <.combobox collection={[]} id="cb" errors={["Required"]}> + <:empty>None + <:trigger>Open + <:error :let={msg}>{msg} + + """ + end + + def render_date_picker(assigns) do + ~H""" + <.date_picker> + <:label>Date + <:trigger>Pick date + + """ + end + + def render_dialog(assigns) do + ~H""" + <.dialog id="test-dialog"> + <:trigger>Open + <:content>Dialog content + + """ + end + + def render_dialog_nested_slots(assigns) do + ~H""" + <.dialog id="dialog-nested"> + <:trigger>Open + <:content> + <.dialog_title id="dialog-nested">Nested Title + <.dialog_description id="dialog-nested">Nested desc +

Body

+ <.dialog_close_trigger id="dialog-nested">× + + + """ + end + + def render_dialog_controlled(assigns) do + ~H""" + <.dialog id="test-dialog-ctrl" controlled open={false}> + <:trigger>Open + <:title>Title + <:description>Description + <:content>

Content

+ <:close_trigger>Close + + """ + end + + def render_editable(assigns) do + ~H""" + <.editable value="text"> + <:label>Label + <:edit_trigger>Edit + <:submit_trigger>Save + <:cancel_trigger>Cancel + + """ + end + + def render_floating_panel(assigns) do + ~H""" + <.floating_panel> + <:open_trigger>Open + <:closed_trigger>Closed + <:minimize_trigger>Min + <:maximize_trigger>Max + <:default_trigger>Default + <:close_trigger>Close + <:content>Content + + """ + end + + def render_listbox(assigns) do + ~H""" + <.listbox collection={[%{label: "A", id: "a"}]} /> + """ + end + + def render_listbox_grouped(assigns) do + ~H""" + <.listbox collection={[%{label: "E1", id: "e1", group: "Europe"}, %{label: "A1", id: "a1", group: "Asia"}]} /> + """ + end + + def render_listbox_list_items(assigns) do + ~H""" + <.listbox collection={[%Corex.List.Item{id: "li-1", label: "Item 1"}, %Corex.List.Item{id: "li-2", label: "Item 2"}]} /> + """ + end + + def render_listbox_controlled(assigns) do + ~H""" + <.listbox collection={[%{label: "A", id: "a"}]} controlled value={["a"]} /> + """ + end + + def render_marquee(assigns) do + ~H""" + <.marquee items={[%{id: "1"}]} duration={10}> + <:item :let={_item}>Item + + """ + end + + def render_menu(assigns) do + ~H""" + <.menu items={Corex.Tree.new([ [label: "Item", id: "1"] ])}> + <:trigger>Menu + + """ + end + + def render_menu_grouped(assigns) do + ~H""" + <.menu items={Corex.Tree.new([ [label: "A1", id: "a1", group: "Group A"], [label: "A2", id: "a2", group: "Group A"], [label: "B1", id: "b1", group: "Group B"] ])}> + <:trigger>Menu + + """ + end + + def render_menu_nested(assigns) do + ~H""" + <.menu items={Corex.Tree.new([ [label: "Share", id: "share", children: [ [label: "Messages", id: "messages"] ] ] ])}> + <:trigger>Menu + <:nested_indicator>→ + + """ + end + + def render_menu_controlled(assigns) do + ~H""" + <.menu items={Corex.Tree.new([ [label: "Item", id: "1"] ])} controlled open={false}> + <:trigger>Menu + + """ + end + + def render_select(assigns) do + ~H""" + <.select collection={[%{label: "A", id: "a"}]}> + <:trigger>Select + + """ + end + + def render_select_controlled_multiple(assigns) do + ~H""" + <.select collection={[%{label: "A", id: "a"}, %{label: "B", id: "b"}]} controlled value={["a"]} multiple> + <:trigger>Select + + """ + end + + def render_select_grouped(assigns) do + ~H""" + <.select collection={[%{label: "E1", id: "e1", group: "Europe"}, %{label: "A1", id: "a1", group: "Asia"}]}> + <:trigger>Select + + """ + end + + def render_tabs(assigns) do + assigns = assign_new(assigns, :orientation, fn -> "vertical" end) + + ~H""" + <.tabs items={Corex.Content.new([ [trigger: "Tab1", content: "C1"] ])} orientation={@orientation} /> + """ + end + + def render_tabs_trigger(assigns) do + assigns = + assign(assigns, :item, %{ + id: "test-tabs", + value: "tab-1", + values: ["tab-1"], + disabled: false, + orientation: "vertical", + dir: "ltr" + }) + + ~H""" + <.tabs_trigger item={@item}>Tab 1 + """ + end + + def render_tabs_content(assigns) do + assigns = + assign(assigns, :item, %{ + id: "test-tabs", + value: "tab-1", + values: ["tab-1"], + disabled: false, + orientation: "vertical", + dir: "ltr" + }) + + ~H""" + <.tabs_content item={@item}>Content 1 + """ + end + + def render_tabs_custom_slots_only(assigns) do + ~H""" + <.tabs id="custom-tabs" value="tab-2"> + <:trigger value="tab-1">Tab 1 + <:trigger value="tab-2">Tab 2 + <:content value="tab-1">Content 1 + <:content value="tab-2">Content 2 + + """ + end + + def render_tabs_items_with_custom_slots(assigns) do + assigns = + assign_new(assigns, :items, fn -> + Corex.Content.new([ + [trigger: "A", content: "A content"], + [trigger: "B", content: "B content"] + ]) + end) + + ~H""" + <.tabs id="items-custom-tabs" items={@items} value="item-0"> + <:trigger :let={item}>{item.data.trigger} + <:content :let={item}>{item.data.content} + + """ + end + + def render_timer(assigns) do + ~H""" + <.timer start_ms={60_000}> + <:start_trigger>Start + <:pause_trigger>Pause + <:resume_trigger>Resume + <:reset_trigger>Reset + + """ + end + + def render_signature_pad(assigns) do + ~H""" + <.signature_pad name="sig"> + <:label>Sign + <:clear_trigger>Clear + + """ + end + + def render_signature_pad_controlled(assigns) do + ~H""" + <.signature_pad id="sig-pad" controlled paths={[]}> + <:label>Sign + <:clear_trigger>Clear + + """ + end + + def render_signature_pad_drawing_opts(assigns) do + ~H""" + <.signature_pad id="sig-pad" drawing_fill="blue" drawing_size={3} drawing_simulate_pressure> + <:label>Sign + <:clear_trigger>Clear + + """ + end + + def render_signature_pad_on_draw_end(assigns) do + ~H""" + <.signature_pad id="sig-pad" on_draw_end="signature_drawn"> + <:label>Sign + <:clear_trigger>Clear + + """ + end + + def render_signature_pad_with_field(assigns) do + params = Map.get(assigns, :params, %{"signature" => nil}) + form = Phoenix.Component.to_form(params, as: :user) + field = form[:signature] + + assigns = assign(assigns, :field, field) + + ~H""" + <.signature_pad field={@field}> + <:label>Sign + <:clear_trigger>Clear + + """ + end + + def render_signature_pad_with_paths(assigns) do + ~H""" + <.signature_pad id="sig-paths" paths={@paths}> + <:label>Sign + <:clear_trigger>Clear + + """ + end + + def render_signature_pad_with_errors(assigns) do + ~H""" + <.signature_pad id="sig-err" errors={["Required"]}> + <:label>Sign + <:clear_trigger>Clear + <:error :let={msg}>{msg} + + """ + end + + def render_toggle_group(assigns) do + ~H""" + <.toggle_group> + <:item value="a">A + <:item value="b">B + + """ + end + + def render_tree_view(assigns) do + ~H""" + <.tree_view id="tree-test" items={Corex.Tree.new([ [label: "Item", id: "1"] ])} /> + """ + end + + def render_tree_view_with_branch(assigns) do + ~H""" + <.tree_view id="tree-branch" items={Corex.Tree.new([ [label: "Parent", id: "p", children: [ [label: "Child", id: "c"] ] ] ])} /> + """ + end + + def render_tree_view_controlled(assigns) do + ~H""" + <.tree_view id="tree-ctrl" items={Corex.Tree.new([ [label: "Item", id: "1"] ])} controlled expanded_value={[]} value={[]} /> + """ + end + + def render_action(assigns) do + ~H""" + <.action>Click + """ + end + + def render_action_with_opts(assigns) do + ~H""" + <.action type={@type} aria_label={@aria_label} disabled={@disabled}>Save + """ + end + + def render_navigate(assigns) do + ~H""" + <.navigate to={@to} type={@type} external={@external} download={@download} aria_label={@aria_label}> + Link text + + """ + end + + def render_navigate_replace(assigns) do + ~H""" + <.navigate to={@to} type="href" replace>Link + """ + end + + def render_navigate_method(assigns) do + ~H""" + <.navigate to={@to} type="navigate" method="post">Link + """ + end + + def render_password_input_full(assigns) do + ~H""" + <.password_input name="pass"> + <:label>Password + <:visible_indicator>Show + <:hidden_indicator>Hide + <:error :let={msg}>{msg} + + """ + end + + def render_password_input_with_field(assigns) do + form = Phoenix.Component.to_form(%{"password" => nil}, as: :user) + field = form[:password] + assigns = assign(assigns, :field, field) + + ~H""" + <.password_input field={@field}> + <:label>Password + <:visible_indicator>Show + <:hidden_indicator>Hide + <:error :let={msg}>{msg} + + """ + end + + def render_radio_group_with_indicator(assigns) do + ~H""" + <.radio_group id="rg-ind" name="choice" items={[["a", "Option A"], ["b", "Option B"]]}> + <:label>Choose + <:item_control> + + """ + end + + def render_radio_group_controlled(assigns) do + ~H""" + <.radio_group id="rg-ctrl" name="choice" items={[["a", "A"]]} controlled value="a"> + <:label>Choose + + """ + end + + def render_radio_group_with_item_slot(assigns) do + ~H""" + <.radio_group id="rg-item" name="choice" items={[["x", "X"], ["y", "Y"]]}> + <:label>Choose + <:item :let={item}> + {item.label} + + + """ + end + + def render_radio_group_with_form(assigns) do + ~H""" + <.radio_group id="rg-form" name="choice" form="my-form" items={[["a", "A"]]}> + <:label>Choose + + """ + end + + def render_navigate_external_patch(assigns) do + ~H""" + <.navigate to={@to} type="patch" external>Link + """ + end +end diff --git a/test/support/endpoint.ex b/test/support/endpoint.ex new file mode 100644 index 0000000..c6edb72 --- /dev/null +++ b/test/support/endpoint.ex @@ -0,0 +1,3 @@ +defmodule CorexTest.Endpoint do + use Phoenix.Endpoint, otp_app: :corex +end diff --git a/test/support/gettext.ex b/test/support/gettext.ex new file mode 100644 index 0000000..1cb4c70 --- /dev/null +++ b/test/support/gettext.ex @@ -0,0 +1,4 @@ +defmodule CorexTest.Gettext do + @moduledoc false + use Gettext.Backend, otp_app: :corex +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e..409695c 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1,3 @@ +Application.ensure_all_started(:phoenix_live_view) + ExUnit.start() diff --git a/test/tree_test.exs b/test/tree_test.exs new file mode 100644 index 0000000..bb802cc --- /dev/null +++ b/test/tree_test.exs @@ -0,0 +1,122 @@ +defmodule Corex.TreeTest do + use ExUnit.Case, async: true + + alias Corex.Tree + alias Corex.Tree.Item + + describe "Tree.new/1" do + test "returns empty list for empty input" do + assert Tree.new([]) == [] + end + + test "creates list of items from keyword lists" do + items = + Tree.new([ + [label: "File", id: "file"], + [label: "Edit"] + ]) + + assert length(items) == 2 + assert Enum.all?(items, &is_struct(&1, Item)) + assert Enum.at(items, 0).label == "File" + assert Enum.at(items, 0).id == "file" + assert Enum.at(items, 1).label == "Edit" + end + + test "creates nested items with children" do + items = + Tree.new([ + [label: "File", children: [[label: "New"], [label: "Open"]]] + ]) + + assert length(items) == 1 + assert Enum.at(items, 0).label == "File" + assert length(Enum.at(items, 0).children) == 2 + assert Enum.at(Enum.at(items, 0).children, 0).label == "New" + assert Enum.at(Enum.at(items, 0).children, 1).label == "Open" + end + + test "creates items from maps" do + items = Tree.new([%{label: "Map item", id: "map1"}]) + assert length(items) == 1 + assert Enum.at(items, 0).label == "Map item" + assert Enum.at(items, 0).id == "map1" + end + + test "creates items with group and meta" do + items = + Tree.new([ + [label: "A1", id: "a1", group: "Group A"], + [label: "B1", id: "b1", group: "Group B", meta: %{key: "val"}] + ]) + + assert length(items) == 2 + assert Enum.at(items, 0).group == "Group A" + assert Enum.at(items, 1).group == "Group B" + assert Enum.at(items, 1).meta == %{key: "val"} + end + + test "raises for invalid list format" do + assert_raise ArgumentError, ~r/invalid item format/, fn -> + Tree.new(["a", "b"]) + end + end + + test "raises for non-list input" do + assert_raise ArgumentError, ~r/Expected a list/, fn -> + Tree.new("not a list") + end + end + end + + describe "Tree.Item.new/1" do + test "creates item from map" do + item = Item.new(%{label: "From map"}) + assert item.label == "From map" + assert is_binary(item.id) + end + + test "creates item with required label" do + item = Item.new(label: "Foo") + assert item.label == "Foo" + assert is_binary(item.id) + assert String.starts_with?(item.id, "tree-") + assert item.children == [] + end + + test "creates item with children" do + item = Item.new(label: "Parent", children: [[label: "Child"]]) + assert item.label == "Parent" + assert length(item.children) == 1 + assert Enum.at(item.children, 0).label == "Child" + end + + test "raises for invalid child type" do + assert_raise ArgumentError, ~r/Invalid child item/, fn -> + Item.new(label: "Parent", children: [[label: "Valid"], 123]) + end + end + + test "raises when label missing" do + assert_raise ArgumentError, ~r/Required fields/, fn -> + Item.new(id: "x") + end + end + + test "raises for non-keyword non-map input" do + assert_raise ArgumentError, ~r/Expected a keyword list or map/, fn -> + Item.new("string") + end + end + end + + describe "Tree.generate_id/0" do + test "returns unique id" do + id1 = Tree.generate_id() + id2 = Tree.generate_id() + assert is_binary(id1) + assert String.starts_with?(id1, "tree-") + refute id1 == id2 + end + end +end