kopia lustrzana https://github.com/wagtail/wagtail
Allow StructBlocks to be collapsible
rodzic
885d0a8e47
commit
b548bf88bc
|
@ -9,6 +9,7 @@ interface PanelProps {
|
|||
};
|
||||
blockTypeIcon: string;
|
||||
blockTypeLabel: string;
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,11 +31,12 @@ export class CollapsiblePanel {
|
|||
blockDef,
|
||||
blockTypeIcon,
|
||||
blockTypeLabel,
|
||||
collapsed,
|
||||
} = this.props;
|
||||
|
||||
// Keep in sync with wagtailadmin/shared/panel.html
|
||||
template.innerHTML = /* html */ `
|
||||
<section class="w-panel w-panel--nested" id="${panelId}" aria-labelledby="${headingId}" data-panel>
|
||||
<section class="w-panel w-panel--nested${collapsed ? ' collapsed' : ''}" id="${panelId}" aria-labelledby="${headingId}" data-panel>
|
||||
<div class="w-panel__header">
|
||||
<a class="w-panel__anchor w-panel__anchor--prefix" href="#${panelId}" aria-labelledby="${headingId}" data-panel-anchor>
|
||||
<svg class="icon icon-link w-panel__icon" aria-hidden="true">
|
||||
|
|
|
@ -6,6 +6,8 @@ import {
|
|||
addErrorMessages,
|
||||
removeErrorMessages,
|
||||
} from '../../../includes/streamFieldErrors';
|
||||
import { CollapsiblePanel } from './CollapsiblePanel';
|
||||
import { initCollapsiblePanel } from '../../../includes/panels';
|
||||
|
||||
export class StructBlock {
|
||||
constructor(blockDef, placeholder, prefix, initialState, initialError) {
|
||||
|
@ -34,12 +36,32 @@ export class StructBlock {
|
|||
});
|
||||
this.container = dom;
|
||||
} else {
|
||||
const dom = $(`
|
||||
let container = '';
|
||||
// null or undefined means not collapsible
|
||||
const collapsible = blockDef.meta.collapsed != null;
|
||||
if (collapsible) {
|
||||
container = new CollapsiblePanel({
|
||||
panelId: prefix + '-section',
|
||||
headingId: prefix + '-heading',
|
||||
contentId: prefix + '-content',
|
||||
blockTypeIcon: h(blockDef.meta.icon),
|
||||
blockTypeLabel: h(blockDef.meta.label),
|
||||
collapsed: blockDef.meta.collapsed,
|
||||
}).render().outerHTML;
|
||||
}
|
||||
|
||||
let dom = $(`
|
||||
<div class="${h(this.blockDef.meta.classname || '')}">
|
||||
</div>
|
||||
`);
|
||||
dom.append(container);
|
||||
$(placeholder).replaceWith(dom);
|
||||
|
||||
if (collapsible) {
|
||||
initCollapsiblePanel(dom.find('[data-panel-toggle]')[0]);
|
||||
dom = dom.find(`#${prefix}-content`);
|
||||
}
|
||||
|
||||
if (this.blockDef.meta.helpText) {
|
||||
// help text is left unescaped as per Django conventions
|
||||
dom.append(`
|
||||
|
@ -52,13 +74,26 @@ export class StructBlock {
|
|||
}
|
||||
|
||||
this.blockDef.childBlockDefs.forEach((childBlockDef) => {
|
||||
const childDom = $(`
|
||||
<div data-contentpath="${childBlockDef.name}">
|
||||
<label class="w-field__label">${h(childBlockDef.meta.label)}${
|
||||
const isCollapsibleStructBlock =
|
||||
// Cannot use `instanceof StructBlockDefinition` here as it is defined
|
||||
// later in this file. Compare our own blockDef constructor instead.
|
||||
childBlockDef instanceof this.blockDef.constructor &&
|
||||
childBlockDef.meta.collapsed != null;
|
||||
|
||||
// Collapsible struct blocks have their own header, so only add the label
|
||||
// if this is not a collapsible struct block.
|
||||
let label = '';
|
||||
if (!isCollapsibleStructBlock) {
|
||||
label = `<label class="w-field__label">${h(childBlockDef.meta.label)}${
|
||||
childBlockDef.meta.required
|
||||
? '<span class="w-required-mark">*</span>'
|
||||
: ''
|
||||
}</label>
|
||||
}</label>`;
|
||||
}
|
||||
|
||||
const childDom = $(`
|
||||
<div data-contentpath="${childBlockDef.name}">
|
||||
${label}
|
||||
<div data-streamfield-block></div>
|
||||
</div>
|
||||
`);
|
||||
|
|
|
@ -184,6 +184,370 @@ describe('telepath: wagtail.blocks.StructBlock', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('telepath: wagtail.blocks.StructBlock with collapsible panel', () => {
|
||||
let boundBlock;
|
||||
|
||||
const setup = (collapsed = true) => {
|
||||
// Define a test block
|
||||
const blockDef = new StructBlockDefinition(
|
||||
'settings_block',
|
||||
[
|
||||
new FieldBlockDefinition(
|
||||
'accent_color',
|
||||
new DummyWidgetDefinition('Accent color widget'),
|
||||
{
|
||||
label: 'Accent color',
|
||||
required: false,
|
||||
icon: 'placeholder',
|
||||
classname: 'w-field w-field--char_field w-field--text_input',
|
||||
},
|
||||
),
|
||||
new FieldBlockDefinition(
|
||||
'font_size',
|
||||
new DummyWidgetDefinition('Font size widget'),
|
||||
{
|
||||
label: 'Font size',
|
||||
required: false,
|
||||
icon: 'placeholder',
|
||||
classname: 'w-field w-field--choice_field w-field--select',
|
||||
},
|
||||
),
|
||||
],
|
||||
{
|
||||
label: 'Settings block',
|
||||
required: false,
|
||||
icon: 'title',
|
||||
classname: 'struct-block',
|
||||
helpText: 'configure how the block is <strong>displayed</strong>',
|
||||
helpIcon: '<svg></svg>',
|
||||
collapsed,
|
||||
},
|
||||
);
|
||||
|
||||
// Render it
|
||||
document.body.innerHTML = '<div id="placeholder"></div>';
|
||||
boundBlock = blockDef.render($('#placeholder'), 'the-prefix', {
|
||||
accent_color: 'Test accent color',
|
||||
font_size: '16px',
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Create mocks for callbacks
|
||||
constructor = jest.fn();
|
||||
setState = jest.fn();
|
||||
getState = jest.fn();
|
||||
getValue = jest.fn();
|
||||
focus = jest.fn();
|
||||
|
||||
setup();
|
||||
});
|
||||
|
||||
test('it renders correctly with initially collapsed state', () => {
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
|
||||
// Check that the panel can be expanded by clicking the toggle button
|
||||
const button = document.querySelector('[data-panel-toggle]');
|
||||
expect(button).toBeTruthy();
|
||||
button.click();
|
||||
|
||||
// Check that the panel is now expanded
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it renders correctly with initially expanded state', () => {
|
||||
// Setup with initially expanded state (collapsed = False)
|
||||
setup(false);
|
||||
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
|
||||
// Check that the panel can be expanded by clicking the toggle button
|
||||
const button = document.querySelector('[data-panel-toggle]');
|
||||
expect(button).toBeTruthy();
|
||||
button.click();
|
||||
|
||||
// Check that the panel is now expanded
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Widget constructors are called with correct parameters', () => {
|
||||
expect(constructor.mock.calls.length).toBe(2);
|
||||
|
||||
expect(constructor.mock.calls[0][0]).toBe('Accent color widget');
|
||||
expect(constructor.mock.calls[0][1]).toEqual({
|
||||
name: 'the-prefix-accent_color',
|
||||
id: 'the-prefix-accent_color',
|
||||
initialState: 'Test accent color',
|
||||
});
|
||||
|
||||
expect(constructor.mock.calls[1][0]).toBe('Font size widget');
|
||||
expect(constructor.mock.calls[1][1]).toEqual({
|
||||
name: 'the-prefix-font_size',
|
||||
id: 'the-prefix-font_size',
|
||||
initialState: '16px',
|
||||
});
|
||||
});
|
||||
|
||||
test('getValue() calls getValue() on all widgets', () => {
|
||||
const value = boundBlock.getValue();
|
||||
expect(getValue.mock.calls.length).toBe(2);
|
||||
expect(value).toEqual({
|
||||
accent_color: 'value: Accent color widget - the-prefix-accent_color',
|
||||
font_size: 'value: Font size widget - the-prefix-font_size',
|
||||
});
|
||||
});
|
||||
|
||||
test('getState() calls getState() on all widgets', () => {
|
||||
const state = boundBlock.getState();
|
||||
expect(getState.mock.calls.length).toBe(2);
|
||||
expect(state).toEqual({
|
||||
accent_color: 'state: Accent color widget - the-prefix-accent_color',
|
||||
font_size: 'state: Font size widget - the-prefix-font_size',
|
||||
});
|
||||
});
|
||||
|
||||
test('setState() calls setState() on all widgets', () => {
|
||||
boundBlock.setState({
|
||||
accent_color: 'Changed accent color',
|
||||
font_size: '456',
|
||||
});
|
||||
expect(setState.mock.calls.length).toBe(2);
|
||||
expect(setState.mock.calls[0][0]).toBe('Accent color widget');
|
||||
expect(setState.mock.calls[0][1]).toBe('Changed accent color');
|
||||
expect(setState.mock.calls[1][0]).toBe('Font size widget');
|
||||
expect(setState.mock.calls[1][1]).toBe('456');
|
||||
});
|
||||
|
||||
test('focus() calls focus() on first widget', () => {
|
||||
boundBlock.focus();
|
||||
expect(focus.mock.calls.length).toBe(1);
|
||||
expect(focus.mock.calls[0][0]).toBe('Accent color widget');
|
||||
});
|
||||
|
||||
test('getTextLabel() returns text label of first widget', () => {
|
||||
expect(boundBlock.getTextLabel()).toBe('label: the-prefix-accent_color');
|
||||
});
|
||||
|
||||
test('setError passes error messages to children', () => {
|
||||
boundBlock.setError({
|
||||
blockErrors: {
|
||||
font_size: { messages: ['This is too big.'] },
|
||||
},
|
||||
});
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('setError shows non-block errors', () => {
|
||||
boundBlock.setError({
|
||||
messages: ['This is just generally wrong.'],
|
||||
});
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('telepath: wagtail.blocks.StructBlock with nested collapsible panel', () => {
|
||||
let boundBlock;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create mocks for callbacks
|
||||
constructor = jest.fn();
|
||||
setState = jest.fn();
|
||||
getState = jest.fn();
|
||||
getValue = jest.fn();
|
||||
focus = jest.fn();
|
||||
|
||||
// Define a test block
|
||||
const settingsBlockDef = new StructBlockDefinition(
|
||||
'settings',
|
||||
[
|
||||
new FieldBlockDefinition(
|
||||
'accent_color',
|
||||
new DummyWidgetDefinition('Accent color widget'),
|
||||
{
|
||||
label: 'Accent color',
|
||||
required: false,
|
||||
icon: 'placeholder',
|
||||
classname: 'w-field w-field--char_field w-field--text_input',
|
||||
},
|
||||
),
|
||||
new FieldBlockDefinition(
|
||||
'font_size',
|
||||
new DummyWidgetDefinition('Font size widget'),
|
||||
{
|
||||
label: 'Font size',
|
||||
required: false,
|
||||
icon: 'placeholder',
|
||||
classname: 'w-field w-field--choice_field w-field--select',
|
||||
},
|
||||
),
|
||||
],
|
||||
{
|
||||
label: 'Settings block',
|
||||
required: false,
|
||||
icon: 'title',
|
||||
classname: 'struct-block',
|
||||
helpText: 'configure how the block is <strong>displayed</strong>',
|
||||
helpIcon: '<svg></svg>',
|
||||
collapsed: true, // Initially collapsed
|
||||
},
|
||||
);
|
||||
|
||||
const blockDef = new StructBlockDefinition(
|
||||
'heading_block',
|
||||
[
|
||||
new FieldBlockDefinition(
|
||||
'heading_text',
|
||||
new DummyWidgetDefinition('Heading widget'),
|
||||
{
|
||||
label: 'Heading text',
|
||||
required: true,
|
||||
icon: 'placeholder',
|
||||
classname: 'w-field w-field--char_field w-field--text_input',
|
||||
},
|
||||
),
|
||||
new FieldBlockDefinition(
|
||||
'size',
|
||||
new DummyWidgetDefinition('Size widget'),
|
||||
{
|
||||
label: 'Size',
|
||||
required: false,
|
||||
icon: 'placeholder',
|
||||
classname: 'w-field w-field--choice_field w-field--select',
|
||||
},
|
||||
),
|
||||
settingsBlockDef,
|
||||
],
|
||||
{
|
||||
label: 'Heading block',
|
||||
required: false,
|
||||
icon: 'title',
|
||||
classname: 'struct-block',
|
||||
helpText: 'use <strong>lots</strong> of these',
|
||||
helpIcon: '<svg></svg>',
|
||||
},
|
||||
);
|
||||
|
||||
// Render it
|
||||
document.body.innerHTML = '<div id="placeholder"></div>';
|
||||
boundBlock = blockDef.render($('#placeholder'), 'the-prefix', {
|
||||
heading_text: 'Test heading text',
|
||||
size: '123',
|
||||
settings: {
|
||||
accent_color: 'Test accent color',
|
||||
font_size: '16px',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it renders correctly with initially collapsed state', () => {
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
|
||||
// Check that the panel can be expanded by clicking the toggle button
|
||||
const button = document.querySelector('[data-panel-toggle]');
|
||||
expect(button).toBeTruthy();
|
||||
button.click();
|
||||
|
||||
// Check that the panel is now expanded
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it renders correctly with initially expanded state', () => {
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
|
||||
// Check that the panel can be expanded by clicking the toggle button
|
||||
const button = document.querySelector('[data-panel-toggle]');
|
||||
expect(button).toBeTruthy();
|
||||
button.click();
|
||||
|
||||
// Check that the panel is now expanded
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Widget constructors are called with correct parameters', () => {
|
||||
expect(constructor.mock.calls.length).toBe(4);
|
||||
|
||||
expect(constructor.mock.calls[0][0]).toBe('Heading widget');
|
||||
expect(constructor.mock.calls[0][1]).toEqual({
|
||||
name: 'the-prefix-heading_text',
|
||||
id: 'the-prefix-heading_text',
|
||||
initialState: 'Test heading text',
|
||||
});
|
||||
|
||||
expect(constructor.mock.calls[1][0]).toBe('Size widget');
|
||||
expect(constructor.mock.calls[1][1]).toEqual({
|
||||
name: 'the-prefix-size',
|
||||
id: 'the-prefix-size',
|
||||
initialState: '123',
|
||||
});
|
||||
});
|
||||
|
||||
test('getValue() calls getValue() on all widgets', () => {
|
||||
const value = boundBlock.getValue();
|
||||
expect(getValue.mock.calls.length).toBe(4);
|
||||
expect(value).toEqual({
|
||||
heading_text: 'value: Heading widget - the-prefix-heading_text',
|
||||
size: 'value: Size widget - the-prefix-size',
|
||||
settings: {
|
||||
accent_color:
|
||||
'value: Accent color widget - the-prefix-settings-accent_color',
|
||||
font_size: 'value: Font size widget - the-prefix-settings-font_size',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('getState() calls getState() on all widgets', () => {
|
||||
const state = boundBlock.getState();
|
||||
expect(getState.mock.calls.length).toBe(4);
|
||||
expect(state).toEqual({
|
||||
heading_text: 'state: Heading widget - the-prefix-heading_text',
|
||||
size: 'state: Size widget - the-prefix-size',
|
||||
settings: {
|
||||
accent_color:
|
||||
'state: Accent color widget - the-prefix-settings-accent_color',
|
||||
font_size: 'state: Font size widget - the-prefix-settings-font_size',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('setState() calls setState() on all widgets', () => {
|
||||
boundBlock.setState({
|
||||
heading_text: 'Changed heading text',
|
||||
size: '456',
|
||||
});
|
||||
expect(setState.mock.calls.length).toBe(2);
|
||||
expect(setState.mock.calls[0][0]).toBe('Heading widget');
|
||||
expect(setState.mock.calls[0][1]).toBe('Changed heading text');
|
||||
expect(setState.mock.calls[1][0]).toBe('Size widget');
|
||||
expect(setState.mock.calls[1][1]).toBe('456');
|
||||
});
|
||||
|
||||
test('focus() calls focus() on first widget', () => {
|
||||
boundBlock.focus();
|
||||
expect(focus.mock.calls.length).toBe(1);
|
||||
expect(focus.mock.calls[0][0]).toBe('Heading widget');
|
||||
});
|
||||
|
||||
test('getTextLabel() returns text label of first widget', () => {
|
||||
expect(boundBlock.getTextLabel()).toBe('label: the-prefix-heading_text');
|
||||
});
|
||||
|
||||
test('setError passes error messages to children', () => {
|
||||
boundBlock.setError({
|
||||
blockErrors: {
|
||||
size: { messages: ['This is too big.'] },
|
||||
},
|
||||
});
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('setError shows non-block errors', () => {
|
||||
boundBlock.setError({
|
||||
messages: ['This is just generally wrong.'],
|
||||
});
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('telepath: wagtail.blocks.StructBlock with formTemplate', () => {
|
||||
let boundBlock;
|
||||
let blockDefWithBadLabelFormat;
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -26,18 +26,35 @@ You can then provide custom CSS for this block, targeted at the specified classn
|
|||
Wagtail's editor styling has some built-in styling for the `struct-block` class and other related elements. If you specify a value for `form_classname`, it will overwrite the classes that are already applied to `StructBlock`, so you must remember to specify the `struct-block` as well.
|
||||
```
|
||||
|
||||
In addition, the `StructBlock`'s `Meta` class also accepts a `collapsed` attribute. When set to `None` (the default), the block is not collapsible. When set to `True` or `False`, the block is wrapped in a collapsible panel and initially displayed in a collapsed or expanded state in the editing interface, respectively. This can be useful for blocks with many sub-blocks, or blocks that are not expected to be edited frequently.
|
||||
|
||||
```python
|
||||
class PersonBlock(blocks.StructBlock):
|
||||
first_name = blocks.CharBlock()
|
||||
surname = blocks.CharBlock()
|
||||
photo = ImageChooserBlock(required=False)
|
||||
biography = blocks.RichTextBlock()
|
||||
|
||||
class Meta:
|
||||
icon = 'user'
|
||||
collapsed = True # This block will be initially collapsed
|
||||
```
|
||||
|
||||
For more extensive customizations that require changes to the HTML markup as well, you can override the `form_template` attribute in `Meta` to specify your own template path. The following variables are available on this template:
|
||||
|
||||
**`children`**
|
||||
**`children`**\
|
||||
An `OrderedDict` of `BoundBlock`s for all of the child blocks making up this `StructBlock`.
|
||||
|
||||
**`help_text`**
|
||||
**`help_text`**\
|
||||
The help text for this block, if specified.
|
||||
|
||||
**`classname`**
|
||||
**`classname`**\
|
||||
The class name passed as `form_classname` (defaults to `struct-block`).
|
||||
|
||||
**`block_definition`**
|
||||
**`collapsed`**\
|
||||
The initial collapsible state of the block (defaults to `None`). Note that the collapsible panel wrapper is not automatically applied to the block's form template. You must write your own wrapper if you want the block to be collapsible.
|
||||
|
||||
**`block_definition`**\
|
||||
The `StructBlock` instance that defines this block.
|
||||
|
||||
**`prefix`**
|
||||
|
|
|
@ -494,6 +494,7 @@ All block definitions have the following methods and properties that can be over
|
|||
|
||||
:param form_classname: An HTML ``class`` attribute to set on the root element of this block as displayed in the editing interface. Defaults to ``struct-block``; note that the admin interface has CSS styles defined on this class, so it is advised to include ``struct-block`` in this value when overriding. See :ref:`custom_editing_interfaces_for_structblock`.
|
||||
:param form_template: Path to a Django template to use to render this block's form. See :ref:`custom_editing_interfaces_for_structblock`.
|
||||
:param collapsed: When ``None`` (the default), the block is not collapsible. When ``True`` or ``False``, the block is collapsible and initially displayed in a collapsed or expanded state in the editing interface, respectively. This can be useful for blocks with many sub-blocks, or blocks that are not expected to be edited frequently. See :ref:`custom_editing_interfaces_for_structblock`.
|
||||
:param value_class: A subclass of ``wagtail.blocks.StructValue`` to use as the type of returned values for this block. See :ref:`custom_value_class_for_structblock`.
|
||||
:param search_index: If false (default true), the content of this block will not be indexed for searching.
|
||||
:param label_format:
|
||||
|
|
|
@ -378,6 +378,7 @@ class BaseStructBlock(Block):
|
|||
),
|
||||
"help_text": getattr(self.meta, "help_text", None),
|
||||
"classname": self.meta.form_classname,
|
||||
"collapsed": self.meta.collapsed,
|
||||
"block_definition": self,
|
||||
"prefix": prefix,
|
||||
}
|
||||
|
@ -392,6 +393,7 @@ class BaseStructBlock(Block):
|
|||
form_template = None
|
||||
value_class = StructValue
|
||||
label_format = None
|
||||
collapsed = None
|
||||
# No icon specified here, because that depends on the purpose that the
|
||||
# block is being used for. Feel encouraged to specify an icon in your
|
||||
# descendant block type
|
||||
|
@ -414,6 +416,7 @@ class StructBlockAdapter(Adapter):
|
|||
"blockDefId": block.definition_prefix,
|
||||
"isPreviewable": block.is_previewable,
|
||||
"classname": block.meta.form_classname,
|
||||
"collapsed": block.meta.collapsed,
|
||||
}
|
||||
|
||||
help_text = getattr(block.meta, "help_text", None)
|
||||
|
|
|
@ -2090,6 +2090,7 @@ class TestStructBlock(SimpleTestCase):
|
|||
"blockDefId": block.definition_prefix,
|
||||
"isPreviewable": block.is_previewable,
|
||||
"classname": "struct-block",
|
||||
"collapsed": None,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -2122,6 +2123,7 @@ class TestStructBlock(SimpleTestCase):
|
|||
"blockDefId": block.definition_prefix,
|
||||
"isPreviewable": block.is_previewable,
|
||||
"classname": "struct-block",
|
||||
"collapsed": None,
|
||||
"formTemplate": "<div>Hello</div>",
|
||||
},
|
||||
)
|
||||
|
@ -2149,6 +2151,7 @@ class TestStructBlock(SimpleTestCase):
|
|||
"blockDefId": block.definition_prefix,
|
||||
"isPreviewable": block.is_previewable,
|
||||
"classname": "struct-block",
|
||||
"collapsed": None,
|
||||
"formTemplate": "<div>Hello</div>",
|
||||
},
|
||||
)
|
||||
|
@ -2185,6 +2188,7 @@ class TestStructBlock(SimpleTestCase):
|
|||
"blockDefId": block.definition_prefix,
|
||||
"isPreviewable": block.is_previewable,
|
||||
"classname": "struct-block",
|
||||
"collapsed": None,
|
||||
"helpIcon": (
|
||||
'<svg class="icon icon-help default" aria-hidden="true">'
|
||||
'<use href="#icon-help"></use></svg>'
|
||||
|
@ -2213,6 +2217,7 @@ class TestStructBlock(SimpleTestCase):
|
|||
"blockDefId": block.definition_prefix,
|
||||
"isPreviewable": block.is_previewable,
|
||||
"classname": "struct-block",
|
||||
"collapsed": None,
|
||||
"helpIcon": (
|
||||
'<svg class="icon icon-help default" aria-hidden="true">'
|
||||
'<use href="#icon-help"></use></svg>'
|
||||
|
@ -2221,6 +2226,21 @@ class TestStructBlock(SimpleTestCase):
|
|||
},
|
||||
)
|
||||
|
||||
def test_adapt_with_collapsed(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
title = blocks.CharBlock()
|
||||
link = blocks.URLBlock()
|
||||
|
||||
cases = [None, False, True]
|
||||
for case in cases:
|
||||
with self.subTest(collapsed=case):
|
||||
block = LinkBlock(collapsed=case)
|
||||
|
||||
block.set_name("test_structblock")
|
||||
js_args = StructBlockAdapter().js_args(block)
|
||||
|
||||
self.assertIs(js_args[2]["collapsed"], case)
|
||||
|
||||
def test_searchable_content(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
title = blocks.CharBlock()
|
||||
|
|
Ładowanie…
Reference in New Issue