diff --git a/cypress/e2e/01-block-group.cy.js b/cypress/e2e/01-block-group.cy.js index 7b578c9..48409f2 100644 --- a/cypress/e2e/01-block-group.cy.js +++ b/cypress/e2e/01-block-group.cy.js @@ -20,6 +20,8 @@ describe('Blocks Tests', () => { .contains('Section (Group)') .click({ force: true }); + cy.contains('Block').click(); + cy.get('.block-editor-group [contenteditable=true]') .focus() .click() @@ -49,4 +51,86 @@ describe('Blocks Tests', () => { cy.contains('My Add-on Page'); cy.contains('test2'); }); + + it('Add Block: Make content type and add group block to layout', () => { + cy.clearSlateTitle(); + cy.getSlateTitle().type('My Add-on Page'); + + cy.get('.documentFirstHeading').contains('My Add-on Page'); + + cy.get('#toolbar-save').click(); + cy.get('.user').click(); + cy.get('a[href="/controlpanel"]').click(); + cy.get('a[href="/controlpanel/dexterity-types"]').click(); + + // add the content type + cy.get('#toolbar-add').click(); + cy.get('#field-title').click().type('Test Content Type'); + cy.get('.actions button[aria-label="Save"]').click(); + + // change the layout + cy.get('.ui.dropdown.actions-test_content_type').click(); + cy.get('.item.layout-test_content_type').click(); + cy.contains('Enable editable Blocks').click(); + cy.getSlate().click(); + + // Add block + cy.get('.ui.basic.icon.button.block-add-button').first().click(); + cy.get('.blocks-chooser .title').contains('Common').click(); + cy.get('.content.active.common .button.group') + .contains('Section (Group)') + .click({ force: true }); + cy.contains('Section').click(); + + cy.get('.sidebar-container #field-placeholder') + .click() + .type('Test Helper Text'); + cy.get( + '.sidebar-container .field-wrapper-instructions div[role="textbox"] p', + ) + .click() + .type('Description Blocks'); + cy.get( + '.sidebar-container .field-wrapper-allowedBlocks .react-select__value-container', + ).click(); + cy.get('.react-select__option') + .contains('Description') + .click({ force: true }); + cy.get('.field-wrapper-ignoreSpaces input').click({ force: true }); + cy.get('.field-wrapper-required input').click({ force: true }); + + cy.get('#toolbar-save').click(); + cy.get('.ui.button.cancel').click(); + cy.get('a[href="/controlpanel').click(); + cy.contains('Home').click(); + cy.get('#toolbar-add').click(); + cy.get('#toolbar-add-test_content_type').click(); + cy.get('#field-title').click().type('Test Content Type'); + cy.get('.block-editor-group div[role="textbox"]') + .click() + .type('/description{enter}'); + cy.get('.block-editor-group .block-editor-slate').click(); + cy.get( + '.block-editor-slate .block-toolbar button[title="Add block"]', + ).click(); + cy.get('.blocks-chooser .field.searchbox div.ui.transparent.input input') + .click() + .focus() + .type('Description{enter}'); + cy.get( + '.blocks-chooser .accordion div[aria-label="Unfold Text blocks"]', + ).click(); + cy.get('.ui.basic.icon.button.description').click(); + cy.get('button[title="Remove block"]').click(); + + // delete the content type + cy.get('#toolbar-save').click(); + cy.get('.user').click(); + cy.get('a[href="/controlpanel"]').click(); + cy.get('a[href="/controlpanel/dexterity-types"]').click(); + + cy.get('.ui.dropdown.actions-test_content_type').click(); + cy.get('.item.delete-test_content_type').click(); + cy.get('button.ui.primary.button').should('contain', 'Yes').click(); + }); }); diff --git a/src/components/manage/Blocks/Group/CounterComponent.test.jsx b/src/components/manage/Blocks/Group/CounterComponent.test.jsx new file mode 100644 index 0000000..3c34a86 --- /dev/null +++ b/src/components/manage/Blocks/Group/CounterComponent.test.jsx @@ -0,0 +1,234 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import CounterComponent from './CounterComponent'; +import '@testing-library/jest-dom/extend-expect'; + +jest.mock('@plone/volto/registry', () => ({ + blocks: { + blocksConfig: { + group: { + countTextIn: ['text'], + }, + }, + }, +})); + +jest.mock('@plone/volto-slate/editor/render', () => ({ + serializeNodesToText: jest.fn((nodes) => + nodes.map((node) => node.text).join(' '), + ), +})); + +describe('CounterComponent', () => { + const setSidebarTab = jest.fn(); + const setSelectedBlock = jest.fn(); + + it('should render info class when character count is less than 95% of maxChars', () => { + const { container, getByText } = render( + , + ); + expect(getByText('96 characters remaining out of 100')).toBeInTheDocument(); + expect(container.querySelector('.counter.info')).toBeInTheDocument(); + }); + + it('should render warning class when character count is between 95% and 100% of maxChars', () => { + const { container, getByText } = render( + , + ); + expect(getByText('4 characters remaining out of 100')).toBeInTheDocument(); + expect(container.querySelector('.counter.warning')).toBeInTheDocument(); + }); + + it('should render warning class when character count is over the maxChars', () => { + const { container, getByText } = render( + , + ); + expect(getByText('4 characters over the limit')).toBeInTheDocument(); + expect(container.querySelector('.counter.danger')).toBeInTheDocument(); + }); + + it('should handle click event', () => { + const { container } = render( + , + ); + container.querySelector('.counter').click(); + expect(setSidebarTab).toHaveBeenCalledWith(1); + expect(setSelectedBlock).toHaveBeenCalled(); + }); + + it('should handle click event with maxChar undefined', () => { + const { container } = render( + , + ); + container.querySelector('.counter').click(); + expect(setSidebarTab).toHaveBeenCalledWith(1); + expect(setSelectedBlock).toHaveBeenCalled(); + }); + + it('should handle click event with data undefined', () => { + const { container } = render( + , + ); + container.querySelector('.counter').click(); + expect(setSidebarTab).toHaveBeenCalledWith(1); + expect(setSelectedBlock).toHaveBeenCalled(); + }); + + it('should handle click event with plaintext undefined, but values present', () => { + const { container } = render( + , + ); + container.querySelector('.counter').click(); + expect(setSidebarTab).toHaveBeenCalledWith(1); + expect(setSelectedBlock).toHaveBeenCalled(); + }); + + it('should handle click event with plaintext undefined and values is not an array and the type is in countTextIn', () => { + const { container } = render( + , + ); + container.querySelector('.counter').click(); + expect(setSidebarTab).toHaveBeenCalledWith(1); + expect(setSelectedBlock).toHaveBeenCalled(); + }); + + it('should handle click event with plaintext undefined and values is not an array and the type is not in countTextIn', () => { + const { container } = render( + , + ); + container.querySelector('.counter').click(); + expect(setSidebarTab).toHaveBeenCalledWith(1); + expect(setSelectedBlock).toHaveBeenCalled(); + }); +}); diff --git a/src/components/manage/Blocks/Group/Edit.test.jsx b/src/components/manage/Blocks/Group/Edit.test.jsx index 48da9de..6c772f2 100644 --- a/src/components/manage/Blocks/Group/Edit.test.jsx +++ b/src/components/manage/Blocks/Group/Edit.test.jsx @@ -76,4 +76,111 @@ describe('Edit', () => { ); fireEvent.keyDown(getByRole('presentation'), { key: 'ArrowUp', code: 38 }); }); + + it('should call ArrowUp keydown', () => { + const props = { + block: 'testBlock', + data: { + instructions: 'test', + data: { + blocks: { + block1: { + type: 'test', + data: { + value: 'Test', + }, + }, + }, + blocks_layout: { + items: [undefined], + }, + }, + }, + onChangeBlock, + onChangeField, + pathname: '/', + selected: true, + manage: true, + }; + const mockOnFocusPreviousBlock = jest.fn(); + const mockOnFocusNextBlock = jest.fn(); + const mockOnAddBlock = jest.fn(); + const mockSidebarTab = jest.fn(); + + const { container } = render( + + + , + ); + + fireEvent.keyDown(container.querySelector('.section-block'), { + key: 'ArrowUp', + code: 38, + }); + fireEvent.keyDown(container.querySelector('.section-block'), { + key: 'ArrowDown', + code: 40, + }); + fireEvent.keyDown(container.querySelector('.section-block'), { + key: 'Enter', + code: 13, + }); + + fireEvent.click(container.querySelector('.blocks-form'), { + shiftKey: true, + }); + fireEvent.click(container.querySelector('.section-block legend')); + }); + + it('should call ArrowUp keydown', () => { + const props = { + block: 'testBlock', + data: { + instructions: 'test', + data: { + blocks: { + block1: { + type: 'test', + data: { + value: 'Test', + }, + }, + }, + blocks_layout: { + items: [undefined], + }, + }, + }, + onChangeBlock, + onChangeField, + pathname: '/', + selected: true, + manage: true, + }; + const mockOnFocusPreviousBlock = jest.fn(); + const mockOnFocusNextBlock = jest.fn(); + const mockOnAddBlock = jest.fn(); + const mockSidebarTab = jest.fn(); + const { container } = render( + + + , + ); + + fireEvent.click(container.querySelector('.section-block legend')); + }); }); diff --git a/src/index.test.js b/src/index.test.js new file mode 100644 index 0000000..fff5824 --- /dev/null +++ b/src/index.test.js @@ -0,0 +1,101 @@ +import applyConfig from './index'; + +describe('applyConfig', () => { + it('should add group block configuration', () => { + const config = { + blocks: { + blocksConfig: { + text: { title: 'Text', restricted: false }, + image: { title: 'Image', restricted: true }, + }, + }, + }; + + const newConfig = applyConfig(config); + + expect(newConfig.blocks.blocksConfig.group).toBeDefined(); + expect(newConfig.blocks.blocksConfig.group.id).toEqual('group'); + expect(newConfig.blocks.blocksConfig.group.title).toEqual( + 'Section (Group)', + ); + expect(newConfig.blocks.blocksConfig.group.icon).toBeDefined(); + expect(newConfig.blocks.blocksConfig.group.view).toBeDefined(); + expect(newConfig.blocks.blocksConfig.group.edit).toBeDefined(); + expect(newConfig.blocks.blocksConfig.group.schema).toBeDefined(); + expect(newConfig.blocks.blocksConfig.group.restricted).toEqual(false); + }); + + it('should include allowed blocks in schema', () => { + const config = { + blocks: { + blocksConfig: { + text: { title: 'Text', restricted: false }, + image: { restricted: false }, + image_test: { title: 'Image', restricted: true }, + }, + }, + }; + + const newConfig = applyConfig(config); + + expect( + newConfig.blocks.blocksConfig.group.schema.properties.allowedBlocks.items + .choices, + ).toEqual([ + ['text', 'Text'], + ['image', 'image'], + ['group', 'Group'], + ]); + }); + + it('should generate tocEntries correctly', () => { + const config = { + blocks: { + blocksConfig: {}, + }, + }; + + const block = { + data: { + blocks: { + block1: { value: [{ type: 'h1' }], plaintext: 'Heading 1' }, + block2: { value: [{ type: 'h2' }], plaintext: 'Heading 2' }, + block3: { value: [{ type: 'h3' }], plaintext: 'Heading 3' }, // This should be ignored + }, + blocks_layout: { + items: ['block1', 'block2', 'block3'], + }, + }, + }; + const tocData = { + levels: ['h1', 'h2'], + }; + const newConfig = applyConfig(config); + const entries = newConfig.blocks.blocksConfig.group.tocEntries( + block, + tocData, + ); + expect(entries).toEqual([ + [1, 'Heading 1', 'block1'], + [2, 'Heading 2', 'block2'], + ]); + }); + + it('should generate no entries', () => { + const config = { + blocks: { + blocksConfig: {}, + }, + }; + const block = undefined; + const tocData = { + levels: undefined, + }; + const newConfig = applyConfig(config); + const entries = newConfig.blocks.blocksConfig.group.tocEntries( + block, + tocData, + ); + expect(entries).toEqual([]); + }); +});