kopia lustrzana https://github.com/wagtail/wagtail
Telepath: StaticBlock
rodzic
0e45188aa8
commit
2e55191a75
|
@ -0,0 +1,46 @@
|
|||
/* global $ */
|
||||
|
||||
import { escapeHtml as h } from '../../../utils/text';
|
||||
|
||||
export class StaticBlock {
|
||||
constructor(blockDef, placeholder) {
|
||||
this.blockDef = blockDef;
|
||||
|
||||
const element = document.createElement('div');
|
||||
|
||||
if (this.blockDef.meta.html) {
|
||||
element.innerHTML = this.blockDef.meta.html;
|
||||
} else {
|
||||
element.innerHTML = h(this.blockDef.meta.text);
|
||||
}
|
||||
|
||||
placeholder.replaceWith(element);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setState(_state) {}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setError(_errorList) {}
|
||||
|
||||
getState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
focus() {}
|
||||
}
|
||||
|
||||
export class StaticBlockDefinition {
|
||||
constructor(name, meta) {
|
||||
this.name = name;
|
||||
this.meta = meta;
|
||||
}
|
||||
|
||||
render(placeholder) {
|
||||
return new StaticBlock(this, placeholder);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
|
||||
import { StaticBlockDefinition } from './StaticBlock';
|
||||
|
||||
import $ from 'jquery';
|
||||
window.$ = $;
|
||||
|
||||
describe('telepath: wagtail.blocks.StaticBlock', () => {
|
||||
let boundBlock;
|
||||
|
||||
beforeEach(() => {
|
||||
// Define a test block
|
||||
const blockDef = new StaticBlockDefinition(
|
||||
'test_field',
|
||||
{
|
||||
text: 'The admin text',
|
||||
icon: 'icon',
|
||||
label: 'The label',
|
||||
}
|
||||
);
|
||||
|
||||
// Render it
|
||||
document.body.innerHTML = '<div id="placeholder"></div>';
|
||||
boundBlock = blockDef.render($('#placeholder'));
|
||||
});
|
||||
|
||||
test('it renders correctly', () => {
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('telepath: wagtail.blocks.StaticBlock HTML escaping', () => {
|
||||
let boundBlock;
|
||||
|
||||
beforeEach(() => {
|
||||
window.somethingBad = jest.fn();
|
||||
|
||||
// Define a test block
|
||||
const blockDef = new StaticBlockDefinition(
|
||||
'test_field',
|
||||
{
|
||||
text: 'The admin text <script>somethingBad();</script>',
|
||||
icon: 'icon',
|
||||
label: 'The label',
|
||||
}
|
||||
);
|
||||
|
||||
// Render it
|
||||
document.body.innerHTML = '<div id="placeholder"></div>';
|
||||
boundBlock = blockDef.render($('#placeholder'));
|
||||
});
|
||||
|
||||
test('it renders correctly', () => {
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('javascript cant execute', () => {
|
||||
expect(window.somethingBad.mock.calls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('telepath: wagtail.blocks.StaticBlock allows safe HTML', () => {
|
||||
let boundBlock;
|
||||
|
||||
beforeEach(() => {
|
||||
window.somethingBad = jest.fn();
|
||||
|
||||
// Define a test block
|
||||
const blockDef = new StaticBlockDefinition(
|
||||
'test_field',
|
||||
{
|
||||
html: 'The admin text <script>somethingBad();</script>',
|
||||
icon: 'icon',
|
||||
label: 'The label',
|
||||
}
|
||||
);
|
||||
|
||||
// Render it
|
||||
document.body.innerHTML = '<div id="placeholder"></div>';
|
||||
boundBlock = blockDef.render($('#placeholder'));
|
||||
});
|
||||
|
||||
test('it renders correctly', () => {
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('javascript can execute', () => {
|
||||
expect(window.somethingBad.mock.calls.length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`telepath: wagtail.blocks.StaticBlock HTML escaping it renders correctly 1`] = `"<div>The admin text <script>somethingBad();</script></div>"`;
|
||||
|
||||
exports[`telepath: wagtail.blocks.StaticBlock allows safe HTML it renders correctly 1`] = `"<div>The admin text <script>somethingBad();</script></div>"`;
|
||||
|
||||
exports[`telepath: wagtail.blocks.StaticBlock it renders correctly 1`] = `"<div>The admin text</div>"`;
|
|
@ -3,6 +3,7 @@
|
|||
/* global $ */
|
||||
|
||||
import { FieldBlockDefinition } from '../../../components/StreamField/blocks/FieldBlock';
|
||||
import { StaticBlockDefinition } from '../../../components/StreamField/blocks/StaticBlock';
|
||||
import { StructBlockDefinition, StructBlockValidationError } from '../../../components/StreamField/blocks/StructBlock';
|
||||
import { ListBlockDefinition, ListBlockValidationError } from '../../../components/StreamField/blocks/ListBlock';
|
||||
import { StreamBlockDefinition, StreamBlockValidationError } from '../../../components/StreamField/blocks/StreamBlock';
|
||||
|
@ -32,6 +33,7 @@ function initBlockWidget(id) {
|
|||
window.initBlockWidget = initBlockWidget;
|
||||
|
||||
window.telepath.register('wagtail.blocks.FieldBlock', FieldBlockDefinition);
|
||||
window.telepath.register('wagtail.blocks.StaticBlock', StaticBlockDefinition);
|
||||
window.telepath.register('wagtail.blocks.StructBlock', StructBlockDefinition);
|
||||
window.telepath.register('wagtail.blocks.StructBlockValidationError', StructBlockValidationError);
|
||||
window.telepath.register('wagtail.blocks.ListBlock', ListBlockDefinition);
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
from django.utils.safestring import SafeString
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from wagtail.admin.staticfiles import versioned_static
|
||||
from wagtail.core.telepath import Adapter, register
|
||||
|
||||
from .base import Block
|
||||
|
||||
|
||||
|
@ -8,9 +14,45 @@ class StaticBlock(Block):
|
|||
"""
|
||||
A block that just 'exists' and has no fields.
|
||||
"""
|
||||
def get_admin_text(self):
|
||||
if self.meta.admin_text is None:
|
||||
if self.label:
|
||||
return _('%(label)s: this block has no options.') % {'label': self.label}
|
||||
else:
|
||||
return _('This block has no options.')
|
||||
|
||||
return self.meta.admin_text
|
||||
|
||||
def value_from_datadict(self, data, files, prefix):
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
admin_text = None
|
||||
default = None
|
||||
|
||||
|
||||
class StaticBlockAdapter(Adapter):
|
||||
js_constructor = 'wagtail.blocks.StaticBlock'
|
||||
|
||||
def js_args(self, block):
|
||||
admin_text = block.get_admin_text()
|
||||
|
||||
if isinstance(admin_text, SafeString):
|
||||
text_or_html = 'html'
|
||||
else:
|
||||
text_or_html = 'text'
|
||||
|
||||
return [
|
||||
block.name,
|
||||
{
|
||||
text_or_html: admin_text,
|
||||
'icon': block.meta.icon,
|
||||
'label': block.label,
|
||||
},
|
||||
]
|
||||
|
||||
class Media:
|
||||
js = [versioned_static('wagtailadmin/js/telepath/blocks.js')]
|
||||
|
||||
|
||||
register(StaticBlockAdapter(), StaticBlock)
|
||||
|
|
|
@ -18,6 +18,7 @@ from django.utils.translation import gettext_lazy as __
|
|||
from wagtail.core import blocks
|
||||
from wagtail.core.blocks.field_block import FieldBlockAdapter
|
||||
from wagtail.core.blocks.list_block import ListBlockAdapter
|
||||
from wagtail.core.blocks.static_block import StaticBlockAdapter
|
||||
from wagtail.core.blocks.stream_block import StreamBlockAdapter
|
||||
from wagtail.core.blocks.struct_block import StructBlockAdapter
|
||||
from wagtail.core.models import Page
|
||||
|
@ -3517,56 +3518,86 @@ class TestPageChooserBlock(TestCase):
|
|||
|
||||
|
||||
class TestStaticBlock(unittest.TestCase):
|
||||
@unittest.expectedFailure # TODO(telepath)
|
||||
def test_render_form_with_constructor(self):
|
||||
def test_adapt_with_constructor(self):
|
||||
block = blocks.StaticBlock(
|
||||
admin_text="Latest posts - This block doesn't need to be configured, it will be displayed automatically",
|
||||
template='tests/blocks/posts_static_block.html')
|
||||
rendered_html = block.render_form(None)
|
||||
|
||||
self.assertEqual(rendered_html, "Latest posts - This block doesn't need to be configured, it will be displayed automatically")
|
||||
block.set_name('posts_static_block')
|
||||
js_args = StaticBlockAdapter().js_args(block)
|
||||
|
||||
@unittest.expectedFailure # TODO(telepath)
|
||||
def test_render_form_with_subclass(self):
|
||||
self.assertEqual(js_args[0], 'posts_static_block')
|
||||
self.assertEqual(js_args[1], {
|
||||
'text': "Latest posts - This block doesn't need to be configured, it will be displayed automatically",
|
||||
'icon': 'placeholder',
|
||||
'label': 'Posts static block'
|
||||
})
|
||||
|
||||
def test_adapt_with_subclass(self):
|
||||
class PostsStaticBlock(blocks.StaticBlock):
|
||||
class Meta:
|
||||
admin_text = "Latest posts - This block doesn't need to be configured, it will be displayed automatically"
|
||||
template = "tests/blocks/posts_static_block.html"
|
||||
|
||||
block = PostsStaticBlock()
|
||||
rendered_html = block.render_form(None)
|
||||
|
||||
self.assertEqual(rendered_html, "Latest posts - This block doesn't need to be configured, it will be displayed automatically")
|
||||
block.set_name('posts_static_block')
|
||||
js_args = StaticBlockAdapter().js_args(block)
|
||||
|
||||
@unittest.expectedFailure # TODO(telepath)
|
||||
def test_render_form_with_subclass_displays_default_text_if_no_admin_text(self):
|
||||
self.assertEqual(js_args[0], 'posts_static_block')
|
||||
self.assertEqual(js_args[1], {
|
||||
'text': "Latest posts - This block doesn't need to be configured, it will be displayed automatically",
|
||||
'icon': 'placeholder',
|
||||
'label': 'Posts static block'
|
||||
})
|
||||
|
||||
def test_adapt_with_subclass_displays_default_text_if_no_admin_text(self):
|
||||
class LabelOnlyStaticBlock(blocks.StaticBlock):
|
||||
class Meta:
|
||||
label = "Latest posts"
|
||||
|
||||
block = LabelOnlyStaticBlock()
|
||||
rendered_html = block.render_form(None)
|
||||
|
||||
self.assertEqual(rendered_html, "Latest posts: this block has no options.")
|
||||
block.set_name('posts_static_block')
|
||||
js_args = StaticBlockAdapter().js_args(block)
|
||||
|
||||
@unittest.expectedFailure # TODO(telepath)
|
||||
def test_render_form_with_subclass_displays_default_text_if_no_admin_text_and_no_label(self):
|
||||
self.assertEqual(js_args[0], 'posts_static_block')
|
||||
self.assertEqual(js_args[1], {
|
||||
'text': "Latest posts: this block has no options.",
|
||||
'icon': 'placeholder',
|
||||
'label': 'Latest posts'
|
||||
})
|
||||
|
||||
def test_adapt_with_subclass_displays_default_text_if_no_admin_text_and_no_label(self):
|
||||
class NoMetaStaticBlock(blocks.StaticBlock):
|
||||
pass
|
||||
|
||||
block = NoMetaStaticBlock()
|
||||
rendered_html = block.render_form(None)
|
||||
|
||||
self.assertEqual(rendered_html, "This block has no options.")
|
||||
block.set_name('posts_static_block')
|
||||
js_args = StaticBlockAdapter().js_args(block)
|
||||
|
||||
@unittest.expectedFailure # TODO(telepath)
|
||||
def test_render_form_works_with_mark_safe(self):
|
||||
self.assertEqual(js_args[0], 'posts_static_block')
|
||||
self.assertEqual(js_args[1], {
|
||||
'text': "Posts static block: this block has no options.",
|
||||
'icon': 'placeholder',
|
||||
'label': 'Posts static block'
|
||||
})
|
||||
|
||||
def test_adapt_works_with_mark_safe(self):
|
||||
block = blocks.StaticBlock(
|
||||
admin_text=mark_safe("<b>Latest posts</b> - This block doesn't need to be configured, it will be displayed automatically"),
|
||||
template='tests/blocks/posts_static_block.html')
|
||||
rendered_html = block.render_form(None)
|
||||
|
||||
self.assertEqual(rendered_html, "<b>Latest posts</b> - This block doesn't need to be configured, it will be displayed automatically")
|
||||
block.set_name('posts_static_block')
|
||||
js_args = StaticBlockAdapter().js_args(block)
|
||||
|
||||
self.assertEqual(js_args[0], 'posts_static_block')
|
||||
self.assertEqual(js_args[1], {
|
||||
'html': "<b>Latest posts</b> - This block doesn't need to be configured, it will be displayed automatically",
|
||||
'icon': 'placeholder',
|
||||
'label': 'Posts static block'
|
||||
})
|
||||
|
||||
def test_get_default(self):
|
||||
block = blocks.StaticBlock()
|
||||
|
|
Ładowanie…
Reference in New Issue