UNPKG

decap-cms-core

Version:

Decap CMS core application, see decap-cms package for the main distribution.

443 lines (380 loc) 14.8 kB
import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { render, fireEvent } from '@testing-library/react'; import { fromJS } from 'immutable'; import configureStore from 'redux-mock-store'; import { Provider } from 'react-redux'; import ConnectedNestedCollection, { NestedCollection, getTreeData, walk, updateNode, } from '../NestedCollection'; jest.mock('decap-cms-ui-default', () => { const actual = jest.requireActual('decap-cms-ui-default'); return { ...actual, Icon: 'mocked-icon', }; }); const middlewares = []; const mockStore = configureStore(middlewares); function renderWithRedux(component, { store } = {}) { function Wrapper({ children }) { return <Provider store={store}>{children}</Provider>; } return render(component, { wrapper: Wrapper }); } describe('NestedCollection', () => { const collection = fromJS({ name: 'pages', label: 'Pages', folder: 'src/pages', fields: [{ name: 'title', widget: 'string' }], }); it('should render correctly with no entries', () => { const entries = fromJS([]); const { asFragment, getByTestId } = render( <MemoryRouter> <NestedCollection collection={collection} entries={entries} /> </MemoryRouter>, ); expect(getByTestId('/')).toHaveTextContent('Pages'); expect(getByTestId('/')).toHaveAttribute('href', '/collections/pages'); expect(asFragment()).toMatchSnapshot(); }); it('should render correctly with nested entries', () => { const entries = fromJS([ { path: 'src/pages/index.md', data: { title: 'Root' } }, { path: 'src/pages/a/index.md', data: { title: 'File 1' } }, { path: 'src/pages/b/index.md', data: { title: 'File 2' } }, { path: 'src/pages/a/a/index.md', data: { title: 'File 3' } }, { path: 'src/pages/b/a/index.md', data: { title: 'File 4' } }, ]); const { asFragment, getByTestId } = render( <MemoryRouter> <NestedCollection collection={collection} entries={entries} /> </MemoryRouter>, ); // expand the tree fireEvent.click(getByTestId('/')); expect(getByTestId('/a')).toHaveTextContent('File 1'); expect(getByTestId('/a')).toHaveAttribute('href', '/collections/pages/filter/a'); expect(getByTestId('/b')).toHaveTextContent('File 2'); expect(getByTestId('/b')).toHaveAttribute('href', '/collections/pages/filter/b'); expect(asFragment()).toMatchSnapshot(); }); it('should keep expanded nodes on re-render', () => { const entries = fromJS([ { path: 'src/pages/index.md', data: { title: 'Root' } }, { path: 'src/pages/a/index.md', data: { title: 'File 1' } }, { path: 'src/pages/b/index.md', data: { title: 'File 2' } }, { path: 'src/pages/a/a/index.md', data: { title: 'File 3' } }, { path: 'src/pages/b/a/index.md', data: { title: 'File 4' } }, ]); const { getByTestId, rerender } = render( <MemoryRouter> <NestedCollection collection={collection} entries={entries} /> </MemoryRouter>, ); fireEvent.click(getByTestId('/')); fireEvent.click(getByTestId('/a')); expect(getByTestId('/a')).toHaveTextContent('File 1'); const newEntries = fromJS([ { path: 'src/pages/index.md', data: { title: 'Root' } }, { path: 'src/pages/a/index.md', data: { title: 'File 1' } }, { path: 'src/pages/b/index.md', data: { title: 'File 2' } }, { path: 'src/pages/a/a/index.md', data: { title: 'File 3' } }, { path: 'src/pages/b/a/index.md', data: { title: 'File 4' } }, { path: 'src/pages/c/index.md', data: { title: 'File 5' } }, { path: 'src/pages/c/a/index.md', data: { title: 'File 6' } }, ]); rerender( <MemoryRouter> <NestedCollection collection={collection} entries={newEntries} /> </MemoryRouter>, ); expect(getByTestId('/a')).toHaveTextContent('File 1'); }); it('should expand nodes based on filterTerm', () => { const entries = fromJS([ { path: 'src/pages/index.md', data: { title: 'Root' } }, { path: 'src/pages/a/index.md', data: { title: 'File 1' } }, { path: 'src/pages/a/a/index.md', data: { title: 'File 2' } }, { path: 'src/pages/a/a/a/index.md', data: { title: 'File 3' } }, ]); const { getByTestId, queryByTestId, rerender } = render( <MemoryRouter> <NestedCollection collection={collection} entries={entries} /> </MemoryRouter>, ); expect(queryByTestId('/a/a')).toBeNull(); rerender( <MemoryRouter> <NestedCollection collection={collection} entries={entries} filterTerm={'a/a'} /> </MemoryRouter>, ); expect(getByTestId('/a/a')).toHaveTextContent('File 2'); }); it('should ignore filterTerm once a user toggles an node', () => { const entries = fromJS([ { path: 'src/pages/index.md', data: { title: 'Root' } }, { path: 'src/pages/a/index.md', data: { title: 'File 1' } }, { path: 'src/pages/a/a/index.md', data: { title: 'File 2' } }, { path: 'src/pages/a/a/a/index.md', data: { title: 'File 3' } }, ]); const { getByTestId, queryByTestId, rerender } = render( <MemoryRouter> <NestedCollection collection={collection} entries={entries} /> </MemoryRouter>, ); rerender( <MemoryRouter> <NestedCollection collection={collection} entries={entries} filterTerm={'a/a'} /> </MemoryRouter>, ); expect(getByTestId('/a/a')).toHaveTextContent('File 2'); fireEvent.click(getByTestId('/a')); rerender( <MemoryRouter> <NestedCollection collection={collection} entries={fromJS(entries.toJS())} filterTerm={'a/a'} /> </MemoryRouter>, ); expect(queryByTestId('/a/a')).toBeNull(); }); it('should not collapse an unselected node when clicked', () => { const entries = fromJS([ { path: 'src/pages/index.md', data: { title: 'Root' } }, { path: 'src/pages/a/index.md', data: { title: 'File 1' } }, { path: 'src/pages/a/a/index.md', data: { title: 'File 2' } }, { path: 'src/pages/a/a/a/index.md', data: { title: 'File 3' } }, { path: 'src/pages/a/a/a/a/index.md', data: { title: 'File 4' } }, ]); const { getByTestId } = render( <MemoryRouter> <NestedCollection collection={collection} entries={entries} /> </MemoryRouter>, ); fireEvent.click(getByTestId('/')); fireEvent.click(getByTestId('/a')); fireEvent.click(getByTestId('/a/a')); expect(getByTestId('/a/a')).toHaveTextContent('File 2'); fireEvent.click(getByTestId('/a')); expect(getByTestId('/a/a')).toHaveTextContent('File 2'); }); it('should collapse a selected node when clicked', () => { const entries = fromJS([ { path: 'src/pages/index.md', data: { title: 'Root' } }, { path: 'src/pages/a/index.md', data: { title: 'File 1' } }, { path: 'src/pages/a/a/index.md', data: { title: 'File 2' } }, { path: 'src/pages/a/a/a/index.md', data: { title: 'File 3' } }, { path: 'src/pages/a/a/a/a/index.md', data: { title: 'File 4' } }, ]); const { getByTestId, queryByTestId } = render( <MemoryRouter> <NestedCollection collection={collection} entries={entries} /> </MemoryRouter>, ); fireEvent.click(getByTestId('/')); fireEvent.click(getByTestId('/a')); fireEvent.click(getByTestId('/a/a')); expect(getByTestId('/a/a/a')).toHaveTextContent('File 3'); fireEvent.click(getByTestId('/a/a')); expect(queryByTestId('/a/a/a')).toBeNull(); }); it('should render connected component', () => { const entriesArray = [ { slug: 'index', path: 'src/pages/index.md', data: { title: 'Root' } }, { slug: 'a/index', path: 'src/pages/a/index.md', data: { title: 'File 1' } }, { slug: 'b/index', path: 'src/pages/b/index.md', data: { title: 'File 2' } }, { slug: 'a/a/index', path: 'src/pages/a/a/index.md', data: { title: 'File 3' } }, { slug: 'b/a/index', path: 'src/pages/b/a/index.md', data: { title: 'File 4' } }, ]; const entries = entriesArray.reduce( (acc, entry) => { acc.entities[`${collection.get('name')}.${entry.slug}`] = entry; acc.pages[collection.get('name')].ids.push(entry.slug); return acc; }, { pages: { [collection.get('name')]: { ids: [] } }, entities: {} }, ); const store = mockStore({ entries: fromJS(entries) }); const { asFragment, getByTestId } = renderWithRedux( <MemoryRouter> <ConnectedNestedCollection collection={collection} entries={entries} /> </MemoryRouter>, { store }, ); // expand the root fireEvent.click(getByTestId('/')); expect(getByTestId('/a')).toHaveTextContent('File 1'); expect(getByTestId('/a')).toHaveAttribute('href', '/collections/pages/filter/a'); expect(getByTestId('/b')).toHaveTextContent('File 2'); expect(getByTestId('/b')).toHaveAttribute('href', '/collections/pages/filter/b'); expect(asFragment()).toMatchSnapshot(); }); describe('getTreeData', () => { it('should return nested tree data from entries', () => { const entries = fromJS([ { path: 'src/pages/index.md', data: { title: 'Root' } }, { path: 'src/pages/intro/index.md', data: { title: 'intro index' } }, { path: 'src/pages/intro/category/index.md', data: { title: 'intro category index' } }, { path: 'src/pages/compliance/index.md', data: { title: 'compliance index' } }, ]); const treeData = getTreeData(collection, entries); expect(treeData).toEqual([ { title: 'Pages', path: '/', isDir: true, isRoot: true, children: [ { title: 'intro', path: '/intro', isDir: true, isRoot: false, children: [ { title: 'category', path: '/intro/category', isDir: true, isRoot: false, children: [ { path: '/intro/category/index.md', data: { title: 'intro category index' }, title: 'intro category index', isDir: false, isRoot: false, children: [], }, ], }, { path: '/intro/index.md', data: { title: 'intro index' }, title: 'intro index', isDir: false, isRoot: false, children: [], }, ], }, { title: 'compliance', path: '/compliance', isDir: true, isRoot: false, children: [ { path: '/compliance/index.md', data: { title: 'compliance index' }, title: 'compliance index', isDir: false, isRoot: false, children: [], }, ], }, { path: '/index.md', data: { title: 'Root' }, title: 'Root', isDir: false, isRoot: false, children: [], }, ], }, ]); }); it('should ignore collection summary', () => { const entries = fromJS([{ path: 'src/pages/index.md', data: { title: 'Root' } }]); const treeData = getTreeData(collection, entries); expect(treeData).toEqual([ { title: 'Pages', path: '/', isDir: true, isRoot: true, children: [ { path: '/index.md', data: { title: 'Root' }, title: 'Root', isDir: false, isRoot: false, children: [], }, ], }, ]); }); it('should use nested collection summary for title', () => { const entries = fromJS([{ path: 'src/pages/index.md', data: { title: 'Root' } }]); const treeData = getTreeData( collection.setIn(['nested', 'summary'], '{{filename}}'), entries, ); expect(treeData).toEqual([ { title: 'Pages', path: '/', isDir: true, isRoot: true, children: [ { path: '/index.md', data: { title: 'Root' }, title: 'index', isDir: false, isRoot: false, children: [], }, ], }, ]); }); }); describe('walk', () => { it('should visit every tree node', () => { const entries = fromJS([ { path: 'src/pages/index.md', data: { title: 'Root' } }, { path: 'src/pages/dir1/index.md', data: { title: 'Dir1 File' } }, { path: 'src/pages/dir2/index.md', data: { title: 'Dir2 File' } }, ]); const treeData = getTreeData(collection, entries); const callback = jest.fn(); walk(treeData, callback); expect(callback).toHaveBeenCalledTimes(6); expect(callback).toHaveBeenCalledWith(expect.objectContaining({ path: '/' })); expect(callback).toHaveBeenCalledWith(expect.objectContaining({ path: '/index.md' })); expect(callback).toHaveBeenCalledWith(expect.objectContaining({ path: '/dir1' })); expect(callback).toHaveBeenCalledWith(expect.objectContaining({ path: '/dir2' })); expect(callback).toHaveBeenCalledWith(expect.objectContaining({ path: '/dir1/index.md' })); expect(callback).toHaveBeenCalledWith(expect.objectContaining({ path: '/dir2/index.md' })); }); }); describe('updateNode', () => { it('should update node', () => { const entries = fromJS([ { path: 'src/pages/index.md', data: { title: 'Root' } }, { path: 'src/pages/dir1/index.md', data: { title: 'Dir1 File' } }, { path: 'src/pages/dir2/index.md', data: { title: 'Dir2 File' } }, ]); const treeData = getTreeData(collection, entries); expect(treeData[0].children[0].children[0].expanded).toBeUndefined(); const callback = jest.fn(node => ({ ...node, expanded: true })); const node = { path: '/dir1/index.md' }; updateNode(treeData, node, callback); expect(callback).toHaveBeenCalledTimes(1); expect(callback).toHaveBeenCalledWith(node); expect(treeData[0].children[0].children[0].expanded).toEqual(true); }); }); });