From cbe285c1bf7996277e71f05dfdc3a422a1069ed6 Mon Sep 17 00:00:00 2001 From: Nikki Kyllonen Date: Tue, 25 Jun 2024 14:26:30 -0700 Subject: [PATCH 1/4] Add DateScale tests --- test/date_scale_test.exs | 75 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 test/date_scale_test.exs diff --git a/test/date_scale_test.exs b/test/date_scale_test.exs new file mode 100644 index 0000000..8013cca --- /dev/null +++ b/test/date_scale_test.exs @@ -0,0 +1,75 @@ +defmodule Plox.DateScaleTest do + use ExUnit.Case, async: true + + alias Plox.DateScale + alias Plox.Scale + + describe "new/2" do + test "creates a scale from a positive range" do + assert %DateScale{} = DateScale.new(Date.range(~D[2023-08-01], ~D[2023-08-31])) + end + + test "creates a scale from a negative range" do + assert %DateScale{} = DateScale.new(Date.range(~D[2023-08-31], ~D[2023-08-01], -1)) + end + + test "creates a scale from a range that technically only has 1 date (considering step)" do + assert %DateScale{} = DateScale.new(Date.range(~D[2023-08-01], ~D[2023-08-02], 10)) + end + + test "raises if the range contains only one date" do + assert_raise(ArgumentError, fn -> + DateScale.new(Date.range(~D[2023-08-01], ~D[2023-08-01])) + end) + end + + test "raises if the range contains no dates" do + assert_raise(ArgumentError, fn -> + DateScale.new(Date.range(~D[2023-08-01], ~D[2023-08-02], -1)) + end) + end + end + + describe "implementation: Scale.values/1" do + test "returns a Date.Range struct representing the dates to be labeled" do + range = Date.range(~D[2023-08-01], ~D[2023-08-04], 2) + + assert ^range = range |> DateScale.new() |> Scale.values(%{step: 2}) + end + + test "works for a backwards range" do + range = Date.range(~D[2023-08-04], ~D[2023-08-01], -2) + + assert ^range = range |> DateScale.new() |> Scale.values(%{step: 2}) + end + end + + describe "implementation: Scale.convert_to_range/3" do + test "returns a number" do + scale = DateScale.new(Date.range(~D[2023-08-01], ~D[2023-08-04], 2)) + + assert 20.0 = Scale.convert_to_range(scale, ~D[2023-08-02], 0..60) + end + + test "works for a backwards range" do + scale = DateScale.new(Date.range(~D[2023-08-04], ~D[2023-08-01], -2)) + + assert 40.0 = Scale.convert_to_range(scale, ~D[2023-08-02], 0..60) + end + + test "works even if the value is technically not in the given range (considering step)" do + # the range's values are technically: [~D[2023-08-01], ~D[2023-08-03]] + scale = DateScale.new(Date.range(~D[2023-08-01], ~D[2023-08-04], 2)) + + assert 100.0 = Scale.convert_to_range(scale, ~D[2023-08-04], 0..100) + end + + test "raises if given a value not valid for the scale" do + scale = DateScale.new(Date.range(~D[2023-08-01], ~D[2023-08-04], 2)) + + assert_raise(ArgumentError, fn -> + Scale.convert_to_range(scale, ~D[2023-08-05], 0..100) + end) + end + end +end From 896b091746431d90404011648c3a88d1f037f416 Mon Sep 17 00:00:00 2001 From: Nikki Kyllonen Date: Fri, 5 Jul 2024 15:53:52 -0700 Subject: [PATCH 2/4] Add DateTimeScale tests --- test/datetime_scale_test.exs | 264 +++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 test/datetime_scale_test.exs diff --git a/test/datetime_scale_test.exs b/test/datetime_scale_test.exs new file mode 100644 index 0000000..aa31030 --- /dev/null +++ b/test/datetime_scale_test.exs @@ -0,0 +1,264 @@ +defmodule Plox.DateTimeScaleTest do + use ExUnit.Case, async: true + + alias Plox.DateTimeScale + alias Plox.Scale + + describe "new/2" do + test "creates a scale from DateTime structs" do + assert %DateTimeScale{} = + DateTimeScale.new(~U[2023-03-12 09:00:00Z], ~U[2023-03-12 11:00:00Z]) + end + + test "creates a scale from NaiveDateTime structs" do + assert %DateTimeScale{} = + DateTimeScale.new(~N[2023-03-12 09:00:00], ~N[2023-03-12 11:00:00]) + end + + test "raises if the start and end times are the same" do + assert_raise(ArgumentError, fn -> + DateTimeScale.new(~N[2023-03-12 09:00:00], ~N[2023-03-12 09:00:00]) + end) + end + + test "raises if the start time is before the end time" do + assert_raise(ArgumentError, fn -> + DateTimeScale.new(~N[2023-03-12 09:00:01], ~N[2023-03-12 09:00:00]) + end) + end + + test "raises if the types don't match" do + assert_raise(ArgumentError, fn -> + DateTimeScale.new(~N[2023-03-12 09:00:00], ~U[2023-03-12 11:00:00Z]) + end) + end + end + + describe "implementation: Scale.values/1" do + test "returns an ordered list of values representing the times to be labeled" do + scale = DateTimeScale.new(~N[2023-03-12 09:00:00], ~N[2023-03-12 11:00:00]) + step_in_minutes = 30 * 60 + + assert [ + ~N[2023-03-12 09:00:00], + ~N[2023-03-12 09:30:00], + ~N[2023-03-12 10:00:00], + ~N[2023-03-12 10:30:00], + ~N[2023-03-12 11:00:00] + ] = Scale.values(scale, %{step: step_in_minutes}) + end + + test "might not necessarily contain the last value in the given range" do + scale = DateTimeScale.new(~N[2023-08-01 00:00:00], ~N[2023-08-04 23:59:59]) + step_in_minutes = 24 * 60 * 60 + + assert [ + ~N[2023-08-01 00:00:00], + ~N[2023-08-02 00:00:00], + ~N[2023-08-03 00:00:00], + ~N[2023-08-04 00:00:00] + ] = Scale.values(scale, %{step: step_in_minutes}) + end + + # test "returns values spanning the start of Daylight Savings Time in hours" do + # # `start_dt` is getting an argument error: + # # ** (ArgumentError) cannot build datetime with ~D[2023-03-11] and ~T[23:00:00], + # # reason: :utc_only_time_zone_database + # timezone = "America/Los_Angeles" + # start_dt = DateTime.new!(~D[2023-03-11], ~T[23:00:00], timezone) + # end_dt = DateTime.new!(~D[2023-03-12], ~T[03:00:00], timezone) + # scale = DateTimeScale.new(start_dt, end_dt) + # # scale = DateTimeScale.new(start_dt, end_dt, 1, :hour) + # step_in_minutes = 60 * 60 + + # # expected to skip 2am PST since we "spring forward" to 3am at 2am + # expected_utc_values = [ + # # 11pm PST + # ~U[2023-03-12 07:00:00Z], + # # 12am PST + # ~U[2023-03-12 08:00:00Z], + # # 1am PST + # ~U[2023-03-12 09:00:00Z], + # # 3am PDT + # ~U[2023-03-12 10:00:00Z] + # ] + + # test_values = Scale.values(scale, %{step: step_in_minutes}) + + # assert Enum.map(expected_utc_values, &DateTime.shift_zone!(&1, timezone)) == + # test_values + # end + + # test "returns values spanning the start of Daylight Savings Time in days" do + # # `start_dt` is getting an argument error: + # # ** (ArgumentError) cannot build datetime with ~D[2023-03-11] and ~T[00:00:00], + # # reason: :utc_only_time_zone_database + # timezone = "America/Los_Angeles" + # start_dt = DateTime.new!(~D[2023-03-11], ~T[00:00:00], timezone) + # end_dt = DateTime.new!(~D[2023-03-14], ~T[00:00:00], timezone) + # scale = DateTimeScale.new(start_dt, end_dt) + # # scale = DateTimeScale.new(start_dt, end_dt, 1, :day) + # step_in_minutes = 24 * 60 * 60 + + # expected_utc_values = [ + # # 12am PST + # ~U[2023-03-11 08:00:00Z], + # ~U[2023-03-12 08:00:00Z], + # # 12am PDT + # ~U[2023-03-13 07:00:00Z], + # ~U[2023-03-14 07:00:00Z] + # ] + + # test_values = Scale.values(scale, %{step: step_in_minutes}) + + # assert Enum.map(expected_utc_values, &DateTime.shift_zone!(&1, timezone)) == test_values + # end + + # test "returns values spanning the end of Daylight Savings Time in hours" do + # timezone = "America/Los_Angeles" + # start_dt = DateTime.new!(~D[2023-11-04], ~T[23:00:00], timezone) + # end_dt = DateTime.new!(~D[2023-11-05], ~T[03:00:00], timezone) + # scale = DateTimeScale.new(start_dt, end_dt, 1, :hour) + + # # expected to repeat 1am since we "fall back" to 1am at 2am + # expected_utc_values = [ + # # 11pm PDT + # ~U[2023-11-05 06:00:00Z], + # # 12am PDT + # ~U[2023-11-05 07:00:00Z], + # # 1am PDT + # ~U[2023-11-05 08:00:00Z], + # # 1am PST + # ~U[2023-11-05 09:00:00Z], + # # 2am PST + # ~U[2023-11-05 10:00:00Z], + # # 3am PST + # ~U[2023-11-05 11:00:00Z] + # ] + + # test_values = Scale.values(scale) + + # assert Enum.map(expected_utc_values, &DateTime.shift_zone!(&1, timezone)) == + # test_values + # end + + # test "returns values spanning the end of Daylight Savings Time in days" do + # timezone = "America/Los_Angeles" + # start_dt = DateTime.new!(~D[2023-11-04], ~T[00:00:00], timezone) + # end_dt = DateTime.new!(~D[2023-11-07], ~T[00:00:00], timezone) + # scale = DateTimeScale.new(start_dt, end_dt, 1, :day) + + # expected_utc_values = [ + # # 12am PDT + # ~U[2023-11-04 07:00:00Z], + # ~U[2023-11-05 07:00:00Z], + # # 12am PST + # ~U[2023-11-06 08:00:00Z], + # ~U[2023-11-07 08:00:00Z] + # ] + + # test_values = Scale.values(scale) + + # assert Enum.map(expected_utc_values, &DateTime.shift_zone!(&1, timezone)) == test_values + # end + + # test "raises if given a range + step combination that would generate invalid DateTime values" do + # # `start_dt` is getting an argument error: + # # ** (ArgumentError) cannot build datetime with ~D[2023-03-11] and ~T[02:00:00], + # # reason: :utc_only_time_zone_database + # timezone = "America/Los_Angeles" + # start_dt = DateTime.new!(~D[2023-03-11], ~T[02:00:00], timezone) + # end_dt = DateTime.new!(~D[2023-03-14], ~T[02:00:00], timezone) + # scale = DateTimeScale.new(start_dt, end_dt) + # # scale = DateTimeScale.new(start_dt, end_dt, 1, :day) + # step_in_minutes = 24 * 60 * 60 + + # # 2am doesn't exist on the 12th in this timezone: + # assert_raise(ArgumentError, fn -> + # Scale.values(scale, %{step: step_in_minutes}) + # end) + # end + end + + describe "implementation: Scale.convert_to_range/3" do + test "returns a number" do + scale = DateTimeScale.new(~N[2023-03-12 09:00:00], ~N[2023-03-12 11:00:00]) + + assert 25.0 = Scale.convert_to_range(scale, ~N[2023-03-12 09:30:00], 0..100) + end + + test "raises if given a value outside the range" do + scale = DateTimeScale.new(~N[2023-03-12 09:00:00], ~N[2023-03-12 11:00:00]) + + assert_raise(ArgumentError, fn -> + Scale.convert_to_range(scale, ~N[2023-03-12 11:00:01], 0..100) + end) + end + + test "raises if given a non-matching type" do + scale = DateTimeScale.new(~N[2023-03-12 09:00:00], ~N[2023-03-12 11:00:00]) + + assert_raise(ArgumentError, fn -> + Scale.convert_to_range(scale, ~U[2023-03-12 09:30:00Z], 0..100) + end) + end + + # test "returns correct number for scale spanning the start of Daylight Savings Time in hours" do + # timezone = "America/Los_Angeles" + # start_dt = DateTime.new!(~D[2023-03-11], ~T[23:00:00], timezone) + # end_dt = DateTime.new!(~D[2023-03-12], ~T[04:00:00], timezone) + # scale = DateTimeScale.new(start_dt, end_dt, 1, :hour) + + # # 4/5 values between `start_dt` and `end_dt` + # value = DateTime.shift_zone!(~U[2023-03-12 10:00:00Z], timezone) + + # # 4/5 = 75.0 since [0.0, 25.0, 50.0, 75.0, 100.0] + # assert 75.0 = Scale.convert_to_range(scale, value, 0..100) + # end + + # test "returns correct number for scale spanning the start of Daylight Savings Time in days" do + # timezone = "America/Los_Angeles" + # start_dt = DateTime.new!(~D[2023-03-11], ~T[00:00:00], timezone) + # end_dt = DateTime.new!(~D[2023-03-15], ~T[00:00:00], timezone) + # scale = DateTimeScale.new(start_dt, end_dt, 1, :day) + + # # 2/5 values between `start_dt` and `end_dt` + # # This is "off by 15 minutes" because of the missing hour on the 12th + # # 25% will be off by 15 minutes, 50% will be off by half an hour, 75% will + # # be off by 45 minutes. + # value = DateTime.shift_zone!(~U[2023-03-12 07:45:00Z], timezone) + + # # 2/5 = 25.0 since [0.0, 25.0, 50.0, 75.0, 100.0] + # assert 25.0 = Scale.convert_to_range(scale, value, 0..100) + # end + + # test "returns correct number for scale spanning the end of Daylight Savings Time in hours" do + # timezone = "America/Los_Angeles" + # start_dt = DateTime.new!(~D[2023-11-04], ~T[23:00:00], timezone) + # end_dt = DateTime.new!(~D[2023-11-05], ~T[02:00:00], timezone) + # scale = DateTimeScale.new(start_dt, end_dt, 1, :hour) + + # # 4/5 values between `start_dt` and `end_dt` + # value = DateTime.shift_zone!(~U[2023-11-05 09:00:00Z], timezone) + + # # 4/5 = 75.0 since [0.0, 25.0, 50.0, 75.0, 100.0] + # assert 75.0 = Scale.convert_to_range(scale, value, 0..100) + # end + + # test "returns correct number for scale spanning the end of Daylight Savings Time in days" do + # timezone = "America/Los_Angeles" + # start_dt = DateTime.new!(~D[2023-11-04], ~T[00:00:00], timezone) + # end_dt = DateTime.new!(~D[2023-11-08], ~T[00:00:00], timezone) + # scale = DateTimeScale.new(start_dt, end_dt, 1, :day) + + # # 3/5 values between `start_dt` and `end_dt` + # # This is "off by half an hour" because there's an extra hour on the 5th + # # 25% will be off by 15 minutes, 50% will be off by half an hour, 75% will + # # be off by 45 minutes. + # value = DateTime.shift_zone!(~U[2023-11-06 07:30:00Z], timezone) + + # # 3/5 = 50.0 since [0.0, 25.0, 50.0, 75.0, 100.0] + # assert 50.0 = Scale.convert_to_range(scale, value, 0..100) + # end + end +end From 9244062f099e83cd1115b29bbe7687a071d67be9 Mon Sep 17 00:00:00 2001 From: Nikki Kyllonen Date: Wed, 16 Oct 2024 13:26:23 -0400 Subject: [PATCH 3/4] Add NumberScale tests --- test/date_scale_test.exs | 2 +- test/datetime_scale_test.exs | 2 +- test/number_scale_test.exs | 96 ++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 test/number_scale_test.exs diff --git a/test/date_scale_test.exs b/test/date_scale_test.exs index 8013cca..2cc3520 100644 --- a/test/date_scale_test.exs +++ b/test/date_scale_test.exs @@ -30,7 +30,7 @@ defmodule Plox.DateScaleTest do end end - describe "implementation: Scale.values/1" do + describe "implementation: Scale.values/2" do test "returns a Date.Range struct representing the dates to be labeled" do range = Date.range(~D[2023-08-01], ~D[2023-08-04], 2) diff --git a/test/datetime_scale_test.exs b/test/datetime_scale_test.exs index aa31030..832ed55 100644 --- a/test/datetime_scale_test.exs +++ b/test/datetime_scale_test.exs @@ -34,7 +34,7 @@ defmodule Plox.DateTimeScaleTest do end end - describe "implementation: Scale.values/1" do + describe "implementation: Scale.values/2" do test "returns an ordered list of values representing the times to be labeled" do scale = DateTimeScale.new(~N[2023-03-12 09:00:00], ~N[2023-03-12 11:00:00]) step_in_minutes = 30 * 60 diff --git a/test/number_scale_test.exs b/test/number_scale_test.exs new file mode 100644 index 0000000..fa1240d --- /dev/null +++ b/test/number_scale_test.exs @@ -0,0 +1,96 @@ +defmodule Plox.NumberScaleTest do + use ExUnit.Case, async: true + + alias Plox.NumberScale + alias Plox.Scale + + describe "new/2" do + test "creates a scale from floats" do + assert %NumberScale{} = NumberScale.new(0.0, 100.0) + end + + test "creates a scale from integers" do + assert %NumberScale{} = NumberScale.new(0, 100) + end + + test "creates a backwards scale" do + assert %NumberScale{} = NumberScale.new(100, 0) + end + + test "raises if min and max are equal" do + assert_raise(ArgumentError, fn -> + NumberScale.new(0, 0) + end) + end + end + + describe "implementation: Scale.values/1" do + test "works if given floats" do + scale = NumberScale.new(0.0, 100.0) + + assert [+0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] = + Scale.values(scale, %{}) + end + + test "works if given integers" do + scale = NumberScale.new(0, 1) + + assert [+0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] = Scale.values(scale, %{}) + end + + test "doesn't have floating point rounding errors" do + scale = NumberScale.new(0.0, 1.0) + + # with the older floats implementation this would result in: + # [0.0, 0.2, 0.4, 0.6000000000000001, 0.8, 1.0] + assert [+0.0, 0.2, 0.4, 0.6, 0.8, 1.0] = Scale.values(scale, %{ticks: 6}) + end + + test "works for backwards scales" do + scale = NumberScale.new(10, 0) + + assert [10.0, 8.0, 6.0, 4.0, 2.0, +0.0] = Scale.values(scale, %{ticks: 6}) + end + + test "works for negative numbers" do + scale = NumberScale.new(-1.5, 1.5) + + assert [-1.5, -1.0, -0.5, +0.0, 0.5, 1.0, 1.5] = Scale.values(scale, %{ticks: 7}) + end + end + + describe "implementation: Scale.convert_to_range/3" do + test "works if given integers" do + scale = NumberScale.new(0, 4) + + assert 25.0 = Scale.convert_to_range(scale, 1, 0..100) + end + + test "works if given floats" do + scale = NumberScale.new(0.0, 10.0) + + assert 40.0 = Scale.convert_to_range(scale, 4.0, 0..100) + end + + test "works for backwards scales" do + scale = NumberScale.new(10, 0) + + assert 60.0 = Scale.convert_to_range(scale, 4, 0..100) + end + + test "works for negative numbers" do + scale = NumberScale.new(-1.5, 1.5) + + assert 10.0 = Scale.convert_to_range(scale, -1.2, 0..100) + end + + # There used to be a raise when the value was out of range, but that is no longer in place + # test "raises if given a value outside of the scale" do + # scale = NumberScale.new(0, 10) + + # assert_raise(ArgumentError, fn -> + # Scale.convert_to_range(scale, 15, 0..100) + # end) + # end + end +end From 9b58e97dae183fa80cc4163b1badace6a9cb872a Mon Sep 17 00:00:00 2001 From: Nikki Kyllonen Date: Wed, 16 Oct 2024 13:36:52 -0400 Subject: [PATCH 4/4] Add FixedValuesScale tests --- test/fixed_values_scale_test.exs | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 test/fixed_values_scale_test.exs diff --git a/test/fixed_values_scale_test.exs b/test/fixed_values_scale_test.exs new file mode 100644 index 0000000..baada2d --- /dev/null +++ b/test/fixed_values_scale_test.exs @@ -0,0 +1,52 @@ +defmodule Plox.FixedValuesScaleTest do + use ExUnit.Case, async: true + + alias Plox.FixedValuesScale + alias Plox.Scale + + describe "new/2" do + test "creates a scale from any enumerable" do + assert %FixedValuesScale{} = FixedValuesScale.new([:foo, :bar, :baz]) + assert %FixedValuesScale{} = FixedValuesScale.new(1..10) + + # it's silly, but just to prove the point + assert %FixedValuesScale{} = + FixedValuesScale.new(0 |> Stream.unfold(&{&1, &1 + 1}) |> Stream.take(10)) + end + + test "raises if given no values" do + assert_raise(ArgumentError, fn -> + FixedValuesScale.new([]) + end) + end + + test "raises if given only one value" do + assert_raise(ArgumentError, fn -> + FixedValuesScale.new([:foo]) + end) + end + end + + describe "implementation: Scale.values/1" do + test "returns an ordered list of all the values given" do + assert [:foo, :bar, :baz] = [:foo, :bar, :baz] |> FixedValuesScale.new() |> Scale.values(%{}) + assert [1, 2, 3, 4, 5] = 1..5 |> FixedValuesScale.new() |> Scale.values(%{}) + end + end + + describe "implementation: Scale.convert_to_range/3" do + test "returns a number" do + scale = FixedValuesScale.new([:foo, :bar, :baz]) + + assert 50.0 = Scale.convert_to_range(scale, :bar, 0..100) + end + + test "raises if given a value not valid for the scale" do + scale = FixedValuesScale.new([:foo, :bar, :baz]) + + assert_raise(ArgumentError, fn -> + Scale.convert_to_range(scale, :lol, 0..100) + end) + end + end +end