From 14325d461506c0fa645b62ed81e7a6ee0e899ae2 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Fri, 7 Jun 2024 18:19:12 +0100 Subject: [PATCH] Show error when StreamBlock falls below min_num --- .../StreamField/blocks/StreamBlock.js | 47 ++++-- .../StreamField/blocks/StreamBlock.test.js | 148 ++++++++++++++++++ 2 files changed, 182 insertions(+), 13 deletions(-) diff --git a/client/src/components/StreamField/blocks/StreamBlock.js b/client/src/components/StreamField/blocks/StreamBlock.js index c39ff9d515..3eea901747 100644 --- a/client/src/components/StreamField/blocks/StreamBlock.js +++ b/client/src/components/StreamField/blocks/StreamBlock.js @@ -274,22 +274,43 @@ export class StreamBlock extends BaseSequenceBlock { errorMessages.push(message); } + const minNum = this.blockDef.meta.minNum; + if (typeof minNum === 'number' && this.children.length < minNum) { + const message = gettext( + 'The minimum number of items is %(min_num)d', + ).replace('%(min_num)d', `${minNum}`); + errorMessages.push(message); + } + // Check if there are any block types that have count limits - for (const blockType in this.blockDef.meta.blockCounts) { - if (hasOwn(this.blockDef.meta.blockCounts, blockType)) { - const blockMaxNum = this.getBlockMax(blockType); + for (const [blockType, constraints] of Object.entries( + this.blockDef.meta.blockCounts, + )) { + const blockMaxNum = constraints.max_num; + if (typeof blockMaxNum === 'number') { + const currentBlockCount = this.getBlockCount(blockType); - if (typeof blockMaxNum === 'number') { - const currentBlockCount = this.getBlockCount(blockType); + if (currentBlockCount > blockMaxNum) { + const childBlockDef = this.blockDef.childBlockDefsByName[blockType]; + const message = gettext( + 'The maximum number of items is %(max_num)d', + ).replace('%(max_num)d', `${blockMaxNum}`); + const messageWithPrefix = `${childBlockDef.meta.label}: ${message}`; + errorMessages.push(messageWithPrefix); + } + } - if (currentBlockCount > blockMaxNum) { - const childBlockDef = this.blockDef.childBlockDefsByName[blockType]; - const message = gettext( - 'The maximum number of items is %(max_num)d', - ).replace('%(max_num)d', `${blockMaxNum}`); - const messageWithPrefix = `${childBlockDef.meta.label}: ${message}`; - errorMessages.push(messageWithPrefix); - } + const blockMinNum = constraints.min_num; + if (typeof blockMinNum === 'number') { + const currentBlockCount = this.getBlockCount(blockType); + + if (currentBlockCount < blockMinNum) { + const childBlockDef = this.blockDef.childBlockDefsByName[blockType]; + const message = gettext( + 'The minimum number of items is %(min_num)d', + ).replace('%(min_num)d', `${blockMinNum}`); + const messageWithPrefix = `${childBlockDef.meta.label}: ${message}`; + errorMessages.push(messageWithPrefix); } } } diff --git a/client/src/components/StreamField/blocks/StreamBlock.test.js b/client/src/components/StreamField/blocks/StreamBlock.test.js index cecd6b340e..1c41a78feb 100644 --- a/client/src/components/StreamField/blocks/StreamBlock.test.js +++ b/client/src/components/StreamField/blocks/StreamBlock.test.js @@ -722,6 +722,154 @@ describe('telepath: wagtail.blocks.StreamBlock with maxNum set', () => { }); }); +describe('telepath: wagtail.blocks.StreamBlock with minNum set', () => { + // Define a test block + const blockDef = new StreamBlockDefinition( + '', + [ + [ + '', + [ + new FieldBlockDefinition( + 'test_block_a', + new DummyWidgetDefinition('Block A widget'), + { + label: 'Test Block ', + required: true, + icon: 'placeholder', + classname: 'w-field w-field--char_field w-field--text_input', + }, + ), + new FieldBlockDefinition( + 'test_block_b', + new DummyWidgetDefinition('Block B widget'), + { + label: 'Test Block ', + required: true, + icon: 'pilcrow', + classname: + 'w-field w-field--char_field w-field--admin_auto_height_text_input', + }, + ), + ], + ], + ], + { + test_block_a: 'Block A options', + test_block_b: 'Block B options', + }, + { + label: '', + required: true, + icon: 'placeholder', + classname: null, + helpText: 'use plenty of these', + helpIcon: '', + maxNum: null, + minNum: 2, + blockCounts: {}, + strings: { + MOVE_UP: 'Move up', + MOVE_DOWN: 'Move down', + DELETE: 'Delete & kill with fire', + DUPLICATE: 'Duplicate', + ADD: 'Add', + }, + }, + ); + + const assertShowingErrorMessage = () => { + expect(document.querySelector('p.help-block.help-critical').innerHTML).toBe( + 'The minimum number of items is 2', + ); + }; + + const assertNotShowingErrorMessage = () => { + expect(document.querySelector('p.help-block.help-critical')).toBe(null); + }; + + test('test no error message when at lower limit', () => { + document.body.innerHTML = '
'; + const boundBlock = blockDef.render($('#placeholder'), 'the-prefix', [ + { + id: '1', + type: 'test_block_a', + value: 'First value', + }, + { + id: '2', + type: 'test_block_b', + value: 'Second value', + }, + ]); + boundBlock.inserters[0].open(); + + assertNotShowingErrorMessage(); + }); + + test('initialising under minNum shows error message', () => { + document.body.innerHTML = '
'; + const boundBlock = blockDef.render($('#placeholder'), 'the-prefix', [ + { + id: '1', + type: 'test_block_a', + value: 'First value', + }, + ]); + boundBlock.inserters[0].open(); + + assertShowingErrorMessage(); + }); + + test('inserting to reach lower limit removes error', () => { + document.body.innerHTML = '
'; + const boundBlock = blockDef.render($('#placeholder'), 'the-prefix', [ + { + id: '1', + type: 'test_block_a', + value: 'First value', + }, + ]); + boundBlock.inserters[0].open(); + + assertShowingErrorMessage(); + + boundBlock.insert( + { + id: '2', + type: 'test_block_b', + value: 'Second value', + }, + 1, + ); + + assertNotShowingErrorMessage(); + }); + + test('deleting to under lower limit shows error mesage', () => { + document.body.innerHTML = '
'; + const boundBlock = blockDef.render($('#placeholder'), 'the-prefix', [ + { + id: '1', + type: 'test_block_a', + value: 'First value', + }, + { + id: '2', + type: 'test_block_b', + value: 'Second value', + }, + ]); + boundBlock.inserters[0].open(); + + assertNotShowingErrorMessage(); + + boundBlock.deleteBlock(1); + + assertShowingErrorMessage(); + }); +}); + describe('telepath: wagtail.blocks.StreamBlock with blockCounts.max_num set', () => { // Define a test block const blockDef = new StreamBlockDefinition(