diff --git a/phoenix/contraq/lib/contraq_web/cells/gig/template.html.haml b/phoenix/contraq/lib/contraq_web/cells/gig/template.html.haml index 7b51256..7ef6e87 100644 --- a/phoenix/contraq/lib/contraq_web/cells/gig/template.html.haml +++ b/phoenix/contraq/lib/contraq_web/cells/gig/template.html.haml @@ -1,5 +1,5 @@ - container! @gig do %h1.name= link_to @gig - - for field <- fields do - - field_class = dasherize(field) - %div{class: field_class}= apply(__MODULE__, field, [@gig]) + - for field_name <- fields do + - field_class = dasherize(field_name) + %div{class: field_class}= field @gig, field_name diff --git a/phoenix/contraq/lib/contraq_web/cells/gig/view.ex b/phoenix/contraq/lib/contraq_web/cells/gig/view.ex index 07726c1..00a2612 100644 --- a/phoenix/contraq/lib/contraq_web/cells/gig/view.ex +++ b/phoenix/contraq/lib/contraq_web/cells/gig/view.ex @@ -12,22 +12,16 @@ defmodule ContraqWeb.GigCell do end end - # TODO: Some of these seem like they belong elsewhere, but where? On Gig? On Gigs (the context)? On a helper module? GigView, maybe? - @spec location(%Gig{}) :: String.t - def location(%Gig{city: city, state: state}) do - [city, state] |> Enum.reject(&is_nil/1) |> Enum.join(", ") - end - - @spec time_range(%Gig{}) :: String.t - def time_range(%Gig{start_time: start_time, end_time: end_time}) do - (for time <- [start_time, end_time], do: Timex.format!(time, Application.get_env(:contraq, ContraqWeb)[:datetime_format])) |> Enum.join("–") - end - @spec dasherize(atom | String.t) :: String.t defp dasherize(string) do string |> to_string |> String.replace("_", "-") end + @spec field(%Gig{}, atom) :: String.t + defp field(%Gig{} = gig, name) do + apply(ContraqWeb.GigView, name, [gig]) + end + @spec fields :: [atom] defp fields do [:time_range, :location] diff --git a/phoenix/contraq/lib/contraq_web/views/gig_view.ex b/phoenix/contraq/lib/contraq_web/views/gig_view.ex index 4666d5d..130fabc 100644 --- a/phoenix/contraq/lib/contraq_web/views/gig_view.ex +++ b/phoenix/contraq/lib/contraq_web/views/gig_view.ex @@ -1,4 +1,17 @@ defmodule ContraqWeb.GigView do @dialyzer :no_return use ContraqWeb, :view + alias Contraq.Gigs.Gig + + @spec location(%Gig{}) :: String.t + def location(%Gig{city: city, state: state}) do + [city, state] |> Enum.reject(&is_nil/1) |> Enum.join(", ") + end + + @spec time_range(%Gig{}) :: String.t + def time_range(%Gig{start_time: start_time, end_time: end_time}) do + for time <- [start_time, end_time] do + Timex.format!(time, Application.get_env(:contraq, ContraqWeb)[:datetime_format]) + end |> Enum.join("–") + end end diff --git a/phoenix/contraq/spec/contraq/gigs/gig_spec.exs b/phoenix/contraq/spec/contraq/gigs/gig_spec.exs index 3f772b5..04e030e 100644 --- a/phoenix/contraq/spec/contraq/gigs/gig_spec.exs +++ b/phoenix/contraq/spec/contraq/gigs/gig_spec.exs @@ -7,7 +7,7 @@ defmodule Gigs.GigSpec do import Map, only: [delete: 2] context "validations" do - let :valid_attributes, do: Map.from_struct(Factory.insert! :gig) + let :valid_attributes, do: Map.from_struct(Factory.insert! :gig, required_only: true) let :valid_changeset, do: Gig.changeset(%Gig{}, valid_attributes) it "is valid with valid attributes" do diff --git a/phoenix/contraq/spec/contraq_web/views/gig_view_spec.exs b/phoenix/contraq/spec/contraq_web/views/gig_view_spec.exs new file mode 100644 index 0000000..4878b0d --- /dev/null +++ b/phoenix/contraq/spec/contraq_web/views/gig_view_spec.exs @@ -0,0 +1,52 @@ +defmodule ContraqWeb.GigViewSpec do + use ESpec.Phoenix, async: true, view: ContraqWeb.GigView + alias Contraq.Factory + + let :attributes, do: %{} + let :gig, do: Factory.build(:gig, attributes) + + describe ".location" do + subject do: described_module.location gig + + context "city and state present" do + it "returns the city and state, joined with a comma" do + expect(subject).to eq "#{gig.city}, #{gig.state}" + end + end + + context "city not present" do + let :attributes, do: %{city: nil} + + it "returns the state" do + expect(subject).to eq gig.state + end + end + + context "state not present" do + let :attributes, do: %{state: nil} + + it "returns the city" do + expect(subject).to eq gig.city + end + end + + context "no location" do + let :attributes, do: %{city: nil, state: nil} + + it "returns an empty string" do + expect(subject).to eq "" + end + end + end + + describe ".time_range" do + subject do: described_module.time_range gig + + it "returns the starting and ending times for the gig, formatted and joined with an en-dash" do + formatted_times = for time <- [gig.start_time, gig.end_time] do + Timex.format! time, Application.get_env(:contraq, ContraqWeb)[:datetime_format] + end + expect(subject).to eq Enum.join(formatted_times, "–") + end + end +end diff --git a/phoenix/contraq/test/support/factory.ex b/phoenix/contraq/test/support/factory.ex index c60ca6d..4788233 100644 --- a/phoenix/contraq/test/support/factory.ex +++ b/phoenix/contraq/test/support/factory.ex @@ -1,32 +1,48 @@ defmodule Contraq.Factory do # Cribbed from http://blog.plataformatec.com.br/wp-content/uploads/2016/12/whats-new-in-ecto-2-0-1.pdf#page=39 + @type factory :: atom + alias Contraq.Repo @tags %{ gig: Contraq.Gigs.Gig, user: Contraq.Coherence.User } + @build_defaults [required_only: false] + + @spec base(factory, keyword) :: struct + defp base(factory_name, opts \\ []) - defp base(:gig) do - %{ + defp base(:gig, opts) do + [required_only: required_only] = Keyword.merge @build_defaults, opts + required = %{ name: Faker.Lorem.sentence, start_time: Timex.to_datetime(Faker.Date.forward(100)), # TODO: randomize the time too? end_time: &(Timex.shift &1[:start_time], minutes: :rand.uniform(4*60)), user: fn _ -> build :user end } + optional = %{ + city: Faker.Address.city, + state: Faker.Address.state_abbr + } + if required_only, do: required, else: Map.merge optional, required end - defp base(:user) do - %{ + defp base(:user, opts) do + [required_only: required_only] = Keyword.merge @build_defaults, opts + required = %{ email: Faker.Internet.email, password: Faker.Lorem.sentence, password_confirmation: &(&1[:password]) } + optional = %{} + if required_only, do: required, else: Map.merge optional, required end - def build(factory_name, attributes \\ %{}) do - base = base(factory_name) + @spec build(factory, map, keyword) :: struct + def build(factory_name, %{} = attributes, opts) do + base = base(factory_name, opts) merged_attributes = Map.merge(base, attributes) expanded_attributes = for {key, value} <- merged_attributes, into: %{} do new_value = if is_function(value), do: value.(merged_attributes), else: value @@ -35,10 +51,31 @@ defmodule Contraq.Factory do struct Map.fetch!(@tags, factory_name), expanded_attributes end - def insert!(factory_name, attributes \\ []) do - attributes_map = attributes |> Enum.into(%{}) - record = build(factory_name, attributes_map) + @spec build(factory, map) :: struct + def build(factory_name, %{} = attributes), do: build(factory_name, attributes, []) + + @spec build(factory, keyword) :: struct + def build(factory_name, opts), do: build(factory_name, %{}, opts) + + @spec build(factory) :: struct + def build(factory_name), do: build(factory_name, %{}, []) + + @spec insert!(factory, map, keyword) :: struct + def insert!(factory_name, %{} = attributes, opts) do + record = build(factory_name, attributes, opts) tag = record.__struct__ Repo.insert! tag.changeset(struct(tag), Map.from_struct record) end + + @spec insert!(factory, map) :: struct + def insert!(factory_name, %{} = attributes), do: insert!(factory_name, attributes, []) + + @spec insert!(factory, map) :: struct + def insert!(factory_name, %{} = attributes), do: insert!(factory_name, attributes, []) + + @spec insert!(factory, keyword) :: struct + def insert!(factory_name, opts), do: insert!(factory_name, %{}, opts) + + @spec insert!(factory) :: struct + def insert!(factory_name), do: build(factory_name, %{}, []) end