kopia lustrzana https://github.com/wagtail/wagtail
Fix inconsistent StreamField ValidationError nesting
Fixes #7086. As per https://github.com/wagtail/wagtail/issues/7086#issuecomment-826945031, ensure that .as_data() is consistently called when telepath-packing ErrorList objects (so that we preserve any embedded ValidationError objects instead of casting them to strings), and introduce an explicit ValidationError class on the client side to make mismatches more obvious (and for future extensibility in case we need to attach more fancy logic to ValidationError). Also add tests for setError, and fix rendering of StreamBlock non-field errors (selector to clear old errors was incorrect, and jest apparently doesn't support innerText).pull/7118/head
rodzic
53e55d28e6
commit
1061caa5ef
|
@ -27,7 +27,7 @@ export class FieldBlock {
|
|||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
this.setError([
|
||||
['This widget failed to render, please check the console for details']
|
||||
{ messages: ['This widget failed to render, please check the console for details'] }
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ export class FieldBlock {
|
|||
|
||||
const errorElement = document.createElement('p');
|
||||
errorElement.classList.add('error-message');
|
||||
errorElement.innerHTML = errorList.map(error => `<span>${h(error[0])}</span>`).join('');
|
||||
errorElement.innerHTML = errorList.map(error => `<span>${h(error.messages[0])}</span>`).join('');
|
||||
this.element.querySelector('.field-content').appendChild(errorElement);
|
||||
} else {
|
||||
this.element.classList.remove('error');
|
||||
|
|
|
@ -41,6 +41,12 @@ class DummyWidgetDefinition {
|
|||
}
|
||||
}
|
||||
|
||||
class ValidationError {
|
||||
constructor(messages) {
|
||||
this.messages = messages;
|
||||
}
|
||||
}
|
||||
|
||||
describe('telepath: wagtail.blocks.FieldBlock', () => {
|
||||
let boundBlock;
|
||||
|
||||
|
@ -117,6 +123,14 @@ describe('telepath: wagtail.blocks.FieldBlock', () => {
|
|||
expect(focus.mock.calls.length).toBe(1);
|
||||
expect(focus.mock.calls[0][0]).toBe('The widget');
|
||||
});
|
||||
|
||||
test('setError() renders errors', () => {
|
||||
boundBlock.setError([
|
||||
new ValidationError(['Field must not contain the letter E']),
|
||||
new ValidationError(['Field must contain a story about kittens']),
|
||||
]);
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('telepath: wagtail.blocks.FieldBlock with comments enabled', () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { FieldBlockDefinition } from './FieldBlock';
|
||||
import { ListBlockDefinition } from './ListBlock';
|
||||
import { ListBlockDefinition, ListBlockValidationError } from './ListBlock';
|
||||
|
||||
import $ from 'jquery';
|
||||
window.$ = $;
|
||||
|
@ -37,6 +37,12 @@ class DummyWidgetDefinition {
|
|||
}
|
||||
}
|
||||
|
||||
class ValidationError {
|
||||
constructor(messages) {
|
||||
this.messages = messages;
|
||||
}
|
||||
}
|
||||
|
||||
describe('telepath: wagtail.blocks.ListBlock', () => {
|
||||
let boundBlock;
|
||||
|
||||
|
@ -202,4 +208,14 @@ describe('telepath: wagtail.blocks.ListBlock', () => {
|
|||
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('setError passes error messages to children', () => {
|
||||
boundBlock.setError([
|
||||
new ListBlockValidationError([
|
||||
null,
|
||||
[new ValidationError(['Not as good as the first one'])],
|
||||
]),
|
||||
]);
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -330,15 +330,15 @@ export class StreamBlock extends BaseSequenceBlock {
|
|||
|
||||
// Non block errors
|
||||
const container = this.container[0];
|
||||
container.querySelectorAll(':scope > .help-block .help-critical').forEach(element => element.remove());
|
||||
container.querySelectorAll(':scope > .help-block.help-critical').forEach(element => element.remove());
|
||||
|
||||
if (error.nonBlockErrors.length > 0) {
|
||||
// Add a help block for each error raised
|
||||
error.nonBlockErrors.forEach(errorText => {
|
||||
error.nonBlockErrors.forEach(nonBlockError => {
|
||||
const errorElement = document.createElement('p');
|
||||
errorElement.classList.add('help-block');
|
||||
errorElement.classList.add('help-critical');
|
||||
errorElement.innerText = errorText;
|
||||
errorElement.innerHTML = h(nonBlockError.messages[0]);
|
||||
container.insertBefore(errorElement, container.childNodes[0]);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { FieldBlockDefinition } from './FieldBlock';
|
||||
import { StreamBlockDefinition } from './StreamBlock';
|
||||
import { StreamBlockDefinition, StreamBlockValidationError } from './StreamBlock';
|
||||
|
||||
import $ from 'jquery';
|
||||
window.$ = $;
|
||||
|
@ -37,6 +37,12 @@ class DummyWidgetDefinition {
|
|||
}
|
||||
}
|
||||
|
||||
class ValidationError {
|
||||
constructor(messages) {
|
||||
this.messages = messages;
|
||||
}
|
||||
}
|
||||
|
||||
describe('telepath: wagtail.blocks.StreamBlock', () => {
|
||||
let boundBlock;
|
||||
|
||||
|
@ -272,6 +278,21 @@ describe('telepath: wagtail.blocks.StreamBlock', () => {
|
|||
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('setError renders error messages', () => {
|
||||
boundBlock.setError([
|
||||
new StreamBlockValidationError(
|
||||
[
|
||||
/* non-block error */
|
||||
new ValidationError(['At least three blocks are required']),
|
||||
],
|
||||
{
|
||||
/* block error */
|
||||
1: [new ValidationError(['Not as good as the first one'])],
|
||||
}),
|
||||
]);
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('telepath: wagtail.blocks.StreamBlock with labels that need escaping', () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { FieldBlockDefinition } from './FieldBlock';
|
||||
import { StructBlockDefinition } from './StructBlock';
|
||||
import { StructBlockDefinition, StructBlockValidationError } from './StructBlock';
|
||||
|
||||
import $ from 'jquery';
|
||||
window.$ = $;
|
||||
|
@ -37,6 +37,12 @@ class DummyWidgetDefinition {
|
|||
}
|
||||
}
|
||||
|
||||
class ValidationError {
|
||||
constructor(messages) {
|
||||
this.messages = messages;
|
||||
}
|
||||
}
|
||||
|
||||
describe('telepath: wagtail.blocks.StructBlock', () => {
|
||||
let boundBlock;
|
||||
|
||||
|
@ -152,6 +158,15 @@ describe('telepath: wagtail.blocks.StructBlock', () => {
|
|||
expect(focus.mock.calls.length).toBe(1);
|
||||
expect(focus.mock.calls[0][0]).toBe('Heading widget');
|
||||
});
|
||||
|
||||
test('setError passes error messages to children', () => {
|
||||
boundBlock.setError([
|
||||
new StructBlockValidationError({
|
||||
size: [new ValidationError(['This is too big'])],
|
||||
}),
|
||||
]);
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('telepath: wagtail.blocks.StructBlock with formTemplate', () => {
|
||||
|
|
|
@ -22,6 +22,17 @@ exports[`telepath: wagtail.blocks.FieldBlock it renders correctly 1`] = `
|
|||
</div>"
|
||||
`;
|
||||
|
||||
exports[`telepath: wagtail.blocks.FieldBlock setError() renders errors 1`] = `
|
||||
"<div class=\\"field char_field widget-text_input fieldname-test_charblock error\\">
|
||||
<div class=\\"field-content\\">
|
||||
<div class=\\"input\\">
|
||||
<p name=\\"the-prefix\\" id=\\"the-prefix\\">The widget</p>
|
||||
<span></span>
|
||||
</div>
|
||||
<p class=\\"help\\">drink <em>more</em> water</p><p class=\\"error-message\\"><span>Field must not contain the letter E</span><span>Field must contain a story about kittens</span></p></div>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`telepath: wagtail.blocks.FieldBlock with comments enabled it renders correctly 1`] = `
|
||||
"<div class=\\"field char_field widget-text_input fieldname-test_charblock\\">
|
||||
<div class=\\"field-content\\">
|
||||
|
|
|
@ -591,3 +591,112 @@ exports[`telepath: wagtail.blocks.ListBlock it renders correctly 1`] = `
|
|||
</button></div>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`telepath: wagtail.blocks.ListBlock setError passes error messages to children 1`] = `
|
||||
"<span>
|
||||
<div class=\\"help\\">
|
||||
<div class=\\"icon-help\\">?</div>
|
||||
use <strong>a few</strong> of these
|
||||
</div>
|
||||
</span><div class=\\"c-sf-container \\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-count\\" data-streamfield-list-count=\\"\\" value=\\"2\\">
|
||||
|
||||
<div data-streamfield-list-container=\\"\\"><button type=\\"button\\" title=\\"Add\\" data-streamfield-list-add=\\"\\" class=\\"c-sf-add-button c-sf-add-button--visible\\">
|
||||
<i aria-hidden=\\"true\\">+</i>
|
||||
</button><div aria-hidden=\\"false\\" data-contentpath-disabled=\\"\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-0-deleted\\" value=\\"\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-0-order\\" value=\\"0\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-0-type\\" value=\\"\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-0-id\\" value=\\"\\">
|
||||
|
||||
<div>
|
||||
<div class=\\"c-sf-container__block-container\\">
|
||||
<div class=\\"c-sf-block\\">
|
||||
<div data-block-header=\\"\\" class=\\"c-sf-block__header c-sf-block__header--collapsible\\">
|
||||
<span class=\\"c-sf-block__header__icon\\">
|
||||
<i class=\\"icon icon-pilcrow\\"></i>
|
||||
</span>
|
||||
<h3 data-block-title=\\"\\" class=\\"c-sf-block__header__title\\"></h3>
|
||||
<div class=\\"c-sf-block__actions\\">
|
||||
<span class=\\"c-sf-block__type\\"></span>
|
||||
<button type=\\"button\\" data-move-up-button=\\"\\" class=\\"c-sf-block__actions__single\\" disabled=\\"\\" title=\\"Move up\\">
|
||||
<i class=\\"icon icon-arrow-up\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
<button type=\\"button\\" data-move-down-button=\\"\\" class=\\"c-sf-block__actions__single\\" title=\\"Move down\\">
|
||||
<i class=\\"icon icon-arrow-down\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
<button type=\\"button\\" data-duplicate-button=\\"\\" class=\\"c-sf-block__actions__single\\" title=\\"Duplicate\\">
|
||||
<i class=\\"icon icon-duplicate\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
<button type=\\"button\\" data-delete-button=\\"\\" class=\\"c-sf-block__actions__single\\" title=\\"Delete\\">
|
||||
<i class=\\"icon icon-bin\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div data-block-content=\\"\\" class=\\"c-sf-block__content\\" aria-hidden=\\"false\\">
|
||||
<div class=\\"c-sf-block__content-inner\\">
|
||||
<div class=\\"field char_field widget-admin_auto_height_text_input fieldname-\\">
|
||||
<div class=\\"field-content\\">
|
||||
<div class=\\"input\\">
|
||||
<p name=\\"the-prefix-0-value\\" id=\\"the-prefix-0-value\\">The widget</p>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><button type=\\"button\\" title=\\"Add\\" data-streamfield-list-add=\\"\\" class=\\"c-sf-add-button c-sf-add-button--visible\\">
|
||||
<i aria-hidden=\\"true\\">+</i>
|
||||
</button><div aria-hidden=\\"false\\" data-contentpath-disabled=\\"\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-1-deleted\\" value=\\"\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-1-order\\" value=\\"1\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-1-type\\" value=\\"\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-1-id\\" value=\\"\\">
|
||||
|
||||
<div>
|
||||
<div class=\\"c-sf-container__block-container\\">
|
||||
<div class=\\"c-sf-block\\">
|
||||
<div data-block-header=\\"\\" class=\\"c-sf-block__header c-sf-block__header--collapsible\\">
|
||||
<span class=\\"c-sf-block__header__icon\\">
|
||||
<i class=\\"icon icon-pilcrow\\"></i>
|
||||
</span>
|
||||
<h3 data-block-title=\\"\\" class=\\"c-sf-block__header__title\\"></h3>
|
||||
<div class=\\"c-sf-block__actions\\">
|
||||
<span class=\\"c-sf-block__type\\"></span>
|
||||
<button type=\\"button\\" data-move-up-button=\\"\\" class=\\"c-sf-block__actions__single\\" title=\\"Move up\\">
|
||||
<i class=\\"icon icon-arrow-up\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
<button type=\\"button\\" data-move-down-button=\\"\\" class=\\"c-sf-block__actions__single\\" disabled=\\"\\" title=\\"Move down\\">
|
||||
<i class=\\"icon icon-arrow-down\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
<button type=\\"button\\" data-duplicate-button=\\"\\" class=\\"c-sf-block__actions__single\\" title=\\"Duplicate\\">
|
||||
<i class=\\"icon icon-duplicate\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
<button type=\\"button\\" data-delete-button=\\"\\" class=\\"c-sf-block__actions__single\\" title=\\"Delete\\">
|
||||
<i class=\\"icon icon-bin\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div data-block-content=\\"\\" class=\\"c-sf-block__content\\" aria-hidden=\\"false\\">
|
||||
<div class=\\"c-sf-block__content-inner\\">
|
||||
<div class=\\"field char_field widget-admin_auto_height_text_input fieldname- error\\">
|
||||
<div class=\\"field-content\\">
|
||||
<div class=\\"input\\">
|
||||
<p name=\\"the-prefix-1-value\\" id=\\"the-prefix-1-value\\">The widget</p>
|
||||
<span></span>
|
||||
</div>
|
||||
<p class=\\"error-message\\"><span>Not as good as the first one</span></p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><button type=\\"button\\" title=\\"Add\\" data-streamfield-list-add=\\"\\" class=\\"c-sf-add-button c-sf-add-button--visible\\">
|
||||
<i aria-hidden=\\"true\\">+</i>
|
||||
</button></div>
|
||||
</div>"
|
||||
`;
|
||||
|
|
|
@ -677,6 +677,129 @@ exports[`telepath: wagtail.blocks.StreamBlock it renders menus on opening 1`] =
|
|||
</div>"
|
||||
`;
|
||||
|
||||
exports[`telepath: wagtail.blocks.StreamBlock setError renders error messages 1`] = `
|
||||
"<span>
|
||||
<div class=\\"help\\">
|
||||
<div class=\\"icon-help\\">?</div>
|
||||
use <strong>plenty</strong> of these
|
||||
</div>
|
||||
</span><div class=\\"c-sf-container \\"><p class=\\"help-block help-critical\\">At least three blocks are required</p>
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-count\\" data-streamfield-stream-count=\\"\\" value=\\"2\\">
|
||||
<div data-streamfield-stream-container=\\"\\"><div>
|
||||
<button data-streamblock-menu-open=\\"\\" type=\\"button\\" title=\\"Add\\" class=\\"c-sf-add-button c-sf-add-button--visible\\">
|
||||
<i aria-hidden=\\"true\\">+</i>
|
||||
</button>
|
||||
<div data-streamblock-menu-outer=\\"\\" style=\\"display: none;\\" aria-hidden=\\"true\\">
|
||||
<div data-streamblock-menu-inner=\\"\\" class=\\"c-sf-add-panel\\"></div>
|
||||
</div>
|
||||
</div><div aria-hidden=\\"false\\" data-contentpath=\\"1\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-0-deleted\\" value=\\"\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-0-order\\" value=\\"0\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-0-type\\" value=\\"test_block_a\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-0-id\\" value=\\"1\\">
|
||||
|
||||
<div>
|
||||
<div class=\\"c-sf-container__block-container\\">
|
||||
<div class=\\"c-sf-block\\">
|
||||
<div data-block-header=\\"\\" class=\\"c-sf-block__header c-sf-block__header--collapsible\\">
|
||||
<span class=\\"c-sf-block__header__icon\\">
|
||||
<i class=\\"icon icon-placeholder\\"></i>
|
||||
</span>
|
||||
<h3 data-block-title=\\"\\" class=\\"c-sf-block__header__title\\"></h3>
|
||||
<div class=\\"c-sf-block__actions\\">
|
||||
<span class=\\"c-sf-block__type\\">Test Block A</span>
|
||||
<button type=\\"button\\" data-move-up-button=\\"\\" class=\\"c-sf-block__actions__single\\" disabled=\\"\\" title=\\"Move up\\">
|
||||
<i class=\\"icon icon-arrow-up\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
<button type=\\"button\\" data-move-down-button=\\"\\" class=\\"c-sf-block__actions__single\\" title=\\"Move down\\">
|
||||
<i class=\\"icon icon-arrow-down\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
<button type=\\"button\\" data-duplicate-button=\\"\\" class=\\"c-sf-block__actions__single\\" title=\\"Duplicate\\">
|
||||
<i class=\\"icon icon-duplicate\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
<button type=\\"button\\" data-delete-button=\\"\\" class=\\"c-sf-block__actions__single\\" title=\\"Delete\\">
|
||||
<i class=\\"icon icon-bin\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div data-block-content=\\"\\" class=\\"c-sf-block__content\\" aria-hidden=\\"false\\">
|
||||
<div class=\\"c-sf-block__content-inner\\">
|
||||
<div class=\\"field char_field widget-text_input fieldname-test_charblock\\">
|
||||
<div class=\\"field-content\\">
|
||||
<div class=\\"input\\">
|
||||
<p name=\\"the-prefix-0-value\\" id=\\"the-prefix-0-value\\">Block A widget</p>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><div>
|
||||
<button data-streamblock-menu-open=\\"\\" type=\\"button\\" title=\\"Add\\" class=\\"c-sf-add-button c-sf-add-button--visible\\">
|
||||
<i aria-hidden=\\"true\\">+</i>
|
||||
</button>
|
||||
<div data-streamblock-menu-outer=\\"\\" style=\\"display: none;\\" aria-hidden=\\"true\\">
|
||||
<div data-streamblock-menu-inner=\\"\\" class=\\"c-sf-add-panel\\"></div>
|
||||
</div>
|
||||
</div><div aria-hidden=\\"false\\" data-contentpath=\\"2\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-1-deleted\\" value=\\"\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-1-order\\" value=\\"1\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-1-type\\" value=\\"test_block_b\\">
|
||||
<input type=\\"hidden\\" name=\\"the-prefix-1-id\\" value=\\"2\\">
|
||||
|
||||
<div>
|
||||
<div class=\\"c-sf-container__block-container\\">
|
||||
<div class=\\"c-sf-block\\">
|
||||
<div data-block-header=\\"\\" class=\\"c-sf-block__header c-sf-block__header--collapsible\\">
|
||||
<span class=\\"c-sf-block__header__icon\\">
|
||||
<i class=\\"icon icon-pilcrow\\"></i>
|
||||
</span>
|
||||
<h3 data-block-title=\\"\\" class=\\"c-sf-block__header__title\\"></h3>
|
||||
<div class=\\"c-sf-block__actions\\">
|
||||
<span class=\\"c-sf-block__type\\">Test Block B</span>
|
||||
<button type=\\"button\\" data-move-up-button=\\"\\" class=\\"c-sf-block__actions__single\\" title=\\"Move up\\">
|
||||
<i class=\\"icon icon-arrow-up\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
<button type=\\"button\\" data-move-down-button=\\"\\" class=\\"c-sf-block__actions__single\\" disabled=\\"\\" title=\\"Move down\\">
|
||||
<i class=\\"icon icon-arrow-down\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
<button type=\\"button\\" data-duplicate-button=\\"\\" class=\\"c-sf-block__actions__single\\" title=\\"Duplicate\\">
|
||||
<i class=\\"icon icon-duplicate\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
<button type=\\"button\\" data-delete-button=\\"\\" class=\\"c-sf-block__actions__single\\" title=\\"Delete\\">
|
||||
<i class=\\"icon icon-bin\\" aria-hidden=\\"true\\"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div data-block-content=\\"\\" class=\\"c-sf-block__content\\" aria-hidden=\\"false\\">
|
||||
<div class=\\"c-sf-block__content-inner\\">
|
||||
<div class=\\"field char_field widget-admin_auto_height_text_input fieldname-test_textblock error\\">
|
||||
<div class=\\"field-content\\">
|
||||
<div class=\\"input\\">
|
||||
<p name=\\"the-prefix-1-value\\" id=\\"the-prefix-1-value\\">Block B widget</p>
|
||||
<span></span>
|
||||
</div>
|
||||
<p class=\\"error-message\\"><span>Not as good as the first one</span></p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><div>
|
||||
<button data-streamblock-menu-open=\\"\\" type=\\"button\\" title=\\"Add\\" class=\\"c-sf-add-button c-sf-add-button--visible\\">
|
||||
<i aria-hidden=\\"true\\">+</i>
|
||||
</button>
|
||||
<div data-streamblock-menu-outer=\\"\\" style=\\"display: none;\\" aria-hidden=\\"true\\">
|
||||
<div data-streamblock-menu-inner=\\"\\" class=\\"c-sf-add-panel\\"></div>
|
||||
</div>
|
||||
</div></div>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`telepath: wagtail.blocks.StreamBlock with labels that need escaping it renders correctly 1`] = `
|
||||
"<span>
|
||||
<div class=\\"help\\">
|
||||
|
|
|
@ -32,6 +32,38 @@ exports[`telepath: wagtail.blocks.StructBlock it renders correctly 1`] = `
|
|||
</div></div>"
|
||||
`;
|
||||
|
||||
exports[`telepath: wagtail.blocks.StructBlock setError passes error messages to children 1`] = `
|
||||
"<div class=\\"struct-block\\">
|
||||
|
||||
<span>
|
||||
<div class=\\"help\\">
|
||||
<div class=\\"icon-help\\">?</div>
|
||||
use <strong>lots</strong> of these
|
||||
</div>
|
||||
</span>
|
||||
<div class=\\"field required\\" data-contentpath=\\"heading_text\\">
|
||||
<label class=\\"field__label\\" for=\\"the-prefix-heading_text\\">Heading text</label>
|
||||
<div class=\\"field char_field widget-text_input fieldname-heading_text\\">
|
||||
<div class=\\"field-content\\">
|
||||
<div class=\\"input\\">
|
||||
<p name=\\"the-prefix-heading_text\\" id=\\"the-prefix-heading_text\\">Heading widget</p>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><div class=\\"field \\" data-contentpath=\\"size\\">
|
||||
<label class=\\"field__label\\" for=\\"the-prefix-size\\">Size</label>
|
||||
<div class=\\"field choice_field widget-select fieldname-size error\\">
|
||||
<div class=\\"field-content\\">
|
||||
<div class=\\"input\\">
|
||||
<p name=\\"the-prefix-size\\" id=\\"the-prefix-size\\">Size widget</p>
|
||||
<span></span>
|
||||
</div>
|
||||
<p class=\\"error-message\\"><span>This is too big</span></p></div>
|
||||
</div>
|
||||
</div></div>"
|
||||
`;
|
||||
|
||||
exports[`telepath: wagtail.blocks.StructBlock with formTemplate it renders correctly 1`] = `
|
||||
"<div class=\\"custom-form-template\\">
|
||||
<p>here comes the first field:</p>
|
||||
|
|
|
@ -235,3 +235,10 @@ class AdminDateTimeInput extends BaseDateTimeWidget {
|
|||
initChooserFn = window.initDateTimeChooser;
|
||||
}
|
||||
window.telepath.register('wagtail.widgets.AdminDateTimeInput', AdminDateTimeInput);
|
||||
|
||||
class ValidationError {
|
||||
constructor(messages) {
|
||||
this.messages = messages;
|
||||
}
|
||||
}
|
||||
window.telepath.register('wagtail.errors.ValidationError', ValidationError);
|
||||
|
|
|
@ -38,7 +38,7 @@ class StreamBlockValidationErrorAdapter(Adapter):
|
|||
js_constructor = 'wagtail.blocks.StreamBlockValidationError'
|
||||
|
||||
def js_args(self, error):
|
||||
return [error.non_block_errors, {
|
||||
return [error.non_block_errors.as_data(), {
|
||||
block_id: child_errors.as_data()
|
||||
for block_id, child_errors in error.block_errors.items()
|
||||
}]
|
||||
|
|
|
@ -27,7 +27,15 @@ class StructBlockValidationErrorAdapter(Adapter):
|
|||
js_constructor = 'wagtail.blocks.StructBlockValidationError'
|
||||
|
||||
def js_args(self, error):
|
||||
return [error.block_errors]
|
||||
if error.block_errors is None:
|
||||
return [None]
|
||||
else:
|
||||
return [
|
||||
{
|
||||
name: error_list.as_data()
|
||||
for name, error_list in error.block_errors.items()
|
||||
}
|
||||
]
|
||||
|
||||
@cached_property
|
||||
def media(self):
|
||||
|
|
|
@ -5,6 +5,7 @@ and extract field values.
|
|||
"""
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from wagtail.admin.staticfiles import versioned_static
|
||||
|
@ -49,3 +50,21 @@ class RadioSelectAdapter(WidgetAdapter):
|
|||
|
||||
|
||||
register(RadioSelectAdapter(), forms.RadioSelect)
|
||||
|
||||
|
||||
class ValidationErrorAdapter(Adapter):
|
||||
js_constructor = 'wagtail.errors.ValidationError'
|
||||
|
||||
def js_args(self, error):
|
||||
return [
|
||||
error.messages,
|
||||
]
|
||||
|
||||
@cached_property
|
||||
def media(self):
|
||||
return forms.Media(js=[
|
||||
versioned_static('wagtailadmin/js/telepath/widgets.js'),
|
||||
])
|
||||
|
||||
|
||||
register(ValidationErrorAdapter(), ValidationError)
|
||||
|
|
Ładowanie…
Reference in New Issue