diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..8a524c0 --- /dev/null +++ b/.credo.exs @@ -0,0 +1,219 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "lib/", + "src/", + "test/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/" + ], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: false, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: %{ + enabled: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, false}, + {Credo.Check.Design.TagFIXME, []}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, [only_greater_than: 99_999]}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, false}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, false}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, [max_complexity: 12]}, + {Credo.Check.Refactor.FilterCount, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, [max_nesting: 3]}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.Dbg, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.StructFieldAmount, []}, + {Credo.Check.Warning.UnsafeExec, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedMapOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.WrongTestFileExtension, []} + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now) + {Credo.Check.Refactor.UtcNowTruncate, []}, + + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.UnusedVariableNames, []}, + {Credo.Check.Design.DuplicatedCode, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + {Credo.Check.Readability.AliasAs, []}, + {Credo.Check.Readability.BlockPipe, []}, + {Credo.Check.Readability.ImplTrue, []}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.NestedFunctionCalls, []}, + {Credo.Check.Readability.OneArityFunctionInPipe, []}, + {Credo.Check.Readability.OnePipePerLine, []}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, + {Credo.Check.Readability.SinglePipe, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.StrictModuleLayout, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, + {Credo.Check.Refactor.FilterReject, []}, + {Credo.Check.Refactor.IoPuts, []}, + {Credo.Check.Refactor.MapMap, []}, + {Credo.Check.Refactor.ModuleDependencies, []}, + {Credo.Check.Refactor.NegatedIsNil, []}, + {Credo.Check.Refactor.PassAsyncInTestCases, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectFilter, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + # {Credo.Check.Warning.UnusedOperation, [{MyMagicModule, [:fun1, :fun2]}]} + + # {Credo.Check.Refactor.MapInto, []}, + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + } + ] +} diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs new file mode 100644 index 0000000..6d301fb --- /dev/null +++ b/.dialyzer_ignore.exs @@ -0,0 +1,30 @@ +[ + # Mix.Task callbacks and functions not in dialyzer PLT (dev-only modules) + {"lib/mix/tasks/generate_mockups.ex", :callback_info_missing}, + {"lib/mix/tasks/generate_mockups.ex", :unknown_function}, + {"lib/mix/tasks/optimize_images.ex", :callback_info_missing}, + {"lib/mix/tasks/optimize_images.ex", :unknown_function}, + {"lib/mix/tasks/register_webhooks.ex", :callback_info_missing}, + {"lib/mix/tasks/register_webhooks.ex", :unknown_function}, + {"lib/mix/tasks/simpleshop/download_images.ex", :callback_info_missing}, + {"lib/mix/tasks/simpleshop/download_images.ex", :unknown_function}, + + # Stripe library type specs cause false positives + {"lib/simpleshop_theme/stripe/setup.ex", :pattern_match_cov}, + {"lib/simpleshop_theme/stripe/setup.ex", :pattern_match}, + {"lib/simpleshop_theme/stripe/setup.ex", :no_return}, + {"lib/simpleshop_theme/stripe/setup.ex", :call}, + {"lib/simpleshop_theme_web/controllers/checkout_controller.ex", :call}, + {"lib/simpleshop_theme_web/controllers/checkout_controller.ex", :pattern_match_cov}, + {"lib/simpleshop_theme_web/controllers/checkout_controller.ex", :no_return}, + + # Environment-dependent: localhost?() is always true in dev + {"lib/simpleshop_theme_web/live/admin_live/settings.ex", :pattern_match}, + + # Provider behaviour type not derived by dialyzer + {"lib/simpleshop_theme/providers/provider.ex", :unknown_type}, + + # ExUnit internals not in PLT (test support files) + {"test/support/conn_case.ex", :unknown_function}, + {"test/support/data_case.ex", :unknown_function} +] diff --git a/PROGRESS.md b/PROGRESS.md index a050130..70b4088 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -20,7 +20,7 @@ - Transactional emails (order confirmation, shipping notification) - Demo content polished and ready for production -**Tier 1 MVP complete.** Next up: Tier 2 — hosting & deployment. +**Tier 1 MVP complete.** CI pipeline done. Next up: Tier 2 — hosting & deployment. ## Roadmap @@ -35,7 +35,7 @@ 5. **Hosting & deployment** — Fly.io or similar deployment config. Release configuration, runtime env setup, health checks. Observability: structured logging, error tracking (Sentry or similar), basic metrics. 6. **Litestream / SQLite replication** — Litestream for continuous SQLite backup to S3-compatible storage. Point-in-time recovery. Simple sidecar process, no code changes needed, works with vanilla SQLite. For the hosted platform (Tier 5), evaluate [Turso](https://turso.tech/) (libSQL fork of SQLite) with embedded read replicas via [ecto_libsql](https://github.com/ocean/ecto_libsql) adapter — gives multi-node reads without a separate replication daemon, but adds a dependency on the libSQL fork. -7. **CI pipeline** — All checks implemented as mix aliases (`mix ci` wrapping `mix precommit` + credo + dialyzer). CI config is a thin wrapper that calls `mix ci` — portable across GitHub Actions, Forgejo Actions, Woodpecker, or plain SSH. No proprietary CI dependencies. Custom mix tasks for anything project-specific (e.g. `mix lighthouse`). Oban stays for runtime background jobs, not CI. +7. ~~**CI pipeline**~~ — ✅ Complete. `mix ci` alias: compile --warning-as-errors, deps.unlock --unused, format --check-formatted, credo, dialyzer, test. Credo configured with sensible defaults. Dialyzer with ignore file for false positives (Stripe types, Mix tasks, ExUnit internals). 612 tests, 0 failures. 8. **PageSpeed in CI** — Lighthouse CI to catch regressions. Fail the build if score drops below threshold. Protects the current 100% score. 9. **End-to-end tests** — Wallaby or similar for browser-based tests of critical flows: browse → add to cart → checkout → order confirmation. Covers the full happy path plus key error cases. @@ -219,6 +219,16 @@ All shop pages now have LiveView integration tests (612 total): - **Collection page** (16 tests, pre-existing) — category filtering, sorting, URL params - **Content pages** (10 tests, pre-existing) — about, delivery, privacy, terms +### CI Pipeline +**Status:** Complete + +- `mix ci` alias: compile --warning-as-errors → deps.unlock --unused → format --check-formatted → credo → dialyzer → test +- `mix precommit` alias: compile --warning-as-errors → deps.unlock --unused → format → test +- Credo with tuned config (disabled AliasUsage, ModuleDoc, PredicateFunctionNames; relaxed line length, nesting, complexity) +- Dialyzer with ignore file for known false positives (Stripe library types, Mix.Task dev-only modules, ExUnit internals) +- All credo issues resolved (map_join, filter consolidation, nesting extraction) +- 612 tests, 0 failures + ### Page Editor **Status:** Future (Tier 4) @@ -232,6 +242,7 @@ See: [docs/plans/page-builder.md](docs/plans/page-builder.md) for design | Feature | Commit | Notes | |---------|--------|-------| +| CI pipeline | — | mix ci/precommit aliases, credo, dialyzer, 612 tests | | Default content pages | 5a43cfc | Generic Content LiveView, delivery/privacy/terms pages, 10 tests | | Transactional emails | — | Plain text order confirmation + shipping notification, 10 tests | | Printify order submission & fulfilment | — | Submit, track, webhooks, polling, admin UI, 33 tests | diff --git a/lib/mix/tasks/generate_mockups.ex b/lib/mix/tasks/generate_mockups.ex index e4541b0..2c43c50 100644 --- a/lib/mix/tasks/generate_mockups.ex +++ b/lib/mix/tasks/generate_mockups.ex @@ -129,7 +129,7 @@ defmodule Mix.Tasks.GenerateMockups do case MockupGenerator.search_blueprints(term) do results when is_list(results) -> - if length(results) == 0 do + if results == [] do Mix.shell().info("No blueprints found matching '#{term}'") else Mix.shell().info("\nMatching Blueprints:\n") diff --git a/lib/simpleshop_theme/clients/printify.ex b/lib/simpleshop_theme/clients/printify.ex index e483c22..7a8c029 100644 --- a/lib/simpleshop_theme/clients/printify.ex +++ b/lib/simpleshop_theme/clients/printify.ex @@ -89,7 +89,7 @@ defmodule SimpleshopTheme.Clients.Printify do """ def get_shop_id do case get_shops() do - {:ok, shops} when is_list(shops) and length(shops) > 0 -> + {:ok, shops} when is_list(shops) and shops != [] -> {:ok, hd(shops)["id"]} {:ok, []} -> diff --git a/lib/simpleshop_theme/mockups/generator.ex b/lib/simpleshop_theme/mockups/generator.ex index 955f68c..8360f80 100644 --- a/lib/simpleshop_theme/mockups/generator.ex +++ b/lib/simpleshop_theme/mockups/generator.ex @@ -214,7 +214,7 @@ defmodule SimpleshopTheme.Mockups.Generator do """ def find_print_provider(blueprint_id) do case Client.get_print_providers(blueprint_id) do - {:ok, providers} when is_list(providers) and length(providers) > 0 -> + {:ok, providers} when is_list(providers) and providers != [] -> # Just pick the first provider for simplicity {:ok, hd(providers)} diff --git a/lib/simpleshop_theme/orders/fulfilment_status_worker.ex b/lib/simpleshop_theme/orders/fulfilment_status_worker.ex index 0287795..254b1fc 100644 --- a/lib/simpleshop_theme/orders/fulfilment_status_worker.ex +++ b/lib/simpleshop_theme/orders/fulfilment_status_worker.ex @@ -23,24 +23,27 @@ defmodule SimpleshopTheme.Orders.FulfilmentStatusWorker do Logger.info("Polling fulfilment status for #{length(orders)} order(s)") Enum.each(orders, fn order -> - case Orders.refresh_fulfilment_status(order) do - {:ok, updated} -> - if updated.fulfilment_status != order.fulfilment_status do - Logger.info( - "Order #{order.order_number} status: #{order.fulfilment_status} → #{updated.fulfilment_status}" - ) - end - - {:error, reason} -> - Logger.warning( - "Failed to refresh status for order #{order.order_number}: #{inspect(reason)}" - ) - end - + refresh_order(order) Process.sleep(200) end) :ok end end + + defp refresh_order(order) do + case Orders.refresh_fulfilment_status(order) do + {:ok, updated} -> + if updated.fulfilment_status != order.fulfilment_status do + Logger.info( + "Order #{order.order_number} status: #{order.fulfilment_status} → #{updated.fulfilment_status}" + ) + end + + {:error, reason} -> + Logger.warning( + "Failed to refresh status for order #{order.order_number}: #{inspect(reason)}" + ) + end + end end diff --git a/lib/simpleshop_theme/providers/printify.ex b/lib/simpleshop_theme/providers/printify.ex index 9e56962..0037761 100644 --- a/lib/simpleshop_theme/providers/printify.ex +++ b/lib/simpleshop_theme/providers/printify.ex @@ -146,21 +146,22 @@ defmodule SimpleshopTheme.Providers.Printify do true -> with api_key when is_binary(api_key) <- ProviderConnection.get_api_key(conn), :ok <- set_api_key(api_key) do - results = - Enum.map(@webhook_events, fn event -> - case Client.create_webhook(shop_id, webhook_url, event, secret) do - {:ok, response} -> {:ok, event, response} - {:error, reason} -> {:error, event, reason} - end - end) - - {:ok, results} + {:ok, create_all_webhooks(shop_id, webhook_url, secret)} else nil -> {:error, :no_api_key} end end end + defp create_all_webhooks(shop_id, webhook_url, secret) do + Enum.map(@webhook_events, fn event -> + case Client.create_webhook(shop_id, webhook_url, event, secret) do + {:ok, response} -> {:ok, event, response} + {:error, reason} -> {:error, event, reason} + end + end) + end + @doc """ Lists registered webhooks for the shop. """ diff --git a/lib/simpleshop_theme/theme/fonts.ex b/lib/simpleshop_theme/theme/fonts.ex index 732aaeb..4824c16 100644 --- a/lib/simpleshop_theme/theme/fonts.ex +++ b/lib/simpleshop_theme/theme/fonts.ex @@ -128,9 +128,7 @@ defmodule SimpleshopTheme.Theme.Fonts do [heading_key, body_key] end - font_keys - |> Enum.map(&generate_font_face_for_font(&1, path_resolver)) - |> Enum.join("\n") + Enum.map_join(font_keys, "\n", &generate_font_face_for_font(&1, path_resolver)) end @doc """ @@ -143,8 +141,7 @@ defmodule SimpleshopTheme.Theme.Fonts do def generate_all_font_faces(path_resolver \\ &default_path_resolver/1) do @fonts |> Map.keys() - |> Enum.map(&generate_font_face_for_font(&1, path_resolver)) - |> Enum.join("\n") + |> Enum.map_join("\n", &generate_font_face_for_font(&1, path_resolver)) end @doc """ @@ -213,8 +210,7 @@ defmodule SimpleshopTheme.Theme.Fonts do defp generate_font_face_for_font(key, path_resolver) do case get_font(key) do %{family: family, file_prefix: prefix, weights: weights} -> - weights - |> Enum.map(fn weight -> + Enum.map_join(weights, "", fn weight -> weight_suffix = if weight == 400, do: "regular", else: to_string(weight) font_path = path_resolver.("/fonts/#{prefix}-#{weight_suffix}.woff2") @@ -228,7 +224,6 @@ defmodule SimpleshopTheme.Theme.Fonts do } """ end) - |> Enum.join("") nil -> "" diff --git a/lib/simpleshop_theme/theme/preview_data.ex b/lib/simpleshop_theme/theme/preview_data.ex index f73f8ee..8a2074e 100644 --- a/lib/simpleshop_theme/theme/preview_data.ex +++ b/lib/simpleshop_theme/theme/preview_data.ex @@ -393,8 +393,7 @@ defmodule SimpleshopTheme.Theme.PreviewData do # Get available variants for pricing available_variants = product.variants - |> Enum.filter(& &1.is_enabled) - |> Enum.filter(& &1.is_available) + |> Enum.filter(&(&1.is_enabled and &1.is_available)) # Get the cheapest available variant for display price cheapest_variant = diff --git a/lib/simpleshop_theme_web/components/shop_components/content.ex b/lib/simpleshop_theme_web/components/shop_components/content.ex index 7b94337..520c28a 100644 --- a/lib/simpleshop_theme_web/components/shop_components/content.ex +++ b/lib/simpleshop_theme_web/components/shop_components/content.ex @@ -1199,7 +1199,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Content do widths |> Enum.sort() - |> Enum.map(&"#{base}#{separator}#{&1}.#{format} #{&1}w") - |> Enum.join(", ") + |> Enum.map_join(", ", &"#{base}#{separator}#{&1}.#{format} #{&1}w") end end diff --git a/lib/simpleshop_theme_web/components/shop_components/product.ex b/lib/simpleshop_theme_web/components/shop_components/product.ex index 450925f..95331e3 100644 --- a/lib/simpleshop_theme_web/components/shop_components/product.ex +++ b/lib/simpleshop_theme_web/components/shop_components/product.ex @@ -417,8 +417,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do gap_class = gap || "" [base, cols, gap_class, extra_class] - |> Enum.reject(&is_nil/1) - |> Enum.reject(&(&1 == "")) + |> Enum.reject(&(is_nil(&1) or &1 == "")) |> Enum.join(" ") end diff --git a/lib/simpleshop_theme_web/controllers/checkout_controller.ex b/lib/simpleshop_theme_web/controllers/checkout_controller.ex index 4c4f86c..fd1796b 100644 --- a/lib/simpleshop_theme_web/controllers/checkout_controller.ex +++ b/lib/simpleshop_theme_web/controllers/checkout_controller.ex @@ -10,14 +10,12 @@ defmodule SimpleshopThemeWeb.CheckoutController do cart_items = Cart.get_from_session(get_session(conn)) hydrated = Cart.hydrate(cart_items) - cond do - hydrated == [] -> - conn - |> put_flash(:error, "Your basket is empty") - |> redirect(to: ~p"/cart") - - true -> - create_checkout(conn, hydrated) + if hydrated == [] do + conn + |> put_flash(:error, "Your basket is empty") + |> redirect(to: ~p"/cart") + else + create_checkout(conn, hydrated) end end diff --git a/mix.exs b/mix.exs index 9aab2fd..68c29db 100644 --- a/mix.exs +++ b/mix.exs @@ -11,7 +11,8 @@ defmodule SimpleshopTheme.MixProject do aliases: aliases(), deps: deps(), compilers: [:phoenix_live_view] ++ Mix.compilers(), - listeners: [Phoenix.CodeReloader] + listeners: [Phoenix.CodeReloader], + dialyzer: [ignore_warnings: ".dialyzer_ignore.exs"] ] end @@ -27,7 +28,7 @@ defmodule SimpleshopTheme.MixProject do def cli do [ - preferred_envs: [precommit: :test] + preferred_envs: [precommit: :test, ci: :test] ] end @@ -73,7 +74,9 @@ defmodule SimpleshopTheme.MixProject do {:oban, "~> 2.18"}, {:ex_money, "~> 5.0"}, {:ex_money_sql, "~> 1.0"}, - {:stripity_stripe, "~> 3.2"} + {:stripity_stripe, "~> 3.2"}, + {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, + {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} ] end @@ -102,7 +105,15 @@ defmodule SimpleshopTheme.MixProject do "esbuild simpleshop_theme --minify", "phx.digest" ], - precommit: ["compile --warning-as-errors", "deps.unlock --unused", "format", "test"] + precommit: ["compile --warning-as-errors", "deps.unlock --unused", "format", "test"], + ci: [ + "compile --warning-as-errors", + "deps.unlock --unused", + "format --check-formatted", + "credo", + "dialyzer", + "test" + ] ] end end diff --git a/mix.lock b/mix.lock index 8e8a2fd..03adc77 100644 --- a/mix.lock +++ b/mix.lock @@ -1,19 +1,23 @@ %{ "bandit": {:hex, :bandit, "1.10.2", "d15ea32eb853b5b42b965b24221eb045462b2ba9aff9a0bda71157c06338cbff", [: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", "27b2a61b647914b1726c2ced3601473be5f7aa6bb468564a688646a689b3ee45"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.3.2", "d50091e3c9492d73e17fc1e1619a9b09d6a5ef99160eb4d736926fd475a16ca3", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "471be5151874ae7931911057d1467d908955f93554f7a6cd1b7d804cac8cef53"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"}, "circular_buffer": {:hex, :circular_buffer, "1.0.0", "25c004da0cba7bd8bc1bdabded4f9a902d095e20600fd15faf1f2ffbaea18a07", [:mix], [], "hexpm", "c829ec31c13c7bafd1f546677263dff5bfb006e929f25635878ac3cfba8749e5"}, "cldr_utils": {:hex, :cldr_utils, "2.29.4", "11437b0bf9a0d57db4eccdf751c49f675a04fa4261c5dae1e23552a0347e25c9", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "e72a43e69a3f546979085cbdbeae7e9049998cd21cedfdd796cff9155998114e"}, "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"}, + "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"}, "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, + "dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"}, "digital_token": {:hex, :digital_token, "1.0.0", "454a4444061943f7349a51ef74b7fb1ebd19e6a94f43ef711f7dae88c09347df", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "8ed6f5a8c2fa7b07147b9963db506a1b4c7475d9afca6492136535b064c9e9e6"}, "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"}, "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"}, "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"}, "ecto_sqlite3": {:hex, :ecto_sqlite3, "0.22.0", "edab2d0f701b7dd05dcf7e2d97769c106aff62b5cfddc000d1dd6f46b9cbd8c3", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.13.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "5af9e031bffcc5da0b7bca90c271a7b1e7c04a93fecf7f6cd35bc1b1921a64bd"}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, + "erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"}, "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.46.0", "29b5bb638932ca4fc4339595145e327b797f59963a398c12a6aee1efe5a35b1b", [: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", "14157ac16694e99c1339ac25a4f10d3df0e0d15cc1a35073b37e195487c1b6cb"}, "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.17.0", "c38d76339dbee413f7dd1aba4cdf05758bd4c0bbfe9c3b1c8602f96082c2890a", [:mix], [{:ex_cldr, "~> 2.38", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "9af59bd29407dcca59fa39ded8c1649ae1cf6ec29fd0611576dcad0279bce0db"}, diff --git a/test/simpleshop_theme/theme/preview_data_test.exs b/test/simpleshop_theme/theme/preview_data_test.exs index 95982af..beaadc6 100644 --- a/test/simpleshop_theme/theme/preview_data_test.exs +++ b/test/simpleshop_theme/theme/preview_data_test.exs @@ -8,7 +8,7 @@ defmodule SimpleshopTheme.Theme.PreviewDataTest do products = PreviewData.products() assert is_list(products) - assert length(products) > 0 + assert products != [] end test "each product has required fields" do @@ -62,7 +62,7 @@ defmodule SimpleshopTheme.Theme.PreviewDataTest do products = PreviewData.products() on_sale_products = Enum.filter(products, & &1.on_sale) - assert length(on_sale_products) > 0 + assert on_sale_products != [] end test "on-sale products have compare_at_price" do @@ -80,7 +80,7 @@ defmodule SimpleshopTheme.Theme.PreviewDataTest do cart_items = PreviewData.cart_items() assert is_list(cart_items) - assert length(cart_items) > 0 + assert cart_items != [] end test "each cart item has required fields" do @@ -120,7 +120,7 @@ defmodule SimpleshopTheme.Theme.PreviewDataTest do testimonials = PreviewData.testimonials() assert is_list(testimonials) - assert length(testimonials) > 0 + assert testimonials != [] end test "each testimonial has required fields" do @@ -160,7 +160,7 @@ defmodule SimpleshopTheme.Theme.PreviewDataTest do categories = PreviewData.categories() assert is_list(categories) - assert length(categories) > 0 + assert categories != [] end test "each category has required fields" do