From 00f43f0f41f9ef04d5f4e523cc2881793faad001 Mon Sep 17 00:00:00 2001 From: LightOfHeaven1994 Date: Tue, 28 Mar 2023 17:05:25 +0200 Subject: [PATCH] Add CalendarMonth widget --- README.md | 2 + src/widgetastic_patternfly4/__init__.py | 2 + src/widgetastic_patternfly4/calendarmonth.py | 102 +++++++++++++++++++ testing/test_calendarmonth.py | 74 ++++++++++++++ 4 files changed, 180 insertions(+) create mode 100644 src/widgetastic_patternfly4/calendarmonth.py create mode 100644 testing/test_calendarmonth.py diff --git a/README.md b/README.md index c642f552..13d1fb30 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Button - Bullet Chart - +Calendar month - + Card - Chip Group - diff --git a/src/widgetastic_patternfly4/__init__.py b/src/widgetastic_patternfly4/__init__.py index e15b1bfe..10536c88 100644 --- a/src/widgetastic_patternfly4/__init__.py +++ b/src/widgetastic_patternfly4/__init__.py @@ -2,6 +2,7 @@ from .breadcrumb import BreadCrumb from .bulletchart import BulletChart from .button import Button +from .calendarmonth import CalendarMonth from .card import Card from .card import CardCheckBox from .card import CardForCardGroup @@ -64,6 +65,7 @@ "BreadCrumb", "Button", "BulletChart", + "CalendarMonth", "CheckboxMenu", "CheckboxSelect", "Chip", diff --git a/src/widgetastic_patternfly4/calendarmonth.py b/src/widgetastic_patternfly4/calendarmonth.py new file mode 100644 index 00000000..18cf53bb --- /dev/null +++ b/src/widgetastic_patternfly4/calendarmonth.py @@ -0,0 +1,102 @@ +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.common.keys import Keys +from widgetastic.utils import ParametrizedLocator +from widgetastic.widget import Widget +from widgetastic.xpath import quote + +from .select import Select + + +class DisabledDate(Exception): + pass + + +class BaseCalendarMonth: + """Represents calendar month component. + + https://www.patternfly.org/v4/components/calendar-month + """ + + CALENDAR_HEADER = ".//div[@class='pf-c-calendar-month__header']" + MONTH_SELECT_LOCATOR = ( + f"{CALENDAR_HEADER}//div[contains(@class, 'header-month')]" + f"//div[@data-ouia-component-type='PF4/Select']" + ) + _month_select_widget = Select(locator=MONTH_SELECT_LOCATOR) + YEAR_INPUT_LOCATOR = f"{CALENDAR_HEADER}//div[contains(@class, 'header-year')]/input" + DATE_LOCATOR = ".//button[text()={date}]" + + PREV_BUTTON_LOCATOR = f"{CALENDAR_HEADER}//div[contains(@class, 'prev-month')]" + NEXT_BUTTON_LOCATOR = f"{CALENDAR_HEADER}//div[contains(@class, 'next-month')]" + + TABLE = ".//table" + SELECTED_DATE_LOCATOR = f"{TABLE}/tbody//td[contains(@class, 'pf-m-selected')]" + + def prev(self): + return self.browser.click(self.PREV_BUTTON_LOCATOR) + + def next(self): + return self.browser.click(self.NEXT_BUTTON_LOCATOR) + + @property + def year(self): + el = self.browser.element(self.YEAR_INPUT_LOCATOR) + return el.get_attribute("value") + + @year.setter + def year(self, value): + el = self.browser.element(self.YEAR_INPUT_LOCATOR) + el.send_keys(Keys.CONTROL + "a") + el.send_keys(str(value) + Keys.ENTER) + + @property + def month(self): + el = self.browser.element(self.MONTH_SELECT_LOCATOR) + return self.browser.text(el) + + @month.setter + def month(self, value): + self._month_select_widget.item_select(value) + + @property + def day(self): + try: + el = self.browser.element(self.SELECTED_DATE_LOCATOR) + except NoSuchElementException: + return "" + return self.browser.text(el) + + @day.setter + def day(self, value): + el = self.browser.element(self.DATE_LOCATOR.format(date=quote(value))) + if "pf-m-disabled" in el.get_attribute("class") or el.get_attribute("disabled"): + raise DisabledDate(f"Date {value} is disabled for selection") + el.click() + + def read(self): + """Returns the currently selected date in format DD MONTH YYYY.""" + return f"{self.day} {self.month} {self.year}" + + def fill(self, items): + """Fills a Calendar with all items. + Example dictionary: {'day': '22', 'month': 'November', 'year': '2023'}" + + Args: + items: A dictionary containing what items to select + """ + if type(items) is not dict: + raise TypeError("'items' value has to be dictionary type. ") + if "day" in items: + self.day = items["day"] + if "month" in items: + self.month = items["month"] + if "year" in items: + self.year = items["year"] + + +class CalendarMonth(BaseCalendarMonth, Widget): + ROOT = ParametrizedLocator("{@locator}") + + def __init__(self, parent, locator, logger=None): + super().__init__(parent, logger=logger) + self.locator = locator diff --git a/testing/test_calendarmonth.py b/testing/test_calendarmonth.py new file mode 100644 index 00000000..f5909982 --- /dev/null +++ b/testing/test_calendarmonth.py @@ -0,0 +1,74 @@ +import calendar + +import pytest +from widgetastic.widget import View + +from widgetastic_patternfly4 import CalendarMonth + +TESTING_PAGE_URL = "https://patternfly-react.surge.sh/components/calendar-month" + +MONTHS_LIST = list(calendar.month_name[1:]) + + +@pytest.fixture +def calendar_month_view(browser): + class TestView(View): + ROOT = ".//div[@id='ws-react-c-calendar-month-selectable-date']" + calendar = CalendarMonth(locator=".//div[@class='pf-c-calendar-month']") + + return TestView(browser) + + +def test_year_selection(calendar_month_view): + assert calendar_month_view.calendar.year + calendar_month_view.calendar.year = "2023" + assert calendar_month_view.calendar.year == "2023" + + +def test_month_selection(calendar_month_view): + assert calendar_month_view.calendar.month + calendar_month_view.calendar.month = "December" + assert calendar_month_view.calendar.month == "December" + + +def test_day_selection(calendar_month_view): + calendar_month_view.calendar.day = "20" + assert calendar_month_view.calendar.day == "20" + calendar_month_view.calendar.next() + assert not calendar_month_view.calendar.day + + +def _get_proper_month_index(index): + max_index = len(MONTHS_LIST) - 1 + if index < 0: + index = max_index + elif index > max_index: + index = 0 + + return index + + +def test_month_navigation(calendar_month_view): + current_month = calendar_month_view.calendar.month + prev_month_index = _get_proper_month_index(MONTHS_LIST.index(current_month) - 1) + next_month_index = _get_proper_month_index(MONTHS_LIST.index(current_month) + 1) + + calendar_month_view.calendar.prev() + prev_month = calendar_month_view.calendar.month + assert prev_month == MONTHS_LIST[prev_month_index] + + # reset to current default month + calendar_month_view.calendar.month = current_month + + calendar_month_view.calendar.next() + next_month = calendar_month_view.calendar.month + assert next_month == MONTHS_LIST[next_month_index] + + +def test_fill_and_read(calendar_month_view): + calendar_month_view.calendar.fill({"month": "February", "year": "2023"}) + result = calendar_month_view.calendar.read() + assert result == " February 2023" + + with pytest.raises(TypeError): + calendar_month_view.calendar.fill("foo")