add CI pipeline with credo and dialyzer
mix ci alias: compile --warning-as-errors, format --check-formatted, credo, dialyzer, test. Credo configured with sensible defaults. Dialyzer ignore file for false positives (Stripe types, Mix tasks, ExUnit internals). Credo fixes: map_join, filter consolidation, nesting extraction, cond→if simplification. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
518da36c8f
commit
eaa4bbb3fa
219
.credo.exs
Normal file
219
.credo.exs
Normal file
@ -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 <name>`. 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`.
|
||||||
|
#
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
30
.dialyzer_ignore.exs
Normal file
30
.dialyzer_ignore.exs
Normal file
@ -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}
|
||||||
|
]
|
||||||
15
PROGRESS.md
15
PROGRESS.md
@ -20,7 +20,7 @@
|
|||||||
- Transactional emails (order confirmation, shipping notification)
|
- Transactional emails (order confirmation, shipping notification)
|
||||||
- Demo content polished and ready for production
|
- 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
|
## 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.
|
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.
|
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.
|
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.
|
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
|
- **Collection page** (16 tests, pre-existing) — category filtering, sorting, URL params
|
||||||
- **Content pages** (10 tests, pre-existing) — about, delivery, privacy, terms
|
- **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
|
### Page Editor
|
||||||
**Status:** Future (Tier 4)
|
**Status:** Future (Tier 4)
|
||||||
|
|
||||||
@ -232,6 +242,7 @@ See: [docs/plans/page-builder.md](docs/plans/page-builder.md) for design
|
|||||||
|
|
||||||
| Feature | Commit | Notes |
|
| 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 |
|
| Default content pages | 5a43cfc | Generic Content LiveView, delivery/privacy/terms pages, 10 tests |
|
||||||
| Transactional emails | — | Plain text order confirmation + shipping notification, 10 tests |
|
| Transactional emails | — | Plain text order confirmation + shipping notification, 10 tests |
|
||||||
| Printify order submission & fulfilment | — | Submit, track, webhooks, polling, admin UI, 33 tests |
|
| Printify order submission & fulfilment | — | Submit, track, webhooks, polling, admin UI, 33 tests |
|
||||||
|
|||||||
@ -129,7 +129,7 @@ defmodule Mix.Tasks.GenerateMockups do
|
|||||||
|
|
||||||
case MockupGenerator.search_blueprints(term) do
|
case MockupGenerator.search_blueprints(term) do
|
||||||
results when is_list(results) ->
|
results when is_list(results) ->
|
||||||
if length(results) == 0 do
|
if results == [] do
|
||||||
Mix.shell().info("No blueprints found matching '#{term}'")
|
Mix.shell().info("No blueprints found matching '#{term}'")
|
||||||
else
|
else
|
||||||
Mix.shell().info("\nMatching Blueprints:\n")
|
Mix.shell().info("\nMatching Blueprints:\n")
|
||||||
|
|||||||
@ -89,7 +89,7 @@ defmodule SimpleshopTheme.Clients.Printify do
|
|||||||
"""
|
"""
|
||||||
def get_shop_id do
|
def get_shop_id do
|
||||||
case get_shops() 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, hd(shops)["id"]}
|
||||||
|
|
||||||
{:ok, []} ->
|
{:ok, []} ->
|
||||||
|
|||||||
@ -214,7 +214,7 @@ defmodule SimpleshopTheme.Mockups.Generator do
|
|||||||
"""
|
"""
|
||||||
def find_print_provider(blueprint_id) do
|
def find_print_provider(blueprint_id) do
|
||||||
case Client.get_print_providers(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
|
# Just pick the first provider for simplicity
|
||||||
{:ok, hd(providers)}
|
{:ok, hd(providers)}
|
||||||
|
|
||||||
|
|||||||
@ -23,24 +23,27 @@ defmodule SimpleshopTheme.Orders.FulfilmentStatusWorker do
|
|||||||
Logger.info("Polling fulfilment status for #{length(orders)} order(s)")
|
Logger.info("Polling fulfilment status for #{length(orders)} order(s)")
|
||||||
|
|
||||||
Enum.each(orders, fn order ->
|
Enum.each(orders, fn order ->
|
||||||
case Orders.refresh_fulfilment_status(order) do
|
refresh_order(order)
|
||||||
{: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
|
|
||||||
|
|
||||||
Process.sleep(200)
|
Process.sleep(200)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
@ -146,21 +146,22 @@ defmodule SimpleshopTheme.Providers.Printify do
|
|||||||
true ->
|
true ->
|
||||||
with api_key when is_binary(api_key) <- ProviderConnection.get_api_key(conn),
|
with api_key when is_binary(api_key) <- ProviderConnection.get_api_key(conn),
|
||||||
:ok <- set_api_key(api_key) do
|
:ok <- set_api_key(api_key) do
|
||||||
results =
|
{:ok, create_all_webhooks(shop_id, webhook_url, secret)}
|
||||||
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}
|
|
||||||
else
|
else
|
||||||
nil -> {:error, :no_api_key}
|
nil -> {:error, :no_api_key}
|
||||||
end
|
end
|
||||||
end
|
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 """
|
@doc """
|
||||||
Lists registered webhooks for the shop.
|
Lists registered webhooks for the shop.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -128,9 +128,7 @@ defmodule SimpleshopTheme.Theme.Fonts do
|
|||||||
[heading_key, body_key]
|
[heading_key, body_key]
|
||||||
end
|
end
|
||||||
|
|
||||||
font_keys
|
Enum.map_join(font_keys, "\n", &generate_font_face_for_font(&1, path_resolver))
|
||||||
|> Enum.map(&generate_font_face_for_font(&1, path_resolver))
|
|
||||||
|> Enum.join("\n")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -143,8 +141,7 @@ defmodule SimpleshopTheme.Theme.Fonts do
|
|||||||
def generate_all_font_faces(path_resolver \\ &default_path_resolver/1) do
|
def generate_all_font_faces(path_resolver \\ &default_path_resolver/1) do
|
||||||
@fonts
|
@fonts
|
||||||
|> Map.keys()
|
|> Map.keys()
|
||||||
|> Enum.map(&generate_font_face_for_font(&1, path_resolver))
|
|> Enum.map_join("\n", &generate_font_face_for_font(&1, path_resolver))
|
||||||
|> Enum.join("\n")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -213,8 +210,7 @@ defmodule SimpleshopTheme.Theme.Fonts do
|
|||||||
defp generate_font_face_for_font(key, path_resolver) do
|
defp generate_font_face_for_font(key, path_resolver) do
|
||||||
case get_font(key) do
|
case get_font(key) do
|
||||||
%{family: family, file_prefix: prefix, weights: weights} ->
|
%{family: family, file_prefix: prefix, weights: weights} ->
|
||||||
weights
|
Enum.map_join(weights, "", fn weight ->
|
||||||
|> Enum.map(fn weight ->
|
|
||||||
weight_suffix = if weight == 400, do: "regular", else: to_string(weight)
|
weight_suffix = if weight == 400, do: "regular", else: to_string(weight)
|
||||||
font_path = path_resolver.("/fonts/#{prefix}-#{weight_suffix}.woff2")
|
font_path = path_resolver.("/fonts/#{prefix}-#{weight_suffix}.woff2")
|
||||||
|
|
||||||
@ -228,7 +224,6 @@ defmodule SimpleshopTheme.Theme.Fonts do
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
end)
|
end)
|
||||||
|> Enum.join("")
|
|
||||||
|
|
||||||
nil ->
|
nil ->
|
||||||
""
|
""
|
||||||
|
|||||||
@ -393,8 +393,7 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
# Get available variants for pricing
|
# Get available variants for pricing
|
||||||
available_variants =
|
available_variants =
|
||||||
product.variants
|
product.variants
|
||||||
|> Enum.filter(& &1.is_enabled)
|
|> Enum.filter(&(&1.is_enabled and &1.is_available))
|
||||||
|> Enum.filter(& &1.is_available)
|
|
||||||
|
|
||||||
# Get the cheapest available variant for display price
|
# Get the cheapest available variant for display price
|
||||||
cheapest_variant =
|
cheapest_variant =
|
||||||
|
|||||||
@ -1199,7 +1199,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Content do
|
|||||||
|
|
||||||
widths
|
widths
|
||||||
|> Enum.sort()
|
|> Enum.sort()
|
||||||
|> Enum.map(&"#{base}#{separator}#{&1}.#{format} #{&1}w")
|
|> Enum.map_join(", ", &"#{base}#{separator}#{&1}.#{format} #{&1}w")
|
||||||
|> Enum.join(", ")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -417,8 +417,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
gap_class = gap || ""
|
gap_class = gap || ""
|
||||||
|
|
||||||
[base, cols, gap_class, extra_class]
|
[base, cols, gap_class, extra_class]
|
||||||
|> Enum.reject(&is_nil/1)
|
|> Enum.reject(&(is_nil(&1) or &1 == ""))
|
||||||
|> Enum.reject(&(&1 == ""))
|
|
||||||
|> Enum.join(" ")
|
|> Enum.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -10,14 +10,12 @@ defmodule SimpleshopThemeWeb.CheckoutController do
|
|||||||
cart_items = Cart.get_from_session(get_session(conn))
|
cart_items = Cart.get_from_session(get_session(conn))
|
||||||
hydrated = Cart.hydrate(cart_items)
|
hydrated = Cart.hydrate(cart_items)
|
||||||
|
|
||||||
cond do
|
if hydrated == [] do
|
||||||
hydrated == [] ->
|
conn
|
||||||
conn
|
|> put_flash(:error, "Your basket is empty")
|
||||||
|> put_flash(:error, "Your basket is empty")
|
|> redirect(to: ~p"/cart")
|
||||||
|> redirect(to: ~p"/cart")
|
else
|
||||||
|
create_checkout(conn, hydrated)
|
||||||
true ->
|
|
||||||
create_checkout(conn, hydrated)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
19
mix.exs
19
mix.exs
@ -11,7 +11,8 @@ defmodule SimpleshopTheme.MixProject do
|
|||||||
aliases: aliases(),
|
aliases: aliases(),
|
||||||
deps: deps(),
|
deps: deps(),
|
||||||
compilers: [:phoenix_live_view] ++ Mix.compilers(),
|
compilers: [:phoenix_live_view] ++ Mix.compilers(),
|
||||||
listeners: [Phoenix.CodeReloader]
|
listeners: [Phoenix.CodeReloader],
|
||||||
|
dialyzer: [ignore_warnings: ".dialyzer_ignore.exs"]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ defmodule SimpleshopTheme.MixProject do
|
|||||||
|
|
||||||
def cli do
|
def cli do
|
||||||
[
|
[
|
||||||
preferred_envs: [precommit: :test]
|
preferred_envs: [precommit: :test, ci: :test]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -73,7 +74,9 @@ defmodule SimpleshopTheme.MixProject do
|
|||||||
{:oban, "~> 2.18"},
|
{:oban, "~> 2.18"},
|
||||||
{:ex_money, "~> 5.0"},
|
{:ex_money, "~> 5.0"},
|
||||||
{:ex_money_sql, "~> 1.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
|
end
|
||||||
|
|
||||||
@ -102,7 +105,15 @@ defmodule SimpleshopTheme.MixProject do
|
|||||||
"esbuild simpleshop_theme --minify",
|
"esbuild simpleshop_theme --minify",
|
||||||
"phx.digest"
|
"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
|
||||||
end
|
end
|
||||||
|
|||||||
4
mix.lock
4
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"},
|
"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"},
|
"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"},
|
"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"},
|
"certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"},
|
||||||
"circular_buffer": {:hex, :circular_buffer, "1.0.0", "25c004da0cba7bd8bc1bdabded4f9a902d095e20600fd15faf1f2ffbaea18a07", [:mix], [], "hexpm", "c829ec31c13c7bafd1f546677263dff5bfb006e929f25635878ac3cfba8749e5"},
|
"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"},
|
"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"},
|
"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"},
|
"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"},
|
"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"},
|
"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"},
|
"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": {: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_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"},
|
"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"},
|
"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"},
|
"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": {: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"},
|
"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"},
|
||||||
|
|||||||
@ -8,7 +8,7 @@ defmodule SimpleshopTheme.Theme.PreviewDataTest do
|
|||||||
products = PreviewData.products()
|
products = PreviewData.products()
|
||||||
|
|
||||||
assert is_list(products)
|
assert is_list(products)
|
||||||
assert length(products) > 0
|
assert products != []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "each product has required fields" do
|
test "each product has required fields" do
|
||||||
@ -62,7 +62,7 @@ defmodule SimpleshopTheme.Theme.PreviewDataTest do
|
|||||||
products = PreviewData.products()
|
products = PreviewData.products()
|
||||||
on_sale_products = Enum.filter(products, & &1.on_sale)
|
on_sale_products = Enum.filter(products, & &1.on_sale)
|
||||||
|
|
||||||
assert length(on_sale_products) > 0
|
assert on_sale_products != []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "on-sale products have compare_at_price" do
|
test "on-sale products have compare_at_price" do
|
||||||
@ -80,7 +80,7 @@ defmodule SimpleshopTheme.Theme.PreviewDataTest do
|
|||||||
cart_items = PreviewData.cart_items()
|
cart_items = PreviewData.cart_items()
|
||||||
|
|
||||||
assert is_list(cart_items)
|
assert is_list(cart_items)
|
||||||
assert length(cart_items) > 0
|
assert cart_items != []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "each cart item has required fields" do
|
test "each cart item has required fields" do
|
||||||
@ -120,7 +120,7 @@ defmodule SimpleshopTheme.Theme.PreviewDataTest do
|
|||||||
testimonials = PreviewData.testimonials()
|
testimonials = PreviewData.testimonials()
|
||||||
|
|
||||||
assert is_list(testimonials)
|
assert is_list(testimonials)
|
||||||
assert length(testimonials) > 0
|
assert testimonials != []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "each testimonial has required fields" do
|
test "each testimonial has required fields" do
|
||||||
@ -160,7 +160,7 @@ defmodule SimpleshopTheme.Theme.PreviewDataTest do
|
|||||||
categories = PreviewData.categories()
|
categories = PreviewData.categories()
|
||||||
|
|
||||||
assert is_list(categories)
|
assert is_list(categories)
|
||||||
assert length(categories) > 0
|
assert categories != []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "each category has required fields" do
|
test "each category has required fields" do
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user