From d98a5a93c9471eb2ab0148ceeea9d631408b091f Mon Sep 17 00:00:00 2001 From: Nikhil Dhandre Date: Fri, 25 Aug 2023 17:47:06 +0530 Subject: [PATCH] Card views --- README.md | 3 + src/widgetastic_patternfly5/__init__.py | 6 + .../components/card.py | 49 ++++++++- testing/components/test_card.py | 103 ++++++++++++++---- 4 files changed, 140 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 8085d47..9b4a674 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,9 @@ itteration of [widgetastic.patternfly4](https://github.com/RedHatQE/widgetastic. - [line-chart](https://www.patternfly.org/charts/line-chart) - [pie-chart](https://www.patternfly.org/charts/pie-chart) +### Patterns: +- [card-view](https://patternfly-react-main.surge.sh/patterns/card-view) + ### Contribution guide diff --git a/src/widgetastic_patternfly5/__init__.py b/src/widgetastic_patternfly5/__init__.py index 4da2029..e9a7975 100644 --- a/src/widgetastic_patternfly5/__init__.py +++ b/src/widgetastic_patternfly5/__init__.py @@ -8,6 +8,9 @@ from .components.breadcrumb import BreadCrumb from .components.button import Button from .components.card import Card +from .components.card import CardCheckBox +from .components.card import CardForCardGroup +from .components.card import CardGroup from .components.card import CardWithActions from .components.chip import CategoryChipGroup from .components.chip import Chip @@ -71,6 +74,9 @@ "Button", "CalendarMonth", "Card", + "CardCheckBox", + "CardForCardGroup", + "CardGroup", "CardWithActions", "CategoryChipGroup", "CheckboxMenu", diff --git a/src/widgetastic_patternfly5/components/card.py b/src/widgetastic_patternfly5/components/card.py index 0de32ca..2b72360 100644 --- a/src/widgetastic_patternfly5/components/card.py +++ b/src/widgetastic_patternfly5/components/card.py @@ -1,6 +1,8 @@ from widgetastic.utils import ParametrizedLocator from widgetastic.widget import Checkbox from widgetastic.widget import GenericLocatorWidget +from widgetastic.widget import ParametrizedView +from widgetastic.widget import View from widgetastic_patternfly5.components.menus.dropdown import Dropdown @@ -32,13 +34,58 @@ def footer(self): class Card(BaseCard, GenericLocatorWidget): + DEFAULT_LOCATOR = ( + ".//div[@data-ouia-component-type='PF5/Card'] | .//article[contains(@class, 'pf-c-card')]" + ) + def __init__(self, parent, locator=None, logger=None): - locator = locator or ".//div[contains(@class, '-c-card')]" + locator = locator or self.DEFAULT_LOCATOR super().__init__(parent, locator, logger=logger) ROOT = ParametrizedLocator("{@locator}") +class CardForCardGroup(BaseCard, ParametrizedView): + DEFAULT_LOCATOR = ( + "(.//div[@data-ouia-component-type='PF5/Card'] | .//article[contains(@class, 'pf-c-card')])" + ) + + def __init__(self, parent, locator=None, logger=None, **kwargs): + View.__init__(self, parent, logger=logger, **kwargs) + self.locator = locator or self.DEFAULT_LOCATOR + + PARAMETERS = ("position",) + + ROOT = ParametrizedLocator("{@locator}[{position}]") + + def __locator__(self): + return self.ROOT + + @classmethod + def all(cls, browser): + # todo: OUIA versions should return component ids + elements = browser.elements(cls.DEFAULT_LOCATOR) + result = [] + for index, item in enumerate(elements): + result.append((index + 1,)) + return result + + +class CardGroup(GenericLocatorWidget, View): + def __init__(self, parent, locator=None, logger=None, **kwargs): + View.__init__(self, parent, logger=logger, **kwargs) + self.locator = locator + + cards = ParametrizedView.nested(CardForCardGroup) + + def __iter__(self): + return iter(self.cards) + + class CardWithActions(Card): dropdown = Dropdown(locator=".//div[contains(@class, '-c-card__actions')]") checkbox = Checkbox(locator=".//input[contains(@class, '-c-check__input')]") + + +class CardCheckBox(Checkbox): + ROOT = ".//input[contains(@class, '-c-check__input')]" diff --git a/testing/components/test_card.py b/testing/components/test_card.py index fed69ce..0bfa623 100644 --- a/testing/components/test_card.py +++ b/testing/components/test_card.py @@ -1,34 +1,97 @@ import pytest -from widgetastic.widget import View +from widgetastic.widget import ParametrizedView +from widgetastic.widget import Text -from widgetastic_patternfly5 import Card +from widgetastic_patternfly5 import CardCheckBox +from widgetastic_patternfly5 import CardForCardGroup +from widgetastic_patternfly5 import CardGroup from widgetastic_patternfly5 import CardWithActions +from widgetastic_patternfly5 import Dropdown -TESTING_PAGE_URL = "https://patternfly-react-main.surge.sh/components/card" +TESTING_PAGE_URL = ( + "https://patternfly-react-main.surge.sh/patterns/card-view/react-demos/card-view/" +) @pytest.fixture -def view(browser): - class TestView(View): - card = Card(locator='.//div[@id="ws-react-c-card-basic-cards"]') - card_with_actions = CardWithActions( - locator='.//div[@id="ws-react-c-card-header-images-and-actions"]' - ) +def pfy_card(browser): + return CardWithActions(browser, locator='.//div[@id="PatternFly"]') - return TestView(browser) +def test_cards_displayed(pfy_card): + assert pfy_card.wait_displayed() -def test_cards_displayed(view): - assert view.card.is_displayed - assert view.card_with_actions.is_displayed +def test_card_content(pfy_card): + assert pfy_card.title == "PatternFly" + assert pfy_card.body.text == ( + "PatternFly is a community project that promotes design commonality and " + "improves user experience." + ) -def test_card_content(view): - assert view.card.title == "Title" - assert view.card.body.text == "Body" - assert view.card.footer.text == "Footer" +def test_card_actionable_items_displayed(pfy_card): + assert pfy_card.dropdown.is_displayed + assert pfy_card.checkbox.is_displayed -def test_card_actionable_items_displayed(view): - assert view.card_with_actions.dropdown.is_displayed - assert view.card_with_actions.checkbox.is_displayed + +class PageCard(CardForCardGroup): + dropdown = Dropdown(locator=".//div[contains(@class, '-c-card__actions')]") + + def delete_action(self): + self.dropdown.item_select("Delete") + + checked = CardCheckBox() + + header_text = Text(locator=".//div[contains(@class, '-c-card__title')]") + + +class Cards(CardGroup): + def __init__(self, parent, locator=None, logger=None, **kwargs): + super().__init__(parent, logger=logger, **kwargs) + self.locator = locator or './/div[contains(@class, "pf-v5-l-gallery")]' + + cards = ParametrizedView.nested(PageCard) + + +@pytest.fixture +def cards(browser): + cards = Cards(browser) + cards.wait_displayed("15s") + return cards + + +def test_read_and_drop_second_card(cards, browser): + second = [*cards][1] + + assert second.header_text.read() == "PatternFly" + + second.delete_action() + + new_second = [*cards][1] + + assert new_second.header_text.read() != "PatternFly" + # refresh to get it back :) + browser.refresh() + + +def read_cards_2_checkmap(cards): + data = cards.cards.read() + return {card["header_text"]: card["checked"] for card in list(data.values())[1:]} + + +def test_select_all_cards(browser, cards): + name2checked = read_cards_2_checkmap(cards) + assert not any(name2checked.values()) + assert all(name2checked.keys()) + + # first card doesn't have header and checkbox + for card in list(cards)[1:]: + browser.execute_script("arguments[0].scrollIntoView({block: 'center'});", card.checked) + card.checked.fill(True) + + name2checked_after = read_cards_2_checkmap(cards) + assert all(name2checked_after.values()) + assert all(name2checked_after.keys()) + + assert name2checked.keys() == name2checked_after.keys()