Skip to content

Commit

Permalink
[Main nav] Navigation column [TP-266] (#12047)
Browse files Browse the repository at this point in the history
* Add description to NavLinkBlock

* Rename NavLinkBlock to NavItem

* Add NavButtonBlock

* Fix test name

* Add a NavColumn model
  • Loading branch information
jhonatan-lopes committed Mar 28, 2024
1 parent d70e078 commit 130317e
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 22 deletions.
46 changes: 42 additions & 4 deletions network-api/networkapi/nav/blocks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from collections import OrderedDict

from wagtail import blocks
from wagtail.telepath import register

Expand All @@ -8,7 +10,7 @@
)


class NavLinkValue(BaseLinkValue):
class NavItemValue(BaseLinkValue):
@property
def open_in_new_window(self) -> bool:
link_to = self.get("link_to")
Expand All @@ -17,14 +19,50 @@ def open_in_new_window(self) -> bool:
return False


class NavLinkBlock(BaseLinkBlock):
class NavItem(BaseLinkBlock):
description = blocks.CharBlock(required=False, max_length=100)

def __init__(self, local_blocks=None, **kwargs):
# Use __init__ method to change the order of the blocks when constructing
# them through inheritance
super().__init__(local_blocks, **kwargs)
self.child_blocks = self.base_blocks.copy()
child_blocks = OrderedDict(
{
"label": self.child_blocks.pop("label"),
"description": self.child_blocks.pop("description"),
}
)
child_blocks.update({k: v for k, v in self.child_blocks.items()})
self.child_blocks = child_blocks

class Meta:
value_class = NavLinkValue
value_class = NavItemValue
label = "Navigation Link"
icon = "link"
template = "nav/blocks/nav_link_block.html"


register(BaseLinkBlockAdapter(), NavLinkBlock)
register(BaseLinkBlockAdapter(), NavItem)


class NavButton(BaseLinkBlock):
class Meta:
value_class = NavItemValue
label = "Navigation Button"
icon = "link"
template = "nav/blocks/nav_button_block.html"


register(BaseLinkBlockAdapter(), NavButton)


class NavColumn(blocks.StructBlock):
title = blocks.CharBlock(max_length=100)
links = blocks.ListBlock(NavItem, min_num=1, max_num=4)
button = blocks.ListBlock(NavButton, required=False, min_num=0, max_num=1)

class Meta:
label = "Navigation Column"
icon = "list-ul"
template = "nav/blocks/nav_column_block.html"
67 changes: 63 additions & 4 deletions network-api/networkapi/nav/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import wagtail_factories
from wagtail import models as wagtail_models

from networkapi.nav.blocks import NavLinkBlock, NavLinkValue
from networkapi.nav import blocks as nav_blocks


class NavLinkBlockFactory(wagtail_factories.StructBlockFactory):
class NavItemFactory(wagtail_factories.StructBlockFactory):
"""Factory for NavLinkBlock.
Use traits to create instances based on the type of link needed:
Expand All @@ -22,12 +22,12 @@ class NavLinkBlockFactory(wagtail_factories.StructBlockFactory):
"""

class Meta:
model = NavLinkBlock
model = nav_blocks.NavItem

@classmethod
def _construct_struct_value(cls, block_class, params):
"""Use NavLinkValue to create the StructValue instance."""
return NavLinkValue(
return nav_blocks.NavItemValue(
block_class(),
[(name, value) for name, value in params.items()],
)
Expand All @@ -51,3 +51,62 @@ class Params:
page = None
external_url = ""
relative_url = ""


class NavButtonFactory(wagtail_factories.StructBlockFactory):
"""Factory for NavButtonBlock.
Use traits to create instances based on the type of link needed:
- page_link: link to a page
- external_url_link: link to a custom external URL
- relative_url_link: link to a relative URL
Example:
```
block = NavButtonBlockFactory(page_link=True)
block = NavButtonBlockFactory(external_url_link=True)
block = NavButtonBlockFactory(relative_url_link=True)
```
"""

class Meta:
model = nav_blocks.NavButton

@classmethod
def _construct_struct_value(cls, block_class, params):
"""Use NavLinkValue to create the StructValue instance."""
return nav_blocks.NavItemValue(
block_class(),
[(name, value) for name, value in params.items()],
)

class Params:
page_link = factory.Trait(
link_to="page",
page=factory.Iterator(wagtail_models.Page.objects.filter(locale_id="1")),
)
external_url_link = factory.Trait(link_to="external_url", external_url=factory.Faker("url"))
relative_url_link = factory.Trait(link_to="relative_url", relative_url=f'/{factory.Faker("uri_path")}')

label = factory.Faker("sentence", nb_words=3)

# Setup default link as external URL (it won't pass validation without a link type defined though
# so it's still necessary to use the factory with traits)
link_to = "external_url"
# Set all link types to None by default. Only define the needed link type in the factory
# trait to avoid conflicts
page = None
external_url = ""
relative_url = ""


class NavColumnFactory(wagtail_factories.StructBlockFactory):
class Meta:
model = nav_blocks.NavColumn

class Params:
with_button = False

title = factory.Faker("sentence", nb_words=3)
links = factory.List([factory.SubFactory(NavItemFactory) for _ in range(4)])
button = factory.LazyAttribute(lambda o: [NavButtonFactory()] if o.with_button else [])
115 changes: 101 additions & 14 deletions network-api/networkapi/nav/tests/test_blocks.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
from django.test import TestCase
from wagtail.blocks import StreamBlockValidationError
from wagtail.blocks import StreamBlockValidationError, StructBlockValidationError
from wagtail.models import Locale, Page

from networkapi.nav.blocks import NavLinkBlock
from networkapi.nav.factories import NavLinkBlockFactory
from networkapi.nav import blocks as nav_blocks
from networkapi.nav import factories as nav_factories


class TestLinkBlock(TestCase):
class TestNavItemBlock(TestCase):
def test_default(self):
"""Assert that default NavLinkBlock factory works and is an external URL."""
block = NavLinkBlockFactory()
"""Assert that default nav_blocks.NavItem factory works and is an external URL."""
block = nav_factories.NavItemFactory()

# Assert that the page link is custom URL and that it is correct
url = block["external_url"]
self.assertEqual(block.url, url)

def test_page_link(self):
"""Create a NavLinkBlock with a page link."""
block = NavLinkBlockFactory(page_link=True)
"""Create a nav_blocks.NavItem with a page link."""
block = nav_factories.NavItemFactory(page_link=True)

page = block["page"]
self.assertIsNotNone(page)
Expand All @@ -32,8 +32,8 @@ def test_page_link(self):
self.assertEqual(block["relative_url"], "")

def test_external_url_link(self):
"""Create a NavLinkBlock with a custom/external URL."""
block = NavLinkBlockFactory(external_url_link=True)
"""Create a nav_blocks.NavItem with a custom/external URL."""
block = nav_factories.NavItemFactory(external_url_link=True)

# Assert that the URL is a URL
url = block["external_url"]
Expand All @@ -46,8 +46,8 @@ def test_external_url_link(self):
self.assertEqual(block["relative_url"], "")

def test_relative_url_link(self):
"""Create a NavLinkBlock with a relative URL."""
block = NavLinkBlockFactory(relative_url_link=True)
"""Create a nav_blocks.NavItem with a relative URL."""
block = nav_factories.NavItemFactory(relative_url_link=True)

# Assert that the URL is a URL
url = block["relative_url"]
Expand All @@ -61,5 +61,92 @@ def test_relative_url_link(self):

def test_needs_to_provide_at_least_one_link(self):
with self.assertRaises(StreamBlockValidationError):
block = NavLinkBlockFactory()
NavLinkBlock().clean(block)
block = nav_factories.NavItemFactory()
nav_blocks.NavItem().clean(block)


class TestNavButton(TestCase):
def test_default(self):
"""Assert that default nav_blocks.NavButton factory works and is an external URL."""
block = nav_factories.NavButtonFactory()

# Assert that the page link is custom URL and that it is correct
url = block["external_url"]
self.assertEqual(block.url, url)

def test_page_link(self):
"""Create a nav_blocks.NavButton with a page link."""
block = nav_factories.NavButtonFactory(page_link=True)

page = block["page"]
self.assertIsNotNone(page)
self.assertTrue(isinstance(page, Page))
default_locale = Locale.get_default()
self.assertEqual(page.locale, default_locale)

# Assert that other fields are empty
self.assertEqual(block["external_url"], "")
self.assertEqual(block["relative_url"], "")

def test_external_url_link(self):
"""Create a nav_blocks.NavButton with a custom/external URL."""
block = nav_factories.NavButtonFactory(external_url_link=True)

# Assert that the URL is a URL
url = block["external_url"]
self.assertIsNotNone(url)

# Assert that other fields are empty
self.assertIsNone(block["page"])
self.assertEqual(block["relative_url"], "")

def test_relative_url_link(self):
"""Create a nav_blocks.NavButton with a relative URL."""
block = nav_factories.NavButtonFactory(relative_url_link=True)

# Assert that the URL is a URL
url = block["relative_url"]
self.assertIsNotNone(url)

# Assert that other fields are empty
self.assertIsNone(block["page"])
self.assertEqual(block["external_url"], "")

def test_needs_to_provide_at_least_one_link(self):
with self.assertRaises(StreamBlockValidationError):
block = nav_factories.NavItemFactory()
nav_blocks.NavItem().clean(block)


class TestNavColumnBlock(TestCase):
def test_default(self):
"""Assert that default nav_blocks.NavColumn factory works and is an external URL."""
block = nav_factories.NavColumnFactory()

self.assertEqual(len(block["links"]), 4)
for link in block["links"]:
self.assertIsInstance(link.block, nav_blocks.NavItem)
self.assertIsInstance(link, nav_blocks.NavItemValue)
self.assertCountEqual(block["button"], [])

def test_with_button(self):
"""Create a nav_blocks.NavColumn with a button."""
block = nav_factories.NavColumnFactory(with_button=True)

self.assertEqual(len(block["button"]), 1)
self.assertIsInstance(block["button"][0].block, nav_blocks.NavButton)

def test_needs_to_provide_at_least_one_link(self):
with self.assertRaises(StructBlockValidationError):
block = nav_factories.NavColumnFactory(links=[])
nav_blocks.NavColumn().clean(block)

def test_needs_to_provide_at_most_four_links(self):
with self.assertRaises(StructBlockValidationError):
block = nav_factories.NavColumnFactory(links=[nav_factories.NavItemFactory() for _ in range(5)])
nav_blocks.NavColumn().clean(block)

def test_cannot_have_more_than_one_button(self):
with self.assertRaises(StructBlockValidationError):
block = nav_factories.NavColumnFactory(button=[nav_factories.NavButtonFactory() for _ in range(2)])
nav_blocks.NavColumn().clean(block)

0 comments on commit 130317e

Please sign in to comment.