Repository: livebook-dev/kino (elixir)

Issue:
Date input default value cannot be at the extremes of the allowed range
I'm trying to use a `Kino.Input.date` in a livebook such that I want to limit the date range using `:min` and `:max`.

For example:

```ex
Kino.Input.date("From", min: ~D[2023-08-01], max: Date.utc_today())
```

Renders a widget with allowed dates 2023-08-01 all the way to 2023-09-01 (today) -- **both ends inclusive**.

---

I would like to set a default value with `:default`:

```ex
Kino.Input.date("From", default: ~D[2023-08-01], min: ~D[2023-08-01], max: Date.utc_today())
```

but I'm getting a validation error:


```
** (ArgumentError) expected :default to be bigger than :min, got: ~D[2023-08-01]
    (kino 0.10.0) lib/kino/input.ex:399: Kino.Input.date/2
```

Same on the `:max` end of the allowed range:

```ex
Kino.Input.date("From", default: Date.utc_today(), min: ~D[2023-08-01], max: Date.utc_today())
```

```
** (ArgumentError) expected :default to be smaller than :max, got: ~D[2023-09-01]
    (kino 0.10.0) lib/kino/input.ex:404: Kino.Input.date/2
```

The relevant code is:

https://github.com/livebook-dev/kino/blob/c0167dd44714fb81744b166b22decb07db7c3bce/lib/kino/input.ex#L396-L404

I think we have an off-by-one error. To match the behavior of the allowed range when using the widget we should test `default < min` and `default > max`.


Pull request:
Fix date validation when allowed range is given
Closes #325.

Patch:
diff --git a/lib/kino/input.ex b/lib/kino/input.ex
index 086f872..f44789c 100644
--- a/lib/kino/input.ex
+++ b/lib/kino/input.ex
@@ -275,7 +275,7 @@ defmodule Kino.Input do
     max = Keyword.get(opts, :max, nil) |> truncate_datetime()
     default = Keyword.get(opts, :default, nil) |> truncate_datetime()
 
-    if min && max && min > max do
+    if min && max && DateTime.compare(min, max) == :gt do
       raise ArgumentError,
             "expected :min to be less than :max, got: #{inspect(min)} and #{inspect(max)}"
     end
@@ -286,14 +286,12 @@ defmodule Kino.Input do
       &(is_struct(&1, NaiveDateTime) or &1 == nil)
     )
 
-    if min && default && default <= min do
-      raise ArgumentError,
-            "expected :default to be bigger than :min, got: #{inspect(default)} #{inspect(min)}"
+    if min && default && DateTime.compare(default, min) == :lt do
+      raise ArgumentError, "expected :default to be bigger than :min, got: #{inspect(default)} "
     end
 
-    if max && default && default > max do
-      raise ArgumentError,
-            "expected :default to be smaller than :max, got: #{inspect(default)}"
+    if max && default && DateTime.compare(default, max) == :gt do
+      raise ArgumentError, "expected :default to be smaller than :max, got: #{inspect(default)}"
     end
 
     new(%{
@@ -333,21 +331,19 @@ defmodule Kino.Input do
     max = Keyword.get(opts, :max, nil) |> truncate_time()
     default = Keyword.get(opts, :default, nil) |> truncate_time()
 
-    if min && max && min > max do
+    if min && max && Time.compare(min, max) == :gt do
       raise ArgumentError,
             "expected :min to be less than :max, got: #{inspect(min)} and #{inspect(max)}"
     end
 
     assert_default_value!(default, "be %Time{} or nil", &(is_struct(&1, Time) or &1 == nil))
 
-    if min && default && default <= min do
-      raise ArgumentError,
-            "expected :default to be bigger than :min, got: #{inspect(default)} #{inspect(min)}"
+    if min && default && Time.compare(default, min) == :lt do
+      raise ArgumentError, "expected :default to be bigger than :min, got: #{inspect(default)}"
     end
 
-    if max && default && default > max do
-      raise ArgumentError,
-            "expected :default to be smaller than :max, got: #{inspect(default)}"
+    if max && default && Time.compare(default, max) == :gt do
+      raise ArgumentError, "expected :default to be smaller than :max, got: #{inspect(default)}"
     end
 
     new(%{
@@ -386,21 +382,19 @@ defmodule Kino.Input do
     max = Keyword.get(opts, :max, nil)
     default = Keyword.get(opts, :default, nil)
 
-    if min && max && min > max do
+    if min && max && Date.compare(min, max) == :gt do
       raise ArgumentError,
             "expected :min to be less than :max, got: #{inspect(min)} and #{inspect(max)}"
     end
 
     assert_default_value!(default, "be %Date{} or nil", &(is_struct(&1, Date) or &1 == nil))
 
-    if min && default && default <= min do
-      raise ArgumentError,
-            "expected :default to be bigger than :min, got: #{inspect(default)}"
+    if min && default && Date.compare(default, min) == :lt do
+      raise ArgumentError, "expected :default to be bigger than :min, got: #{inspect(default)}"
     end
 
-    if max && default && default >= max do
-      raise ArgumentError,
-            "expected :default to be smaller than :max, got: #{inspect(default)}"
+    if max && default && Date.compare(default, max) == :gt do
+      raise ArgumentError, "expected :default to be smaller than :max, got: #{inspect(default)}"
     end
 
     new(%{


Test patch:
diff --git a/test/kino/input_test.exs b/test/kino/input_test.exs
index f08d946..7f7c28e 100644
--- a/test/kino/input_test.exs
+++ b/test/kino/input_test.exs
@@ -51,6 +51,17 @@ defmodule Kino.InputTest do
     end
   end
 
+  describe "date/2" do
+    test "allows default value at the edge of allowed range" do
+      assert %{attrs: %{default: ~D/2023-02-01/}} =
+               Kino.Input.date("Length",
+                 min: ~D/2023-02-01/,
+                 max: ~D/2023-03-01/,
+                 default: ~D/2023-02-01/
+               )
+    end
+  end
+
   describe "file/2" do
     test "raises an error when :accept is an empty list" do
       assert_raise ArgumentError, "expected :accept to be a non-empty list, got: []", fn ->