"""Unit tests for the DateRangePicker component family. Pins the structural contract of DateRangeField / DateRangeCalendar / DateRangePicker — segment inputs ordered by ``dateformat_hyphenated``, the hidden ISO ``{prefix}-min`` / ``{prefix}-max`` inputs that ``filter_bar.js`` serializes, the calendar's preset/footer hooks — and the PurchaseFilterBar integration that replaced the native-date DateRangeFilter for the Purchased field. """ import json import re from django.test import SimpleTestCase, TestCase from common.components import ( DateRangeCalendar, DateRangeField, DateRangePicker, PurchaseFilterBar, ) from common.time import date_parts, dateformat_hyphenated _ESCAPED_TAG_MARKERS = ["<div", "<span", "<button", "<input"] class DatePartsTest(SimpleTestCase): def test_default_format_yields_day_month_year(self): parts = date_parts() self.assertEqual([part.name for part in parts], ["day", "month", "year"]) self.assertEqual([part.placeholder for part in parts], ["DD", "MM", "YYYY"]) self.assertEqual([part.length for part in parts], [2, 2, 4]) def test_parts_follow_format_order(self): parts = date_parts("%Y-%d-%m") self.assertEqual([part.name for part in parts], ["year", "day", "month"]) def test_dateformat_hyphenated_is_parseable(self): self.assertEqual(len(date_parts(dateformat_hyphenated)), 3) class DateRangeFieldTest(SimpleTestCase): def render(self, **kwargs): defaults = {"label": "Purchased", "input_name_prefix": "filter-date-purchased"} defaults.update(kwargs) return str(DateRangeField(**defaults)) def test_renders_hidden_iso_inputs(self): html = self.render(min_value="2024-03-15", max_value="2024-09-20") self.assertIn('name="filter-date-purchased-min"', html) self.assertIn('name="filter-date-purchased-max"', html) self.assertIn('data-date-range-hidden="min"', html) self.assertIn('data-date-range-hidden="max"', html) self.assertIn('value="2024-03-15"', html) self.assertIn('value="2024-09-20"', html) def test_renders_segments_in_dateformat_order_for_both_sides(self): html = self.render() for side in ("min", "max"): side_segments = re.findall( rf'data-date-part="(\w+)" data-date-side="{side}"', html ) self.assertEqual(side_segments, ["day", "month", "year"]) def test_segment_placeholders_and_lengths(self): html = self.render() self.assertEqual(html.count('placeholder="DD"'), 2) self.assertEqual(html.count('placeholder="MM"'), 2) self.assertEqual(html.count('placeholder="YYYY"'), 2) self.assertEqual(html.count('maxlength="2"'), 4) self.assertEqual(html.count('maxlength="4"'), 2) self.assertEqual(html.count('inputmode="numeric"'), 6) def test_prefills_segments_from_iso_values(self): html = self.render(min_value="2024-03-15") self.assertIn('value="15" data-date-part="day" data-date-side="min"', html) self.assertIn('value="03" data-date-part="month" data-date-side="min"', html) self.assertIn('value="2024" data-date-part="year" data-date-side="min"', html) # The max side stays empty. self.assertIn('value="" data-date-part="day" data-date-side="max"', html) def test_malformed_iso_value_renders_empty_segments(self): html = self.render(min_value="not-a-date") self.assertIn('value="" data-date-part="day" data-date-side="min"', html) def test_renders_calendar_toggle(self): html = self.render() self.assertIn("data-date-range-calendar-toggle", html) self.assertIn('aria-label="Open Purchased calendar"', html) def test_no_native_date_inputs(self): self.assertNotIn('type="date"', self.render()) class DateRangeCalendarTest(SimpleTestCase): def render(self): return str(DateRangeCalendar(input_name_prefix="filter-date-purchased")) def test_renders_all_presets(self): html = self.render() for preset in ( "today", "yesterday", "last_7_days", "last_30_days", "this_month", "last_month", "this_year", ): self.assertIn(f'data-date-range-preset="{preset}"', html) def test_renders_footer_buttons(self): html = self.render() self.assertIn("data-date-range-cancel", html) self.assertIn("data-date-range-clear", html) self.assertIn("data-date-range-select", html) self.assertIn(">Cancel<", html) self.assertIn(">Clear<", html) self.assertIn(">Select<", html) def test_renders_grid_and_navigation_hooks(self): html = self.render() self.assertIn("data-date-range-grid", html) self.assertIn("data-date-range-month-label", html) self.assertIn("data-date-range-prev", html) self.assertIn("data-date-range-next", html) def test_starts_hidden(self): self.assertIn('class="hidden absolute', self.render()) def test_all_buttons_are_type_button(self): """No button inside the calendar may submit the surrounding filter form.""" html = self.render() button_count = html.count("