rails-datepicker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +29 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +166 -0
  5. data/app/assets/stylesheets/rails_datepicker.css +98 -0
  6. data/app/javascript/controllers/rails_datepicker_controller.js +412 -0
  7. data/app/javascript/rails-datepicker/calendar.js +236 -0
  8. data/app/javascript/rails-datepicker/index.js +1 -0
  9. data/config/importmap.rb +2 -0
  10. data/config/locales/rails_datepicker.am.yml +12 -0
  11. data/config/locales/rails_datepicker.en.yml +12 -0
  12. data/lib/generators/rails_datepicker/install_generator.rb +87 -0
  13. data/lib/generators/rails_datepicker/templates/initializer.rb.tt +9 -0
  14. data/lib/rails-datepicker.rb +4 -0
  15. data/lib/rails_datepicker/calendar.rb +150 -0
  16. data/lib/rails_datepicker/configuration.rb +55 -0
  17. data/lib/rails_datepicker/engine.rb +31 -0
  18. data/lib/rails_datepicker/form_helper.rb +267 -0
  19. data/lib/rails_datepicker/simple_form_input.rb +17 -0
  20. data/lib/rails_datepicker/version.rb +5 -0
  21. data/lib/rails_datepicker.rb +25 -0
  22. data/package.json +22 -0
  23. data/test/application_system_test_case.rb +11 -0
  24. data/test/dummy/app/controllers/application_controller.rb +4 -0
  25. data/test/dummy/app/controllers/students_controller.rb +27 -0
  26. data/test/dummy/app/javascript/application.js +6 -0
  27. data/test/dummy/app/models/application_record.rb +5 -0
  28. data/test/dummy/app/models/student.rb +5 -0
  29. data/test/dummy/app/views/layouts/application.html.erb +10 -0
  30. data/test/dummy/app/views/students/frame.html.erb +8 -0
  31. data/test/dummy/app/views/students/new.html.erb +21 -0
  32. data/test/dummy/config/application.rb +16 -0
  33. data/test/dummy/config/boot.rb +2 -0
  34. data/test/dummy/config/cable.yml +2 -0
  35. data/test/dummy/config/database.yml +3 -0
  36. data/test/dummy/config/environment.rb +4 -0
  37. data/test/dummy/config/environments/test.rb +11 -0
  38. data/test/dummy/config/importmap.rb +5 -0
  39. data/test/dummy/config/routes.rb +7 -0
  40. data/test/dummy/db/schema.rb +7 -0
  41. data/test/fixtures/tailwind.css +2 -0
  42. data/test/generators/install_generator_test.rb +53 -0
  43. data/test/helpers/form_helper_test.rb +77 -0
  44. data/test/integration/form_submission_test.rb +22 -0
  45. data/test/javascript/calendar_test.js +56 -0
  46. data/test/rails_datepicker/calendar_test.rb +57 -0
  47. data/test/rails_datepicker/configuration_test.rb +39 -0
  48. data/test/simple_form_input_test.rb +20 -0
  49. data/test/system/datepicker_test.rb +93 -0
  50. data/test/test_helper.rb +9 -0
  51. metadata +253 -0
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
6
+ if ENV["RUN_SYSTEM_TESTS"] == "1"
7
+ driven_by :selenium, using: :headless_chrome, screen_size: [375, 812]
8
+ else
9
+ driven_by :rack_test
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationController < ActionController::Base
4
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StudentsController < ApplicationController
4
+ def new
5
+ @student = Student.new(birth_date: Date.new(2023, 9, 12))
6
+ end
7
+
8
+ def create
9
+ @student = Student.new(student_params)
10
+ if @student.save
11
+ render plain: @student.birth_date.iso8601
12
+ else
13
+ render :new, status: :unprocessable_entity
14
+ end
15
+ end
16
+
17
+ def frame
18
+ @student = Student.new(birth_date: Date.new(2024, 1, 1))
19
+ render :frame
20
+ end
21
+
22
+ private
23
+
24
+ def student_params
25
+ params.require(:student).permit(:name, :birth_date)
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ import "@hotwired/turbo-rails"
2
+ import { Application } from "@hotwired/stimulus"
3
+ import RailsDatepickerController from "rails-datepicker/controller"
4
+
5
+ window.Stimulus = Application.start()
6
+ window.Stimulus.register("rails-datepicker", RailsDatepickerController)
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationRecord < ActiveRecord::Base
4
+ primary_abstract_class
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Student < ApplicationRecord
4
+ validates :name, presence: true
5
+ end
@@ -0,0 +1,10 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Stimulus Datepicker test</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= javascript_importmap_tags if respond_to?(:javascript_importmap_tags) %>
7
+ <%= stylesheet_link_tag "rails_datepicker", "data-turbo-track": "reload" %>
8
+ </head>
9
+ <body><%= yield %></body>
10
+ </html>
@@ -0,0 +1,8 @@
1
+ <%= turbo_frame_tag "datepicker_frame" do %>
2
+ <%= rails_datepicker_field :student, :birth_date,
3
+ object: @student,
4
+ calendar: :gregorian,
5
+ inline: true,
6
+ theme: :dark,
7
+ id: "frame_birth_date" %>
8
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <%= form_with model: @student do |form| %>
2
+ <%= form.label :name %>
3
+ <%= form.text_field :name, value: "Test Student" %>
4
+ <%= form.label :birth_date %>
5
+ <%= form.datepicker_field :birth_date,
6
+ calendar: :ethiopian,
7
+ locale: :am,
8
+ min: Date.new(2020, 1, 1),
9
+ max: Date.new(2030, 12, 31),
10
+ disabled_dates: [Date.new(2023, 9, 13)] %>
11
+ <%= form.submit %>
12
+ <% end %>
13
+
14
+ <%= rails_datepicker_field :student, :birth_date, object: @student, calendar: :gregorian, id: "standalone_birth_date" %>
15
+
16
+ <%= turbo_frame_tag "datepicker_frame" do %>
17
+ <%= link_to "Replace frame", datepicker_frame_path %>
18
+ <% end %>
19
+
20
+ <%= rails_datepicker_field :student, :birth_date, object: @student,
21
+ calendar: :ethiopian, display_calendar: :gregorian, locale: :am, id: "gregorian_result" %>
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "boot"
4
+ require "rails/all"
5
+ Bundler.require(*Rails.groups)
6
+ require "rails_datepicker"
7
+
8
+ module Dummy
9
+ class Application < Rails::Application
10
+ config.root = File.expand_path("..", __dir__)
11
+ config.load_defaults 8.0
12
+ config.eager_load = false
13
+ config.secret_key_base = "test-secret-key-base-for-rails-datepicker"
14
+ config.hosts.clear
15
+ end
16
+ end
@@ -0,0 +1,2 @@
1
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__)
2
+ require "bundler/setup"
@@ -0,0 +1,2 @@
1
+ test:
2
+ adapter: test
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: ":memory:"
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "application"
4
+ Rails.application.initialize!
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.configure do
4
+ config.cache_classes = true
5
+ config.eager_load = false
6
+ config.consider_all_requests_local = true
7
+ config.action_controller.perform_caching = false
8
+ config.action_controller.allow_forgery_protection = false
9
+ config.action_dispatch.show_exceptions = :rescuable
10
+ config.active_support.deprecation = :stderr
11
+ end
@@ -0,0 +1,5 @@
1
+ pin "application"
2
+ pin "@hotwired/stimulus", to: "stimulus.min.js"
3
+ pin "@hotwired/turbo-rails", to: "turbo.min.js"
4
+ pin "rails-datepicker/calendar", to: "rails-datepicker/calendar.js"
5
+ pin "rails-datepicker/controller", to: "controllers/rails_datepicker_controller.js"
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ resources :students, only: %i[new create]
5
+ get "/datepicker", to: "students#new"
6
+ get "/datepicker/frame", to: "students#frame"
7
+ end
@@ -0,0 +1,7 @@
1
+ ActiveRecord::Schema.define(version: 1) do
2
+ create_table :students, force: true do |table|
3
+ table.string :name
4
+ table.date :birth_date
5
+ table.timestamps
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ @import "tailwindcss";
2
+ @import "../../app/assets/stylesheets/rails_datepicker.css";
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+ require "generators/rails_datepicker/install_generator"
5
+
6
+ class InstallGeneratorTest < Rails::Generators::TestCase
7
+ tests RailsDatepicker::Generators::InstallGenerator
8
+ destination File.expand_path("../tmp/generator", __dir__)
9
+ setup :prepare_destination
10
+
11
+ setup do
12
+ FileUtils.mkdir_p(File.join(destination_root, "config"))
13
+ File.write(File.join(destination_root, "config/importmap.rb"), "# pins\n")
14
+ FileUtils.mkdir_p(File.join(destination_root, "app/assets/tailwind"))
15
+ File.write(File.join(destination_root, "app/assets/tailwind/application.css"), '@import "tailwindcss";\n')
16
+ end
17
+
18
+ test "installs configuration, JavaScript, importmap pin, and Tailwind source" do
19
+ run_generator
20
+
21
+ assert_file "config/initializers/rails_datepicker.rb"
22
+ assert_file "app/javascript/controllers/rails_datepicker_controller.js"
23
+ assert_file "app/javascript/rails-datepicker/calendar.js"
24
+ assert_file "app/assets/tailwind/rails_datepicker.css"
25
+ assert_file "config/importmap.rb", /rails-datepicker\/calendar/
26
+ assert_file "app/assets/tailwind/application.css", /rails_datepicker/
27
+ end
28
+
29
+ test "can skip Tailwind integration" do
30
+ run_generator ["--skip-tailwind"]
31
+
32
+ assert_no_file "app/assets/tailwind/rails_datepicker.css"
33
+ end
34
+
35
+ test "uses a relative calendar import for JavaScript bundlers" do
36
+ FileUtils.rm_f(File.join(destination_root, "config/importmap.rb"))
37
+ run_generator ["--skip-tailwind"]
38
+
39
+ assert_file "app/javascript/controllers/rails_datepicker_controller.js", /from "\.\.\/rails-datepicker\/calendar\.js"/
40
+ end
41
+
42
+ test "does not modify a non-Tailwind application stylesheet" do
43
+ FileUtils.rm_f(File.join(destination_root, "app/assets/tailwind/application.css"))
44
+ FileUtils.mkdir_p(File.join(destination_root, "app/assets/stylesheets"))
45
+ File.write(File.join(destination_root, "app/assets/stylesheets/application.css"), "body { color: black; }\n")
46
+ run_generator
47
+
48
+ assert_file "app/assets/tailwind/rails_datepicker.css"
49
+ assert_file "app/assets/stylesheets/application.css" do |content|
50
+ assert_equal "body { color: black; }\n", content
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class FormHelperTest < ActionView::TestCase
6
+ include RailsDatepicker::FormHelper
7
+
8
+ test "preserves field attributes and nested names" do
9
+ student = Student.new(birth_date: Date.new(2023, 9, 12))
10
+ html = form_for(student, url: "/") do |form|
11
+ form.fields_for(:profile) do |nested|
12
+ nested.datepicker_field(:birth_date, required: true, disabled: true, class: "custom")
13
+ end
14
+ end
15
+
16
+ assert_includes html, 'name="student[profile][birth_date]"'
17
+ assert_includes html, "required=\"required\""
18
+ assert_includes html, "disabled=\"disabled\""
19
+ assert_includes html, "custom"
20
+ end
21
+
22
+ test "calendar switching data keeps canonical value" do
23
+ student = Student.new(birth_date: Date.new(2023, 9, 12))
24
+ html = rails_datepicker_field(:student, :birth_date, object: student, calendar: :ethiopian)
25
+
26
+ assert_includes html, 'value="2023-09-12"'
27
+ assert_includes html, 'value="2016-01-01"'
28
+ end
29
+
30
+ test "displays Gregorian results when choosing from the Ethiopian calendar" do
31
+ html = rails_datepicker_field(:student, :birth_date,
32
+ object: Student.new(birth_date: Date.new(2023, 9, 12)),
33
+ calendar: :ethiopian, display_calendar: :gregorian)
34
+ fragment = Nokogiri::HTML.fragment(html)
35
+ assert_equal "2023-09-12", fragment.at_css(".rdp__input")["value"]
36
+ assert_equal "gregorian", fragment.at_css(".rdp")["data-rails-datepicker-display-calendar-value"]
37
+ assert fragment.at_css("[data-action='rails-datepicker#switchLocale']")
38
+ end
39
+
40
+ test "uses the field locale and preserves custom accessibility attributes" do
41
+ student = Student.new
42
+ student.errors.add(:birth_date, "is invalid")
43
+ html = rails_datepicker_field(
44
+ :student,
45
+ :birth_date,
46
+ object: student,
47
+ locale: :am,
48
+ aria: { label: "Birth date", describedby: "birth_date_hint" },
49
+ data: { action: "focus->hint#show" }
50
+ )
51
+ fragment = Nokogiri::HTML.fragment(html)
52
+ wrapper = fragment.at_css("[data-controller='rails-datepicker']")
53
+ input = fragment.at_css("input.rdp__input")
54
+
55
+ assert_equal "am", wrapper["lang"]
56
+ assert_equal "Birth date", input["aria-label"]
57
+ assert_equal "birth_date_hint student_birth_date_error", input["aria-describedby"]
58
+ assert_includes input["data-action"], "focus->hint#show"
59
+ assert_equal "ቀን መቁጠሪያ ክፈት", fragment.at_css(".rdp__trigger")["aria-label"]
60
+ assert_equal "ዛሬ", fragment.at_css(".rdp__footer-button").text
61
+ end
62
+
63
+ test "disables every interactive control in a disabled inline picker" do
64
+ html = rails_datepicker_field(
65
+ :student,
66
+ :birth_date,
67
+ object: Student.new,
68
+ inline: true,
69
+ disabled: true
70
+ )
71
+ fragment = Nokogiri::HTML.fragment(html)
72
+ controls = fragment.css(".rdp__popover button, .rdp__popover select")
73
+
74
+ assert controls.any?
75
+ assert controls.all? { |control| control.key?("disabled") }
76
+ end
77
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class FormSubmissionTest < ActionDispatch::IntegrationTest
6
+ test "submits Gregorian ISO into an Active Record date column" do
7
+ post students_path, params: { student: { name: "Mimi", birth_date: "2023-09-12" } }
8
+
9
+ assert_response :success
10
+ assert_equal "2023-09-12", response.body
11
+ assert_equal Date.new(2023, 9, 12), Student.last.birth_date
12
+ end
13
+
14
+ test "renders builder and standalone datepickers with canonical names" do
15
+ get new_student_path
16
+
17
+ assert_response :success
18
+ assert_select "[data-controller='rails-datepicker']", count: 3
19
+ assert_select "input[type='hidden'][name='student[birth_date]'][value='2023-09-12']", count: 3
20
+ assert_select "input.rdp__input:not([name])", count: 3
21
+ end
22
+ end
@@ -0,0 +1,56 @@
1
+ import test from "node:test"
2
+ import assert from "node:assert/strict"
3
+ import {
4
+ decodeEthiopicInteger, encodeEthiopicInteger, ethiopianLeapYear,
5
+ ethiopianMonthLength, ethiopianNewYear, ethiopianToGregorian, formatDate,
6
+ gregorianLeapYear, gregorianToEthiopian, normalizeEthiopicNumerals,
7
+ parseFormatted, parseIso, toIso
8
+ } from "../../app/javascript/rails-datepicker/calendar.js"
9
+
10
+ test("converts Ethiopian New Year boundaries", () => {
11
+ assert.equal(toIso(ethiopianToGregorian(2015, 1, 1)), "2022-09-11")
12
+ assert.equal(toIso(ethiopianToGregorian(2016, 1, 1)), "2023-09-12")
13
+ assert.deepEqual(gregorianToEthiopian(parseIso("2023-09-11")), { year: 2015, month: 13, day: 6 })
14
+ assert.deepEqual(gregorianToEthiopian(parseIso("2023-09-12")), { year: 2016, month: 1, day: 1 })
15
+ })
16
+
17
+ test("remains accurate across Gregorian century boundaries", () => {
18
+ assert.equal(toIso(ethiopianNewYear(2092)), "2099-09-12")
19
+ assert.deepEqual(gregorianToEthiopian(parseIso("2099-09-11")), { year: 2091, month: 13, day: 6 })
20
+ assert.deepEqual(gregorianToEthiopian(parseIso("2099-09-12")), { year: 2092, month: 1, day: 1 })
21
+ assert.equal(toIso(ethiopianNewYear(2096)), "2103-09-13")
22
+ })
23
+
24
+ test("handles Gregorian and Ethiopian leap years", () => {
25
+ assert.equal(gregorianLeapYear(2000), true)
26
+ assert.equal(gregorianLeapYear(2100), false)
27
+ assert.equal(ethiopianLeapYear(2011), true)
28
+ assert.equal(ethiopianMonthLength(2011, 13), 6)
29
+ assert.equal(ethiopianMonthLength(2012, 13), 5)
30
+ assert.equal(toIso(ethiopianToGregorian(2011, 13, 6)), "2019-09-11")
31
+ })
32
+
33
+ test("round trips dates over a broad reference range", () => {
34
+ for (let year = 1900; year <= 2100; year += 1) {
35
+ for (const month of [1, 3, 6, 9, 12]) {
36
+ const date = parseIso(`${year}-${String(month).padStart(2, "0")}-15`)
37
+ const ethiopian = gregorianToEthiopian(date)
38
+ assert.equal(toIso(ethiopianToGregorian(ethiopian.year, ethiopian.month, ethiopian.day)), toIso(date))
39
+ }
40
+ }
41
+ })
42
+
43
+ test("strictly parses configured formats", () => {
44
+ assert.equal(toIso(parseFormatted("2016-01-01", "YYYY-MM-DD", "ethiopian")), "2023-09-12")
45
+ assert.equal(toIso(parseFormatted("31/12/2024", "DD/MM/YYYY", "gregorian")), "2024-12-31")
46
+ assert.throws(() => parseFormatted("2024-2-01", "YYYY-MM-DD", "gregorian"))
47
+ assert.throws(() => parseFormatted("2024-02-30", "YYYY-MM-DD", "gregorian"))
48
+ })
49
+
50
+ test("formats and accepts Ethiopic numerals", () => {
51
+ assert.equal(encodeEthiopicInteger(2016), "፳፻፲፮")
52
+ assert.equal(decodeEthiopicInteger("፳፻፲፮"), 2016)
53
+ assert.equal(normalizeEthiopicNumerals("፳፻፲፮-፩-፩"), "2016-1-1")
54
+ assert.equal(formatDate(parseIso("2023-09-12"), "YYYY-MM-DD", "ethiopian", true), "፳፻፲፮-፩-፩")
55
+ assert.equal(toIso(parseFormatted("፳፻፲፮-፩-፩", "YYYY-MM-DD", "ethiopian")), "2023-09-12")
56
+ })
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require_relative "../../lib/rails_datepicker/calendar"
5
+
6
+ class RailsDatepickerCalendarTest < Minitest::Test
7
+ Calendar = RailsDatepicker::Calendar
8
+
9
+ def test_ethiopian_new_year_boundaries
10
+ assert_equal Date.new(2022, 9, 11), Calendar.ethiopian_to_gregorian(2015, 1, 1)
11
+ assert_equal Date.new(2023, 9, 12), Calendar.ethiopian_to_gregorian(2016, 1, 1)
12
+ assert_equal [2015, 13, 6], Calendar.gregorian_to_ethiopian(Date.new(2023, 9, 11))
13
+ assert_equal [2016, 1, 1], Calendar.gregorian_to_ethiopian(Date.new(2023, 9, 12))
14
+ end
15
+
16
+ def test_conversion_remains_accurate_across_gregorian_century_boundaries
17
+ assert_equal Date.new(2099, 9, 12, Date::GREGORIAN), Calendar.ethiopian_new_year(2092)
18
+ assert_equal [2091, 13, 6], Calendar.gregorian_to_ethiopian(Date.new(2099, 9, 11, Date::GREGORIAN))
19
+ assert_equal [2092, 1, 1], Calendar.gregorian_to_ethiopian(Date.new(2099, 9, 12, Date::GREGORIAN))
20
+ assert_equal Date.new(2103, 9, 13, Date::GREGORIAN), Calendar.ethiopian_new_year(2096)
21
+ end
22
+
23
+ def test_pagume_and_ethiopian_leap_years
24
+ assert Calendar.ethiopian_leap_year?(2011)
25
+ refute Calendar.ethiopian_leap_year?(2012)
26
+ assert_equal 6, Calendar.ethiopian_month_length(2011, 13)
27
+ assert_equal 5, Calendar.ethiopian_month_length(2012, 13)
28
+ assert_equal Date.new(2019, 9, 11), Calendar.ethiopian_to_gregorian(2011, 13, 6)
29
+ assert_raises(RailsDatepicker::Calendar::InvalidDate) { Calendar.ethiopian_to_gregorian(2012, 13, 6) }
30
+ end
31
+
32
+ def test_gregorian_leap_year_rules
33
+ assert Calendar.gregorian_leap_year?(2000)
34
+ refute Calendar.gregorian_leap_year?(2100)
35
+ assert Calendar.gregorian_leap_year?(2024)
36
+ end
37
+
38
+ def test_round_trip_over_reference_range
39
+ (1900..2100).each do |year|
40
+ [1, 3, 6, 9, 12].each do |month|
41
+ date = Date.new(year, month, 15)
42
+ ethiopian = Calendar.gregorian_to_ethiopian(date)
43
+ assert_equal date, Calendar.ethiopian_to_gregorian(*ethiopian)
44
+ end
45
+ end
46
+ end
47
+
48
+ def test_strict_iso_parsing
49
+ assert_equal Date.new(2024, 2, 29), Calendar.parse_iso("2024-02-29")
50
+ assert_raises(RailsDatepicker::Calendar::InvalidDate) { Calendar.parse_iso("2024-2-29") }
51
+ assert_raises(RailsDatepicker::Calendar::InvalidDate) { Calendar.parse_iso("2023-02-29") }
52
+ end
53
+
54
+ def test_ethiopic_numeral_formatting
55
+ assert_equal "፳፻፲፮-፲፫-፮", Calendar.to_ethiopic_numerals("2016-13-06")
56
+ end
57
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require_relative "../../lib/rails_datepicker/configuration"
5
+
6
+ class RailsDatepickerConfigurationTest < Minitest::Test
7
+ def setup
8
+ @configuration = RailsDatepicker::Configuration.new
9
+ end
10
+
11
+ def test_defaults
12
+ assert_equal :ethiopian, @configuration.default_calendar
13
+ assert_equal :am, @configuration.default_locale
14
+ assert_equal "YYYY-MM-DD", @configuration.date_format
15
+ assert_equal :auto, @configuration.theme
16
+ assert_equal :sunday, @configuration.week_start
17
+ end
18
+
19
+ def test_accepts_supported_values
20
+ @configuration.default_calendar = "gregorian"
21
+ @configuration.default_locale = "en"
22
+ @configuration.theme = "dark"
23
+ @configuration.week_start = "monday"
24
+
25
+ assert_equal :gregorian, @configuration.default_calendar
26
+ assert_equal :en, @configuration.default_locale
27
+ assert_equal :dark, @configuration.theme
28
+ assert_equal :monday, @configuration.week_start
29
+ end
30
+
31
+ def test_rejects_unsupported_values
32
+ assert_raises(ArgumentError) { @configuration.default_calendar = :julian }
33
+ assert_raises(ArgumentError) { @configuration.default_locale = :fr }
34
+ assert_raises(ArgumentError) { @configuration.theme = :neon }
35
+ assert_raises(ArgumentError) { @configuration.week_start = :tuesday }
36
+ assert_raises(ArgumentError) { @configuration.date_format = "YYYY-MM" }
37
+ assert_raises(ArgumentError) { @configuration.date_format = "YYYY-YYYY-MM-DD" }
38
+ end
39
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+ require "simple_form"
5
+ require "rails_datepicker/simple_form_input"
6
+
7
+ class SimpleFormInputTest < ActionView::TestCase
8
+ include SimpleForm::ActionViewExtensions::FormHelper
9
+
10
+ test "Simple Form input delegates to the datepicker builder" do
11
+ student = Student.new(birth_date: Date.new(2023, 9, 12))
12
+ html = simple_form_for(student, url: "/") do |form|
13
+ form.input :birth_date, as: :rails_datepicker, calendar: :ethiopian, locale: :am
14
+ end
15
+
16
+ assert_includes html, 'data-controller="rails-datepicker"'
17
+ assert_includes html, 'name="student[birth_date]"'
18
+ assert_includes html, 'value="2016-01-01"'
19
+ end
20
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "application_system_test_case"
4
+
5
+ class DatepickerTest < ApplicationSystemTestCase
6
+ setup do
7
+ skip "set RUN_SYSTEM_TESTS=1 to run browser tests" unless ENV["RUN_SYSTEM_TESTS"] == "1"
8
+ visit datepicker_path
9
+ end
10
+
11
+ test "switches calendars without changing the canonical date" do
12
+ picker = find("[data-controller='rails-datepicker']", match: :first)
13
+ canonical = picker.find("[data-rails-datepicker-target='canonical']", visible: :all)
14
+ assert_equal "2023-09-12", canonical.value
15
+
16
+ picker.find("[data-rails-datepicker-target='trigger']").click
17
+ picker.find("[data-rails-datepicker-target='calendarToggle']").click
18
+
19
+ assert_equal "2023-09-12", canonical.value
20
+ assert_equal "2023-09-12", picker.find(".rdp__input").value
21
+ end
22
+
23
+ test "switches all calendar labels between Amharic and English" do
24
+ picker = find(".rdp", match: :first)
25
+ picker.find(".rdp__trigger").click
26
+ picker.find("[data-rails-datepicker-target='localeToggle']").click
27
+ assert_equal "en", picker["lang"]
28
+ assert_equal "Open calendar", picker.find(".rdp__trigger")["aria-label"]
29
+ assert_equal "Today", picker.find("[data-action='rails-datepicker#today']").text
30
+ assert_equal "Meskerem", picker.find("[data-rails-datepicker-target='monthSelect'] option:checked").text
31
+ picker.find("[data-rails-datepicker-target='localeToggle']").click
32
+ assert_equal "am", picker["lang"]
33
+ assert_equal "ዛሬ", picker.find("[data-action='rails-datepicker#today']").text
34
+ end
35
+
36
+ test "keeps Gregorian results after Ethiopian selection and calendar switching" do
37
+ picker = find(".rdp:has(#gregorian_result)")
38
+ picker.find(".rdp__trigger").click
39
+ width = picker.find(".rdp__popover").evaluate_script("this.getBoundingClientRect().width")
40
+ assert_operator width, :<=, 288
41
+ picker.find("[data-iso='2023-09-14']").click
42
+ assert_equal "2023-09-14", picker.find(".rdp__input").value
43
+ assert_equal "2023-09-14", picker.find("input[type='hidden']", visible: :all).value
44
+ picker.find(".rdp__trigger").click
45
+ picker.find("[data-rails-datepicker-target='calendarToggle']").click
46
+ assert_equal "2023-09-14", picker.find(".rdp__input").value
47
+ picker.find(".rdp__input").fill_in(with: "2024-02-29")
48
+ picker.find(".rdp__input").send_keys(:tab)
49
+ assert_equal "2024-02-29", picker.find("input[type='hidden']", visible: :all).value
50
+ end
51
+
52
+ test "supports independent instances and disabled dates" do
53
+ pickers = all("[data-controller='rails-datepicker']")
54
+ pickers.first.find("[data-rails-datepicker-target='trigger']").click
55
+
56
+ assert pickers.first.find("[data-iso='2023-09-13']", visible: :all).disabled?
57
+ assert pickers.first.find(".rdp__popover", visible: :visible)
58
+ assert pickers[1].find(".rdp__popover", visible: :hidden)
59
+ end
60
+
61
+ test "provides keyboard navigation and Escape focus restoration" do
62
+ trigger = find("[data-rails-datepicker-target='trigger']", match: :first)
63
+ trigger.click
64
+ day = find("[data-rdp-day]:not([disabled])", match: :first)
65
+ day.send_keys(:arrow_right)
66
+ assert_selector "[data-rdp-day]:focus"
67
+ page.send_keys(:escape)
68
+
69
+ assert_equal trigger, page.evaluate_script("document.activeElement")
70
+ end
71
+
72
+ test "reconnects once after a Turbo Frame replacement and renders dark inline mode" do
73
+ click_link "Replace frame"
74
+
75
+ within("turbo-frame#datepicker_frame") do
76
+ assert_selector ".rdp--dark.rdp--inline", count: 1
77
+ assert_selector ".rdp__popover:not([hidden])", count: 1
78
+ find(".rdp__day:not(.rdp__day--empty):not([disabled])", match: :first).click
79
+ assert_selector "input[type='hidden'][value]:not([value=''])", visible: :all, count: 1
80
+ end
81
+ end
82
+
83
+ test "strictly validates manual input and respects range restrictions" do
84
+ input = find(".rdp__input", match: :first)
85
+ input.fill_in(with: "2016-1-1")
86
+ input.send_keys(:tab)
87
+ assert_equal "true", input["aria-invalid"]
88
+
89
+ input.fill_in(with: "2016-01-01")
90
+ input.send_keys(:tab)
91
+ assert_equal "false", input["aria-invalid"]
92
+ end
93
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV["RAILS_ENV"] ||= "test"
4
+
5
+ require_relative "dummy/config/environment"
6
+ require "rails/test_help"
7
+
8
+ ActiveRecord::Migration.verbose = false
9
+ load File.expand_path("dummy/db/schema.rb", __dir__)