feat: add basic theme system
This is intended to allow override of look/feel without overriding the logic/structure of templates. In practice the main goal internally is to allow testing of Vue 3 + Oruga, to eventually replace Vue 2 + Buefy as the default theme.
This commit is contained in:
parent
749aca560a
commit
796e793547
20 changed files with 1604 additions and 52 deletions
|
@ -11,7 +11,7 @@ from pyramid import testing
|
|||
|
||||
from wuttjamaican.conf import WuttaConfig
|
||||
from wuttaweb.forms import base, widgets
|
||||
from wuttaweb import helpers
|
||||
from wuttaweb import helpers, subscribers
|
||||
from wuttaweb.grids import Grid
|
||||
|
||||
|
||||
|
@ -25,10 +25,14 @@ class TestForm(TestCase):
|
|||
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
|
||||
|
||||
self.pyramid_config = testing.setUp(request=self.request, settings={
|
||||
'wutta_config': self.config,
|
||||
'mako.directories': ['wuttaweb:templates'],
|
||||
'pyramid_deform.template_search_path': 'wuttaweb:templates/deform',
|
||||
})
|
||||
|
||||
event = MagicMock(request=self.request)
|
||||
subscribers.new_request(event)
|
||||
|
||||
def tearDown(self):
|
||||
testing.tearDown()
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from unittest import TestCase
|
||||
from unittest.mock import patch
|
||||
|
||||
from wuttjamaican.testing import FileTestCase, ConfigTestCase
|
||||
from wuttjamaican.testing import FileTestCase, ConfigTestCase, DataTestCase
|
||||
|
||||
from asgiref.wsgi import WsgiToAsgi
|
||||
from pyramid.config import Configurator
|
||||
|
@ -11,6 +11,8 @@ from pyramid.router import Router
|
|||
|
||||
from wuttaweb import app as mod
|
||||
from wuttjamaican.conf import WuttaConfig
|
||||
from wuttjamaican.app import AppHandler
|
||||
from wuttjamaican.util import resource_path
|
||||
|
||||
|
||||
class TestWebAppProvider(TestCase):
|
||||
|
@ -45,22 +47,25 @@ class TestMakeWuttaConfig(FileTestCase):
|
|||
self.assertIs(settings['wutta_config'], config)
|
||||
|
||||
|
||||
class TestMakePyramidConfig(TestCase):
|
||||
class TestMakePyramidConfig(DataTestCase):
|
||||
|
||||
def test_basic(self):
|
||||
settings = {}
|
||||
config = mod.make_pyramid_config(settings)
|
||||
self.assertIsInstance(config, Configurator)
|
||||
with patch.object(AppHandler, 'make_session', return_value=self.session):
|
||||
settings = {'wutta_config': self.config}
|
||||
config = mod.make_pyramid_config(settings)
|
||||
self.assertIsInstance(config, Configurator)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
|
||||
|
||||
class TestMain(FileTestCase):
|
||||
class TestMain(DataTestCase):
|
||||
|
||||
def test_basic(self):
|
||||
global_config = None
|
||||
myconf = self.write_file('my.conf', '')
|
||||
settings = {'wutta.config': myconf}
|
||||
app = mod.main(global_config, **settings)
|
||||
self.assertIsInstance(app, Router)
|
||||
with patch.object(AppHandler, 'make_session', return_value=self.session):
|
||||
global_config = None
|
||||
myconf = self.write_file('my.conf', '')
|
||||
settings = {'wutta.config': myconf}
|
||||
app = mod.main(global_config, **settings)
|
||||
self.assertIsInstance(app, Router)
|
||||
|
||||
|
||||
def mock_main(global_config, **settings):
|
||||
|
@ -75,19 +80,20 @@ def mock_main(global_config, **settings):
|
|||
return pyramid_config.make_wsgi_app()
|
||||
|
||||
|
||||
class TestMakeWsgiApp(ConfigTestCase):
|
||||
class TestMakeWsgiApp(DataTestCase):
|
||||
|
||||
def test_with_callable(self):
|
||||
with patch.object(self.app, 'make_session', return_value=self.session):
|
||||
|
||||
# specify config
|
||||
wsgi = mod.make_wsgi_app(mock_main, config=self.config)
|
||||
self.assertIsInstance(wsgi, Router)
|
||||
|
||||
# auto config
|
||||
with patch.object(mod, 'make_config', return_value=self.config):
|
||||
wsgi = mod.make_wsgi_app(mock_main)
|
||||
# specify config
|
||||
wsgi = mod.make_wsgi_app(mock_main, config=self.config)
|
||||
self.assertIsInstance(wsgi, Router)
|
||||
|
||||
# auto config
|
||||
with patch.object(mod, 'make_config', return_value=self.config):
|
||||
wsgi = mod.make_wsgi_app(mock_main)
|
||||
self.assertIsInstance(wsgi, Router)
|
||||
|
||||
def test_with_spec(self):
|
||||
|
||||
# specify config
|
||||
|
@ -103,29 +109,87 @@ class TestMakeWsgiApp(ConfigTestCase):
|
|||
self.assertRaises(ValueError, mod.make_wsgi_app, 42, config=self.config)
|
||||
|
||||
|
||||
class TestMakeAsgiApp(ConfigTestCase):
|
||||
class TestMakeAsgiApp(DataTestCase):
|
||||
|
||||
def test_with_callable(self):
|
||||
with patch.object(self.app, 'make_session', return_value=self.session):
|
||||
|
||||
# specify config
|
||||
asgi = mod.make_asgi_app(mock_main, config=self.config)
|
||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||
|
||||
# auto config
|
||||
with patch.object(mod, 'make_config', return_value=self.config):
|
||||
asgi = mod.make_asgi_app(mock_main)
|
||||
# specify config
|
||||
asgi = mod.make_asgi_app(mock_main, config=self.config)
|
||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||
|
||||
# auto config
|
||||
with patch.object(mod, 'make_config', return_value=self.config):
|
||||
asgi = mod.make_asgi_app(mock_main)
|
||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||
|
||||
def test_with_spec(self):
|
||||
with patch.object(self.app, 'make_session', return_value=self.session):
|
||||
|
||||
# specify config
|
||||
asgi = mod.make_asgi_app('tests.test_app:mock_main', config=self.config)
|
||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||
|
||||
# auto config
|
||||
with patch.object(mod, 'make_config', return_value=self.config):
|
||||
asgi = mod.make_asgi_app('tests.test_app:mock_main')
|
||||
# specify config
|
||||
asgi = mod.make_asgi_app('tests.test_app:mock_main', config=self.config)
|
||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||
|
||||
# auto config
|
||||
with patch.object(mod, 'make_config', return_value=self.config):
|
||||
asgi = mod.make_asgi_app('tests.test_app:mock_main')
|
||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||
|
||||
def test_invalid(self):
|
||||
self.assertRaises(ValueError, mod.make_asgi_app, 42, config=self.config)
|
||||
|
||||
|
||||
class TestEstablishTheme(DataTestCase):
|
||||
|
||||
def test_default(self):
|
||||
settings = {
|
||||
'wutta_config': self.config,
|
||||
'mako.directories': ['wuttaweb:templates'],
|
||||
}
|
||||
mod.establish_theme(settings)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
self.assertEqual(settings['mako.directories'], [
|
||||
resource_path('wuttaweb:templates/themes/default'),
|
||||
'wuttaweb:templates',
|
||||
])
|
||||
|
||||
def test_mako_dirs_as_string(self):
|
||||
settings = {
|
||||
'wutta_config': self.config,
|
||||
'mako.directories': 'wuttaweb:templates',
|
||||
}
|
||||
mod.establish_theme(settings)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
self.assertEqual(settings['mako.directories'], [
|
||||
resource_path('wuttaweb:templates/themes/default'),
|
||||
'wuttaweb:templates',
|
||||
])
|
||||
|
||||
def test_butterfly(self):
|
||||
settings = {
|
||||
'wutta_config': self.config,
|
||||
'mako.directories': 'wuttaweb:templates',
|
||||
}
|
||||
self.app.save_setting(self.session, 'wuttaweb.theme', 'butterfly')
|
||||
self.session.commit()
|
||||
mod.establish_theme(settings)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'butterfly')
|
||||
self.assertEqual(settings['mako.directories'], [
|
||||
resource_path('wuttaweb:templates/themes/butterfly'),
|
||||
'wuttaweb:templates',
|
||||
])
|
||||
|
||||
def test_custom(self):
|
||||
settings = {
|
||||
'wutta_config': self.config,
|
||||
'mako.directories': 'wuttaweb:templates',
|
||||
}
|
||||
self.config.setdefault('wuttaweb.themes.keys', 'anotherone')
|
||||
self.app.save_setting(self.session, 'wuttaweb.theme', 'anotherone')
|
||||
self.session.commit()
|
||||
mod.establish_theme(settings)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'anotherone')
|
||||
self.assertEqual(settings['mako.directories'], [
|
||||
resource_path('wuttaweb:templates/themes/anotherone'),
|
||||
'wuttaweb:templates',
|
||||
])
|
||||
|
|
|
@ -41,13 +41,21 @@ class TestNewRequest(TestCase):
|
|||
self.assertIs(self.request.wutta_config, self.config)
|
||||
|
||||
def test_use_oruga_default(self):
|
||||
event = MagicMock(request=self.request)
|
||||
|
||||
# request gets a new attr, false by default
|
||||
self.assertFalse(hasattr(self.request, 'use_oruga'))
|
||||
event = MagicMock(request=self.request)
|
||||
subscribers.new_request(event)
|
||||
self.assertFalse(self.request.use_oruga)
|
||||
|
||||
# nb. using 'butterfly' theme should cause the 'use_oruga'
|
||||
# flag to be turned on by default
|
||||
self.request = self.make_request()
|
||||
self.request.registry.settings['wuttaweb.theme'] = 'butterfly'
|
||||
event = MagicMock(request=self.request)
|
||||
subscribers.new_request(event)
|
||||
self.assertTrue(self.request.use_oruga)
|
||||
|
||||
def test_use_oruga_custom(self):
|
||||
self.config.setdefault('wuttaweb.oruga_detector.spec', 'tests.test_subscribers:custom_oruga_detector')
|
||||
event = MagicMock(request=self.request)
|
||||
|
@ -57,6 +65,26 @@ class TestNewRequest(TestCase):
|
|||
subscribers.new_request(event)
|
||||
self.assertTrue(self.request.use_oruga)
|
||||
|
||||
def test_register_component(self):
|
||||
event = MagicMock(request=self.request)
|
||||
subscribers.new_request(event)
|
||||
|
||||
# component tracking dict is missing at first
|
||||
self.assertFalse(hasattr(self.request, '_wuttaweb_registered_components'))
|
||||
|
||||
# registering a component
|
||||
self.request.register_component('foo-example', 'FooExample')
|
||||
self.assertTrue(hasattr(self.request, '_wuttaweb_registered_components'))
|
||||
self.assertEqual(len(self.request._wuttaweb_registered_components), 1)
|
||||
self.assertIn('foo-example', self.request._wuttaweb_registered_components)
|
||||
self.assertEqual(self.request._wuttaweb_registered_components['foo-example'], 'FooExample')
|
||||
|
||||
# re-registering same name
|
||||
self.request.register_component('foo-example', 'FooExample')
|
||||
self.assertEqual(len(self.request._wuttaweb_registered_components), 1)
|
||||
self.assertIn('foo-example', self.request._wuttaweb_registered_components)
|
||||
self.assertEqual(self.request._wuttaweb_registered_components['foo-example'], 'FooExample')
|
||||
|
||||
def test_get_referrer(self):
|
||||
event = MagicMock(request=self.request)
|
||||
|
||||
|
@ -346,7 +374,7 @@ class TestBeforeRender(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig(defaults={
|
||||
'wutta.web.menus.handler_spec': 'tests.util:NullMenuHandler',
|
||||
'wutta.web.menus.handler.spec': 'tests.util:NullMenuHandler',
|
||||
})
|
||||
|
||||
def make_request(self):
|
||||
|
@ -378,6 +406,24 @@ class TestBeforeRender(TestCase):
|
|||
self.assertIn('json', event)
|
||||
self.assertIs(event['json'], json)
|
||||
|
||||
# current theme should be 'default' and picker not exposed
|
||||
self.assertEqual(event['theme'], 'default')
|
||||
self.assertFalse(event['expose_theme_picker'])
|
||||
self.assertNotIn('available_themes', event)
|
||||
|
||||
def test_custom_theme(self):
|
||||
self.config.setdefault('wuttaweb.themes.expose_picker', 'true')
|
||||
request = self.make_request()
|
||||
request.registry.settings['wuttaweb.theme'] = 'butterfly'
|
||||
event = {'request': request}
|
||||
|
||||
# event dict will get populated with more context
|
||||
subscribers.before_render(event)
|
||||
self.assertEqual(event['theme'], 'butterfly')
|
||||
self.assertTrue(event['expose_theme_picker'])
|
||||
self.assertIn('available_themes', event)
|
||||
self.assertEqual(event['available_themes'], ['default', 'butterfly'])
|
||||
|
||||
|
||||
class TestIncludeMe(TestCase):
|
||||
|
||||
|
|
|
@ -11,9 +11,12 @@ from fanstatic import Library, Resource
|
|||
from pyramid import testing
|
||||
|
||||
from wuttjamaican.conf import WuttaConfig
|
||||
from wuttjamaican.testing import ConfigTestCase
|
||||
from wuttjamaican.testing import ConfigTestCase, DataTestCase
|
||||
from wuttjamaican.util import resource_path
|
||||
|
||||
from wuttaweb import util as mod
|
||||
from wuttaweb.app import establish_theme
|
||||
from wuttaweb.testing import WebTestCase
|
||||
|
||||
|
||||
class TestFieldList(TestCase):
|
||||
|
@ -621,3 +624,89 @@ class TestMakeJsonSafe(TestCase):
|
|||
'bar',
|
||||
"Betty Boop",
|
||||
])
|
||||
|
||||
|
||||
class TestGetAvailableThemes(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig()
|
||||
self.app = self.config.get_app()
|
||||
|
||||
def test_defaults(self):
|
||||
themes = mod.get_available_themes(self.config)
|
||||
self.assertEqual(themes, ['default', 'butterfly'])
|
||||
|
||||
def test_sorting(self):
|
||||
self.config.setdefault('wuttaweb.themes.keys', 'default, foo2, foo4, foo1')
|
||||
themes = mod.get_available_themes(self.config)
|
||||
self.assertEqual(themes, ['default', 'foo1', 'foo2', 'foo4'])
|
||||
|
||||
def test_default_omitted(self):
|
||||
self.config.setdefault('wuttaweb.themes.keys', 'butterfly, foo')
|
||||
themes = mod.get_available_themes(self.config)
|
||||
self.assertEqual(themes, ['default', 'butterfly', 'foo'])
|
||||
|
||||
def test_default_notfirst(self):
|
||||
self.config.setdefault('wuttaweb.themes.keys', 'butterfly, foo, default')
|
||||
themes = mod.get_available_themes(self.config)
|
||||
self.assertEqual(themes, ['default', 'butterfly', 'foo'])
|
||||
|
||||
|
||||
class TestGetEffectiveTheme(DataTestCase):
|
||||
|
||||
def test_default(self):
|
||||
theme = mod.get_effective_theme(self.config)
|
||||
self.assertEqual(theme, 'default')
|
||||
|
||||
def test_override_config(self):
|
||||
self.app.save_setting(self.session, 'wuttaweb.theme', 'butterfly')
|
||||
self.session.commit()
|
||||
theme = mod.get_effective_theme(self.config)
|
||||
self.assertEqual(theme, 'butterfly')
|
||||
|
||||
def test_override_param(self):
|
||||
theme = mod.get_effective_theme(self.config, theme='butterfly')
|
||||
self.assertEqual(theme, 'butterfly')
|
||||
|
||||
def test_invalid(self):
|
||||
self.assertRaises(ValueError, mod.get_effective_theme, self.config, theme='invalid')
|
||||
|
||||
|
||||
class TestThemeTemplatePath(DataTestCase):
|
||||
|
||||
def test_default(self):
|
||||
path = mod.get_theme_template_path(self.config, theme='default')
|
||||
# nb. even though the path does not exist, we still want to
|
||||
# pretend like it does, hence prev call should return this:
|
||||
expected = resource_path('wuttaweb:templates/themes/default')
|
||||
self.assertEqual(path, expected)
|
||||
|
||||
def test_default(self):
|
||||
path = mod.get_theme_template_path(self.config, theme='butterfly')
|
||||
expected = resource_path('wuttaweb:templates/themes/butterfly')
|
||||
self.assertEqual(path, expected)
|
||||
|
||||
def test_custom(self):
|
||||
self.config.setdefault('wuttaweb.themes.keys', 'default, butterfly, poser')
|
||||
self.config.setdefault('wuttaweb.theme.poser', '/tmp/poser-theme')
|
||||
path = mod.get_theme_template_path(self.config, theme='poser')
|
||||
self.assertEqual(path, '/tmp/poser-theme')
|
||||
|
||||
|
||||
class TestSetAppTheme(WebTestCase):
|
||||
|
||||
def test_basic(self):
|
||||
|
||||
# establish default
|
||||
settings = self.request.registry.settings
|
||||
self.assertNotIn('wuttaweb.theme', settings)
|
||||
establish_theme(settings)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
|
||||
# set to butterfly
|
||||
mod.set_app_theme(self.request, 'butterfly', session=self.session)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'butterfly')
|
||||
|
||||
# set back to default
|
||||
mod.set_app_theme(self.request, 'default', session=self.session)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
|
|
|
@ -6,6 +6,7 @@ import colander
|
|||
|
||||
from wuttaweb.views import common as mod
|
||||
from wuttaweb.testing import WebTestCase
|
||||
from wuttaweb.app import establish_theme
|
||||
|
||||
|
||||
class TestCommonView(WebTestCase):
|
||||
|
@ -180,3 +181,32 @@ class TestCommonView(WebTestCase):
|
|||
self.assertEqual(person.first_name, "Barney")
|
||||
self.assertEqual(person.last_name, "Rubble")
|
||||
self.assertEqual(person.full_name, "Barney Rubble")
|
||||
|
||||
def test_change_theme(self):
|
||||
self.pyramid_config.add_route('home', '/')
|
||||
settings = self.request.registry.settings
|
||||
establish_theme(settings)
|
||||
view = self.make_view()
|
||||
|
||||
# theme is not changed if not provided by caller
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
with patch.object(mod, 'set_app_theme') as set_app_theme:
|
||||
view.change_theme()
|
||||
set_app_theme.assert_not_called()
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
|
||||
# but theme will change if provided
|
||||
with patch.object(self.request, 'params', new={'theme': 'butterfly'}):
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
view.change_theme()
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'butterfly')
|
||||
|
||||
# flash error if invalid theme is provided
|
||||
self.assertFalse(self.request.session.peek_flash('error'))
|
||||
with patch.object(self.request, 'params', new={'theme': 'anotherone'}):
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
view.change_theme()
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'butterfly')
|
||||
self.assertTrue(self.request.session.peek_flash('error'))
|
||||
messages = self.request.session.pop_flash('error')
|
||||
self.assertIn('Failed to set theme', messages[0])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue