From 2798f58989516979659e4b6f349cec6554114b5e Mon Sep 17 00:00:00 2001 From: njausteve Date: Sat, 21 Sep 2024 21:46:07 +0800 Subject: [PATCH] feat: Add is_relative field to FieldContent struct --- lib/structs/field_content.ex | 32 +++++++++++++++++------------ lib/utils/validators.ex | 27 ++++++++++++++---------- test/structs/field_content_test.exs | 29 ++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 24 deletions(-) diff --git a/lib/structs/field_content.ex b/lib/structs/field_content.ex index 61e16c1..e6a6cb0 100644 --- a/lib/structs/field_content.ex +++ b/lib/structs/field_content.ex @@ -40,6 +40,10 @@ defmodule ExPass.Structs.FieldContent do The default value is false, which displays the time and date using the current device's time zone. If set to true, the time and date appear in the time zone associated with the date and time of value. This key doesn't affect the pass relevance calculation. + + - `is_relative`: A Boolean value that controls whether the date appears as a relative date. + The default value is false, which displays the date as an absolute date. + This key doesn't affect the pass relevance calculation. """ use TypedStruct @@ -107,13 +111,14 @@ defmodule ExPass.Structs.FieldContent do field :data_detector_types, data_detector_types(), default: nil field :date_style, date_style(), default: nil field :ignores_time_zone, boolean(), default: nil + field :is_relative, boolean(), default: nil end @doc """ Creates a new FieldContent struct. This function initializes a new FieldContent struct with the given attributes. - It validates the `attributed_value`, `change_message`, `currency_code`, `data_detector_types`, `date_style`, and `ignores_time_zone`. + It validates the `attributed_value`, `change_message`, `currency_code`, `data_detector_types`, `date_style`, `ignores_time_zone`, and `is_relative`. ## Parameters @@ -130,22 +135,22 @@ defmodule ExPass.Structs.FieldContent do ## Examples iex> FieldContent.new(%{attributed_value: "Hello, World!"}) - %FieldContent{attributed_value: "Hello, World!", change_message: nil, currency_code: nil, data_detector_types: nil, date_style: nil, ignores_time_zone: nil} + %FieldContent{attributed_value: "Hello, World!", change_message: nil, currency_code: nil, data_detector_types: nil, date_style: nil, ignores_time_zone: nil, is_relative: nil} - iex> FieldContent.new(%{attributed_value: 42, data_detector_types: ["PKDataDetectorTypePhoneNumber"], date_style: "PKDateStyleShort", ignores_time_zone: true}) - %FieldContent{attributed_value: 42, change_message: nil, currency_code: nil, data_detector_types: ["PKDataDetectorTypePhoneNumber"], date_style: "PKDateStyleShort", ignores_time_zone: true} + iex> FieldContent.new(%{attributed_value: 42, data_detector_types: ["PKDataDetectorTypePhoneNumber"], date_style: "PKDateStyleShort", ignores_time_zone: true, is_relative: false}) + %FieldContent{attributed_value: 42, change_message: nil, currency_code: nil, data_detector_types: ["PKDataDetectorTypePhoneNumber"], date_style: "PKDateStyleShort", ignores_time_zone: true, is_relative: false} iex> datetime = ~U[2023-04-15 14:30:00Z] - iex> field_content = FieldContent.new(%{attributed_value: datetime, currency_code: "USD", date_style: "PKDateStyleLong", ignores_time_zone: true}) - iex> %FieldContent{attributed_value: ^datetime, currency_code: "USD", date_style: "PKDateStyleLong", ignores_time_zone: true} = field_content + iex> field_content = FieldContent.new(%{attributed_value: datetime, currency_code: "USD", date_style: "PKDateStyleLong", ignores_time_zone: true, is_relative: true}) + iex> %FieldContent{attributed_value: ^datetime, currency_code: "USD", date_style: "PKDateStyleLong", ignores_time_zone: true, is_relative: true} = field_content iex> field_content.change_message nil - iex> FieldContent.new(%{attributed_value: "Click here", data_detector_types: ["PKDataDetectorTypeLink"], date_style: "PKDateStyleFull"}) - %FieldContent{attributed_value: "Click here", change_message: nil, currency_code: nil, data_detector_types: ["PKDataDetectorTypeLink"], date_style: "PKDateStyleFull", ignores_time_zone: nil} + iex> FieldContent.new(%{attributed_value: "Click here", data_detector_types: ["PKDataDetectorTypeLink"], date_style: "PKDateStyleFull", is_relative: false}) + %FieldContent{attributed_value: "Click here", change_message: nil, currency_code: nil, data_detector_types: ["PKDataDetectorTypeLink"], date_style: "PKDateStyleFull", ignores_time_zone: nil, is_relative: false} - iex> FieldContent.new(%{attributed_value: "No detectors", data_detector_types: [], change_message: "Updated to %@", ignores_time_zone: true}) - %FieldContent{attributed_value: "No detectors", change_message: "Updated to %@", currency_code: nil, data_detector_types: [], date_style: nil, ignores_time_zone: true} + iex> FieldContent.new(%{attributed_value: "No detectors", data_detector_types: [], change_message: "Updated to %@", ignores_time_zone: true, is_relative: true}) + %FieldContent{attributed_value: "No detectors", change_message: "Updated to %@", currency_code: nil, data_detector_types: [], date_style: nil, ignores_time_zone: true, is_relative: true} """ @spec new(map()) :: %__MODULE__{} def new(attrs \\ %{}) do @@ -157,7 +162,8 @@ defmodule ExPass.Structs.FieldContent do |> validate(:currency_code, &Validators.validate_currency_code/1) |> validate(:data_detector_types, &Validators.validate_data_detector_types/1) |> validate(:date_style, &Validators.validate_date_style/1) - |> validate(:ignores_time_zone, &Validators.validate_ignores_timezone/1) + |> validate(:ignores_time_zone, &Validators.validate_boolean_field(&1, :ignores_time_zone)) + |> validate(:is_relative, &Validators.validate_boolean_field(&1, :is_relative)) struct!(__MODULE__, attrs) end @@ -193,8 +199,8 @@ defmodule ExPass.Structs.FieldContent do :date_style -> "Supported values are: PKDateStyleNone, PKDateStyleShort, PKDateStyleMedium, PKDateStyleLong, PKDateStyleFull" - :ignores_time_zone -> - "ignores_time_zone must be a boolean value (true or false)" + key when key in [:ignores_time_zone, :is_relative] -> + "#{key} must be a boolean value (true or false)" _ -> "" diff --git a/lib/utils/validators.ex b/lib/utils/validators.ex index d047f55..223ac48 100644 --- a/lib/utils/validators.ex +++ b/lib/utils/validators.ex @@ -251,9 +251,14 @@ defmodule ExPass.Utils.Validators do def validate_date_style(_), do: {:error, "date_style must be a string"} @doc """ - Validates the ignores_time_zone field. + Validates a boolean field. - The ignores_time_zone must be a boolean value. + The field must be a boolean value or nil. + + ## Parameters + + * `value` - The value to validate. + * `field_name` - The name of the field being validated as an atom. ## Returns @@ -262,23 +267,23 @@ defmodule ExPass.Utils.Validators do ## Examples - iex> validate_ignores_timezone(true) + iex> validate_boolean_field(true, :ignores_time_zone) :ok - iex> validate_ignores_timezone(false) + iex> validate_boolean_field(false, :is_relative) :ok - iex> validate_ignores_timezone(nil) + iex> validate_boolean_field(nil, :ignores_time_zone) :ok - iex> validate_ignores_timezone("true") - {:error, "ignores_time_zone must be a boolean"} + iex> validate_boolean_field("true", :is_relative) + {:error, "is_relative must be a boolean"} """ - @spec validate_ignores_timezone(boolean() | nil) :: :ok | {:error, String.t()} - def validate_ignores_timezone(nil), do: :ok - def validate_ignores_timezone(value) when is_boolean(value), do: :ok - def validate_ignores_timezone(_), do: {:error, "ignores_time_zone must be a boolean"} + @spec validate_boolean_field(boolean() | nil, atom()) :: :ok | {:error, String.t()} + def validate_boolean_field(nil, _field_name), do: :ok + def validate_boolean_field(value, _field_name) when is_boolean(value), do: :ok + def validate_boolean_field(_, field_name), do: {:error, "#{field_name} must be a boolean"} defp contains_unsupported_html_tags?(string) do # Remove all valid anchor tags diff --git a/test/structs/field_content_test.exs b/test/structs/field_content_test.exs index aaeb132..c55f437 100644 --- a/test/structs/field_content_test.exs +++ b/test/structs/field_content_test.exs @@ -249,4 +249,33 @@ defmodule ExPass.Structs.FieldContentTest do end end end + + describe "is_relative" do + test "new/1 creates a valid FieldContent struct with is_relative set to true" do + result = FieldContent.new(%{is_relative: true}) + + assert %FieldContent{is_relative: true} = result + assert Jason.encode!(result) == ~s({"isRelative":true}) + end + + test "new/1 creates a valid FieldContent struct with is_relative set to false" do + result = FieldContent.new(%{is_relative: false}) + + assert %FieldContent{is_relative: false} = result + assert Jason.encode!(result) == ~s({"isRelative":false}) + end + + test "new/1 defaults to nil when is_relative is not provided" do + result = FieldContent.new(%{}) + + assert %FieldContent{is_relative: nil} = result + assert Jason.encode!(result) == ~s({}) + end + + test "new/1 raises ArgumentError when is_relative is not a boolean" do + assert_raise ArgumentError, ~r/is_relative must be a boolean/, fn -> + FieldContent.new(%{is_relative: "true"}) + end + end + end end