From ba3fa42a9a598bf5527d321b60d0647bc81d0f72 Mon Sep 17 00:00:00 2001 From: Mike Zornek Date: Sun, 8 Sep 2024 15:20:43 -0400 Subject: [PATCH] ci: adjust credo lint rules (#84) --- .credo.exs | 85 +++++++------------ lib/flick/application.ex | 5 +- lib/flick/ranked_voting.ex | 21 +++-- lib/flick/ranked_voting/ballot.ex | 1 + lib/flick/ranked_voting/vote.ex | 8 +- lib/flick/release.ex | 2 + lib/flick_web.ex | 9 ++ lib/flick_web/components/core_components.ex | 3 +- lib/flick_web/controllers/error_html.ex | 1 + lib/flick_web/controllers/error_json.ex | 1 + lib/flick_web/endpoint.ex | 1 + lib/flick_web/router.ex | 1 + lib/flick_web/telemetry.ex | 4 +- test/flick/ranked_voting_test.exs | 4 +- test/flick_web/live/index_live_test.exs | 2 +- .../live/vote/vote_capture_live_test.exs | 4 +- test/support/data_case.ex | 2 + test/support/fixtures/ballot_fixture.ex | 5 ++ 18 files changed, 84 insertions(+), 75 deletions(-) diff --git a/.credo.exs b/.credo.exs index 1129a09..c912600 100644 --- a/.credo.exs +++ b/.credo.exs @@ -46,7 +46,7 @@ # If you want to enforce a style guide and need a more traditional linting # experience, you can change `strict` to `true` below: # - strict: false, + strict: true, # # To modify the timeout for parsing files, change this value: # @@ -75,35 +75,32 @@ {Credo.Check.Consistency.SpaceAroundOperators, []}, {Credo.Check.Consistency.SpaceInParentheses, []}, {Credo.Check.Consistency.TabsOrSpaces, []}, + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, # ## Design Checks # - # You can customize the priority of any check - # Priority values are: `low, normal, high, higher` - # {Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 2]}, - - # FIXMEs are allowed. - # {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.TagFIXME, []} {Credo.Check.Design.TagTODO, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + {Credo.Check.Design.DuplicatedCode, []}, # ## Readability Checks # {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.BlockPipe, []}, {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.ImplTrue, []}, {Credo.Check.Readability.LargeNumbers, []}, {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, {Credo.Check.Readability.ModuleAttributeNames, []}, {Credo.Check.Readability.ModuleDoc, []}, {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.OneArityFunctionInPipe, []}, {Credo.Check.Readability.ParenthesesInCondition, []}, {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, @@ -111,32 +108,50 @@ {Credo.Check.Readability.PreferImplicitTry, []}, {Credo.Check.Readability.RedundantBlankLines, []}, {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.StrictModuleLayout, []}, {Credo.Check.Readability.StringSigils, []}, {Credo.Check.Readability.TrailingBlankLine, []}, {Credo.Check.Readability.TrailingWhiteSpace, []}, {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, {Credo.Check.Readability.WithSingleClause, []}, # ## Refactoring Opportunities # + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, {Credo.Check.Refactor.Apply, []}, {Credo.Check.Refactor.CondStatements, []}, {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, {Credo.Check.Refactor.FilterCount, []}, {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.FilterReject, []}, {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.IoPuts, []}, {Credo.Check.Refactor.LongQuoteBlocks, []}, {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.MapMap, []}, {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.ModuleDependencies, []}, {Credo.Check.Refactor.NegatedConditionsInUnless, []}, {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.NegatedIsNil, []}, {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.PassAsyncInTestCases, []}, + {Credo.Check.Refactor.PipeChainStart, []}, {Credo.Check.Refactor.RedundantWithClauseResult, []}, + {Credo.Check.Refactor.RejectFilter, []}, {Credo.Check.Refactor.RejectReject, []}, {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.UtcNowTruncate, []}, + {Credo.Check.Refactor.VariableRebinding, []}, {Credo.Check.Refactor.WithClauses, []}, # @@ -148,12 +163,16 @@ {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, {Credo.Check.Warning.IExPry, []}, {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, + {Credo.Check.Warning.MixEnv, []}, {Credo.Check.Warning.OperationOnSameValues, []}, {Credo.Check.Warning.OperationWithConstantResult, []}, {Credo.Check.Warning.RaiseInsideRescue, []}, {Credo.Check.Warning.SpecWithStruct, []}, {Credo.Check.Warning.UnsafeExec, []}, + {Credo.Check.Warning.UnsafeToAtom, []}, {Credo.Check.Warning.UnusedEnumOperation, []}, {Credo.Check.Warning.UnusedFileOperation, []}, {Credo.Check.Warning.UnusedKeywordOperation, []}, @@ -165,54 +184,14 @@ {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.Refactor.MapInto, []}, + {Credo.Check.Warning.LazyLogging, []} - # # Custom checks can be created using `mix credo.gen.check`. - # ] } } diff --git a/lib/flick/application.ex b/lib/flick/application.ex index 9467492..e8f205c 100644 --- a/lib/flick/application.ex +++ b/lib/flick/application.ex @@ -1,3 +1,4 @@ +# credo:disable-for-this-file Credo.Check.Refactor.ModuleDependencies defmodule Flick.Application do # See https://hexdocs.pm/elixir/Application.html # for more information on OTP Applications @@ -5,7 +6,7 @@ defmodule Flick.Application do use Application - @impl true + @impl Application def start(_type, _args) do Appsignal.Phoenix.LiveView.attach() @@ -30,7 +31,7 @@ defmodule Flick.Application do # Tell Phoenix to update the endpoint configuration # whenever the application is updated. - @impl true + @impl Application def config_change(changed, _new, removed) do FlickWeb.Endpoint.config_change(changed, removed) :ok diff --git a/lib/flick/ranked_voting.ex b/lib/flick/ranked_voting.ex index b603fd6..a047973 100644 --- a/lib/flick/ranked_voting.ex +++ b/lib/flick/ranked_voting.ex @@ -28,10 +28,6 @@ defmodule Flick.RankedVoting do {:ok, Ballot.t()} | {:error, Ecto.Changeset.t(Ballot.t())} | {:error, :can_not_update_published_ballot} - def update_ballot(%Ballot{published_at: published_at}, _attrs) - when not is_nil(published_at) do - {:error, :can_not_update_published_ballot} - end def update_ballot(%Ballot{published_at: nil} = ballot, attrs) do ballot @@ -39,6 +35,10 @@ defmodule Flick.RankedVoting do |> Repo.update() end + def update_ballot(_ballot, _attrs) do + {:error, :can_not_update_published_ballot} + end + @doc """ Returns a boolean value indicating whether the given `Flick.RankedVoting.Ballot` entity can be updated. @@ -59,17 +59,16 @@ defmodule Flick.RankedVoting do | {:error, :ballot_already_published} def publish_ballot(ballot, published_at \\ DateTime.utc_now()) - def publish_ballot(%Ballot{published_at: published_at}, _published_at) - when not is_nil(published_at) do - {:error, :ballot_already_published} - end - - def publish_ballot(%Ballot{} = ballot, published_at) do + def publish_ballot(%Ballot{published_at: nil} = ballot, published_at) do ballot |> change_ballot(%{published_at: published_at}) |> Repo.update() end + def publish_ballot(_ballot, _published_at) do + {:error, :ballot_already_published} + end + @doc """ Returns a list of all `Flick.RankedVoting.Ballot` entities. """ @@ -172,7 +171,7 @@ defmodule Flick.RankedVoting do * `:action` - An optional atom applied to the changeset, useful for forms that look to a changeset's action to influence form behavior. """ - @spec change_vote(Vote.t() | Vote.struct_t(), map()) :: Ecto.Changeset.t(Vote.t()) + @spec change_vote(Vote.t() | Vote.struct_t(), map(), keyword()) :: Ecto.Changeset.t(Vote.t()) def change_vote(%Vote{} = vote, attrs, opts \\ []) do opts = Keyword.validate!(opts, action: nil) diff --git a/lib/flick/ranked_voting/ballot.ex b/lib/flick/ranked_voting/ballot.ex index afd741e..93410ab 100644 --- a/lib/flick/ranked_voting/ballot.ex +++ b/lib/flick/ranked_voting/ballot.ex @@ -70,6 +70,7 @@ defmodule Flick.RankedVoting.Ballot do |> Enum.map(&String.trim/1) end + @spec validate_published_at(Ecto.Changeset.t()) :: Ecto.Changeset.t() def validate_published_at(%Ecto.Changeset{data: %__MODULE__{id: nil}} = changeset) do # We do not want "new" ballots to be created as already published. validate_change(changeset, :published_at, fn :published_at, _updated_value -> diff --git a/lib/flick/ranked_voting/vote.ex b/lib/flick/ranked_voting/vote.ex index 1c7a2eb..5601ef3 100644 --- a/lib/flick/ranked_voting/vote.ex +++ b/lib/flick/ranked_voting/vote.ex @@ -105,15 +105,17 @@ defmodule Flick.RankedVoting.Vote do ballot = Flick.RankedVoting.get_ballot!(get_field(changeset, :ballot_id)) possible_answers = Ballot.possible_answers_as_list(ballot.possible_answers) ++ ["", nil] - Enum.reduce(new_ranked_answers, [], fn changeset, acc -> + new_ranked_answers + |> Enum.reduce([], fn changeset, acc -> ranked_answer_value = get_field(changeset, :value) if Enum.member?(possible_answers, ranked_answer_value) do acc else - acc ++ [ranked_answer_value] + [ranked_answer_value] ++ acc end end) + |> Enum.reverse() end defp validate_ranked_answers_are_not_duplicated(%Changeset{valid?: false} = changeset) do @@ -163,6 +165,8 @@ defmodule Flick.RankedVoting.Vote do changeset end + # FIXME: Refactor to reduce complexity. + # credo:disable-for-next-line Credo.Check.Refactor.ABCSize defp validate_first_ranked_answers_has_valid_value(%Changeset{changes: changes} = changeset) when is_map_key(changes, :ranked_answers) do ranked_answer_changesets = diff --git a/lib/flick/release.ex b/lib/flick/release.ex index f871780..a796ec8 100644 --- a/lib/flick/release.ex +++ b/lib/flick/release.ex @@ -11,6 +11,7 @@ defmodule Flick.Release do """ @app :flick + @spec migrate() :: [any()] def migrate do ensure_started() @@ -19,6 +20,7 @@ defmodule Flick.Release do end end + @spec rollback(Ecto.Repo.t(), integer()) :: {:ok, any(), list()} def rollback(repo, version) do ensure_started() {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) diff --git a/lib/flick_web.ex b/lib/flick_web.ex index 0d4135a..199be74 100644 --- a/lib/flick_web.ex +++ b/lib/flick_web.ex @@ -1,3 +1,4 @@ +# credo:disable-for-this-file Credo.Check.Refactor.ModuleDependencies defmodule FlickWeb do @moduledoc """ The entrypoint for defining your web interface, such @@ -17,8 +18,10 @@ defmodule FlickWeb do those modules here. """ + @spec static_paths() :: [String.t()] def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) + @spec router() :: Macro.t() def router do quote do use Phoenix.Router, helpers: false @@ -30,12 +33,14 @@ defmodule FlickWeb do end end + @spec channel() :: Macro.t() def channel do quote do use Phoenix.Channel end end + @spec controller() :: Macro.t() def controller do quote do use Phoenix.Controller, @@ -49,6 +54,7 @@ defmodule FlickWeb do end end + @spec live_view() :: Macro.t() def live_view do quote do use Phoenix.LiveView, @@ -60,6 +66,7 @@ defmodule FlickWeb do end end + @spec live_component() :: Macro.t() def live_component do quote do use Phoenix.LiveComponent @@ -68,6 +75,7 @@ defmodule FlickWeb do end end + @spec html() :: Macro.t() def html do quote do use Phoenix.Component @@ -97,6 +105,7 @@ defmodule FlickWeb do end end + @spec verified_routes() :: Macro.t() def verified_routes do quote do use Phoenix.VerifiedRoutes, diff --git a/lib/flick_web/components/core_components.ex b/lib/flick_web/components/core_components.ex index 8615f24..5bbbd78 100644 --- a/lib/flick_web/components/core_components.ex +++ b/lib/flick_web/components/core_components.ex @@ -1,3 +1,4 @@ +# credo:disable-for-this-file Credo.Check.Readability.Specs defmodule FlickWeb.CoreComponents do @moduledoc """ Provides core UI components. @@ -16,8 +17,8 @@ defmodule FlickWeb.CoreComponents do """ use Phoenix.Component - alias Phoenix.LiveView.JS import FlickWeb.Gettext + alias Phoenix.LiveView.JS @doc """ Renders a modal. diff --git a/lib/flick_web/controllers/error_html.ex b/lib/flick_web/controllers/error_html.ex index ade9c0f..69a932d 100644 --- a/lib/flick_web/controllers/error_html.ex +++ b/lib/flick_web/controllers/error_html.ex @@ -13,6 +13,7 @@ defmodule FlickWeb.ErrorHTML do # The default is to render a plain text page based on # the template name. For example, "404.html" becomes # "Not Found". + @spec render(String.t(), map()) :: String.t() def render(template, _assigns) do Phoenix.Controller.status_message_from_template(template) end diff --git a/lib/flick_web/controllers/error_json.ex b/lib/flick_web/controllers/error_json.ex index 437d187..41aaf39 100644 --- a/lib/flick_web/controllers/error_json.ex +++ b/lib/flick_web/controllers/error_json.ex @@ -9,6 +9,7 @@ defmodule FlickWeb.ErrorJSON do # By default, Phoenix returns the status message from # the template name. For example, "404.json" becomes # "Not Found". + @spec render(String.t(), map()) :: map() def render(template, _assigns) do %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} end diff --git a/lib/flick_web/endpoint.ex b/lib/flick_web/endpoint.ex index b265f58..8d08df2 100644 --- a/lib/flick_web/endpoint.ex +++ b/lib/flick_web/endpoint.ex @@ -1,3 +1,4 @@ +# credo:disable-for-this-file Credo.Check.Refactor.ModuleDependencies defmodule FlickWeb.Endpoint do use Phoenix.Endpoint, otp_app: :flick diff --git a/lib/flick_web/router.ex b/lib/flick_web/router.ex index f667172..22cfca0 100644 --- a/lib/flick_web/router.ex +++ b/lib/flick_web/router.ex @@ -1,3 +1,4 @@ +# credo:disable-for-this-file Credo.Check.Refactor.ModuleDependencies defmodule FlickWeb.Router do use FlickWeb, :router diff --git a/lib/flick_web/telemetry.ex b/lib/flick_web/telemetry.ex index 69d2cf2..fa97fd8 100644 --- a/lib/flick_web/telemetry.ex +++ b/lib/flick_web/telemetry.ex @@ -2,11 +2,12 @@ defmodule FlickWeb.Telemetry do use Supervisor import Telemetry.Metrics + @spec start_link(term()) :: Supervisor.on_start() def start_link(arg) do Supervisor.start_link(__MODULE__, arg, name: __MODULE__) end - @impl true + @impl Supervisor def init(_arg) do children = [ # Telemetry poller will execute the given period measurements @@ -19,6 +20,7 @@ defmodule FlickWeb.Telemetry do Supervisor.init(children, strategy: :one_for_one) end + @spec metrics() :: [Telemetry.Metrics.Summary.t()] def metrics do [ # Phoenix Metrics diff --git a/test/flick/ranked_voting_test.exs b/test/flick/ranked_voting_test.exs index 48771f4..2775d7f 100644 --- a/test/flick/ranked_voting_test.exs +++ b/test/flick/ranked_voting_test.exs @@ -265,13 +265,13 @@ defmodule Flick.RankedVotingTest do describe "create_vote/2" do setup do - ballot = + prepublished_ballot = ballot_fixture( question_title: "What's for dinner?", possible_answers: "Pizza, Tacos, Sushi, Burgers" ) - {:ok, ballot} = RankedVoting.publish_ballot(ballot) + {:ok, ballot} = RankedVoting.publish_ballot(prepublished_ballot) {:ok, published_ballot: ballot} end diff --git a/test/flick_web/live/index_live_test.exs b/test/flick_web/live/index_live_test.exs index d40305e..5b74ebf 100644 --- a/test/flick_web/live/index_live_test.exs +++ b/test/flick_web/live/index_live_test.exs @@ -17,7 +17,7 @@ defmodule FlickWeb.IndexLiveTest do expected_welcome_copy = "Flick is an application that allows you to create ballots that capture ranked votes." - assert element(view, "#welcome-copy") |> render() =~ expected_welcome_copy + assert view |> element("#welcome-copy") |> render() =~ expected_welcome_copy end test "contains link to create a ballot", ~M{conn} do diff --git a/test/flick_web/live/vote/vote_capture_live_test.exs b/test/flick_web/live/vote/vote_capture_live_test.exs index 46cc761..137b713 100644 --- a/test/flick_web/live/vote/vote_capture_live_test.exs +++ b/test/flick_web/live/vote/vote_capture_live_test.exs @@ -10,14 +10,14 @@ defmodule FlickWeb.Vote.VoteCaptureLiveTest do alias Flick.RankedVoting.Ballot setup ~M{conn} do - ballot = + prepublished_ballot = ballot_fixture(%{ question_title: "What movie should we go see?", possible_answers: "Hackers, Sneakers, WarGames, The Matrix, Tron", url_slug: "movie-night" }) - {:ok, ballot} = Flick.RankedVoting.publish_ballot(ballot) + {:ok, ballot} = Flick.RankedVoting.publish_ballot(prepublished_ballot) {:ok, view, _html} = live(conn, ~p"/ballot/movie-night") ~M{conn, view, ballot} diff --git a/test/support/data_case.ex b/test/support/data_case.ex index b65d762..408fd47 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -38,6 +38,7 @@ defmodule Flick.DataCase do @doc """ Sets up the sandbox based on the test tags. """ + @spec setup_sandbox(map()) :: :ok def setup_sandbox(tags) do pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Flick.Repo, shared: not tags[:async]) on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) @@ -51,6 +52,7 @@ defmodule Flick.DataCase do assert %{password: ["password is too short"]} = errors_on(changeset) """ + @spec errors_on(Ecto.Changeset.t()) :: map() def errors_on(changeset) do Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> Regex.replace(~r"%{(\w+)}", message, fn _, key -> diff --git a/test/support/fixtures/ballot_fixture.ex b/test/support/fixtures/ballot_fixture.ex index 97743ae..ec2338a 100644 --- a/test/support/fixtures/ballot_fixture.ex +++ b/test/support/fixtures/ballot_fixture.ex @@ -4,10 +4,13 @@ defmodule Support.Fixtures.BallotFixture do `Flick.RankedVoting.Ballot` entities for testing. """ + alias Flick.RankedVoting.Ballot + @doc """ Returns a map of valid attributes for a `Flick.RankedVoting.Ballot` entity, allowing for the passed in attributes to override defaults. """ + @spec valid_ballot_attributes(map()) :: map() def valid_ballot_attributes(attrs \\ %{}) do Enum.into(attrs, %{ question_title: "What day should have dinner?", @@ -23,6 +26,7 @@ defmodule Support.Fixtures.BallotFixture do When not provided, all required attributes will be generated. """ + @spec ballot_fixture(map()) :: {:ok, Ballot.t()} def ballot_fixture(attrs \\ %{}) do attrs = valid_ballot_attributes(attrs) {:ok, ballot} = Flick.RankedVoting.create_ballot(attrs) @@ -35,6 +39,7 @@ defmodule Support.Fixtures.BallotFixture do When not provided, all required attributes will be generated. """ + @spec published_ballot_fixture(map()) :: {:ok, Ballot.t()} def published_ballot_fixture(attrs \\ %{}) do attrs = valid_ballot_attributes(attrs) {:ok, ballot} = Flick.RankedVoting.create_ballot(attrs)