From 6afb5062a2fcee0123132d9d411e746b5cb4de36 Mon Sep 17 00:00:00 2001 From: Sabina Talipova Date: Tue, 13 Feb 2024 13:21:01 +1300 Subject: [PATCH] ENH LinkField Jest tests --- .../LinkField/tests/LinkField-test.js | 123 ++++++--- .../LinkPicker/tests/LinkPicker-test.js | 3 + .../LinkPicker/tests/LinkPickerMenu-test.js | 76 ++++++ .../LinkPicker/tests/LinkPickerTitle-test.js | 238 +++++++++++------- client/src/tests/sample-test.js | 9 - 5 files changed, 320 insertions(+), 129 deletions(-) create mode 100644 client/src/components/LinkPicker/tests/LinkPickerMenu-test.js delete mode 100644 client/src/tests/sample-test.js diff --git a/client/src/components/LinkField/tests/LinkField-test.js b/client/src/components/LinkField/tests/LinkField-test.js index 96c3c4a7..9235388b 100644 --- a/client/src/components/LinkField/tests/LinkField-test.js +++ b/client/src/components/LinkField/tests/LinkField-test.js @@ -57,6 +57,67 @@ function makeProps(obj = {}) { }; } +describe('Render existing links', () => { + test('LinkField returns list of links if they exist', async () => { + const { container } = render(); + + await doResolve({ json: () => ({ + 1: { + Title: 'Page title', + typeKey: 'sitetree', + }, + 2: { + Title: 'Email title', + typeKey: 'email', + }, + }) }); + + await screen.findByText('Page title'); + + expect(container.querySelectorAll('.link-picker__button')).toHaveLength(2); + expect(container.querySelectorAll('.link-picker__button.font-icon-page')[0]).toHaveTextContent('Page title'); + expect(container.querySelectorAll('.link-picker__button.font-icon-email')[0]).toHaveTextContent('Email title'); + }); +}); + +describe('LinkField has disabled or readonly state', () => { + test('LinkField will render disabled state if disabled is true', async () => { + const { container } = render(); + doResolve(); + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + expect(container.querySelectorAll('.link-picker')).toHaveLength(1); + expect(container.querySelectorAll('.link-picker')[0]).toHaveTextContent('Cannot create link'); + }); + + test('LinkField will render readonly state if readonly is true', async () => { + const { container } = render(); + doResolve(); + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + expect(container.querySelectorAll('.link-picker')).toHaveLength(1); + expect(container.querySelectorAll('.link-picker')[0]).toHaveTextContent('Cannot create link'); + }); +}); + test('LinkField tab order', async () => { const user = userEvent.setup(); const { container } = render( { // e.g. el.getBoundingClientRect() will always return 0,0,0,0 }); -test('LinkField will render save-record-first div if ownerID is 0', async () => { - const { container } = render(); - expect(container.querySelectorAll('.link-field__save-record-first')).toHaveLength(1); - expect(container.querySelectorAll('.link-field__loading')).toHaveLength(0); - expect(container.querySelectorAll('.link-picker')).toHaveLength(0); -}); +describe('LinkField loading state', () => { + test('LinkField will render save-record-first div if ownerID is 0', async () => { + const { container } = render(); + expect(container.querySelectorAll('.link-field__save-record-first')).toHaveLength(1); + expect(container.querySelectorAll('.link-field__loading')).toHaveLength(0); + expect(container.querySelectorAll('.link-picker')).toHaveLength(0); + }); -test('LinkField will render loading indicator if ownerID is not 0', async () => { - const { container } = render(); - expect(container.querySelectorAll('.link-field__save-record-first')).toHaveLength(0); - expect(container.querySelectorAll('.link-field__loading')).toHaveLength(1); - expect(container.querySelectorAll('.link-picker')).toHaveLength(1); -}); + test('LinkField will render loading indicator if ownerID is not 0', async () => { + const { container } = render(); + expect(container.querySelectorAll('.link-field__save-record-first')).toHaveLength(0); + expect(container.querySelectorAll('.link-field__loading')).toHaveLength(1); + expect(container.querySelectorAll('.link-picker')).toHaveLength(1); + }); -test('LinkField will render link-picker if ownerID is not 0 and has finished loading', async () => { - const { container } = render(); - doResolve(); - // Short wait - we can't use screen.find* because we're waiting for something to be removed, not added to the DOM - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); + test('LinkField will render link-picker if ownerID is not 0 and has finished loading', async () => { + const { container } = render(); + doResolve(); + // Short wait - we can't use screen.find* because we're waiting for something to be removed, not added to the DOM + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + expect(container.querySelectorAll('.link-field__save-record-first')).toHaveLength(0); + expect(container.querySelectorAll('.link-field__loading')).toHaveLength(0); + expect(container.querySelectorAll('.link-picker')).toHaveLength(1); }); - expect(container.querySelectorAll('.link-field__save-record-first')).toHaveLength(0); - expect(container.querySelectorAll('.link-field__loading')).toHaveLength(0); - expect(container.querySelectorAll('.link-picker')).toHaveLength(1); }); diff --git a/client/src/components/LinkPicker/tests/LinkPicker-test.js b/client/src/components/LinkPicker/tests/LinkPicker-test.js index f9a0e59c..7c05ee75 100644 --- a/client/src/components/LinkPicker/tests/LinkPicker-test.js +++ b/client/src/components/LinkPicker/tests/LinkPicker-test.js @@ -1,6 +1,7 @@ /* global jest, test */ import React from 'react'; import { render, fireEvent, act } from '@testing-library/react'; +import '@testing-library/jest-dom'; import { LinkFieldContext } from 'components/LinkField/LinkField'; import LinkPicker from '../LinkPicker'; @@ -22,6 +23,7 @@ test('LinkPickerMenu render() should display toggle if can create', () => { ); expect(container.querySelectorAll('.link-picker__menu-toggle')).toHaveLength(1); + expect(container.querySelector('.link-picker__menu-toggle')).toHaveTextContent('Add Link'); expect(container.querySelectorAll('.link-picker__cannot-create')).toHaveLength(0); }); @@ -31,6 +33,7 @@ test('LinkPickerMenu render() should display cannot create message if cannot cre ); expect(container.querySelectorAll('.link-picker__menu-toggle')).toHaveLength(0); expect(container.querySelectorAll('.link-picker__cannot-create')).toHaveLength(1); + expect(container.querySelector('.link-picker__cannot-create')).toHaveTextContent('Cannot create link'); }); test('LinkPickerMenu render() should display cannot create message if types is empty', () => { diff --git a/client/src/components/LinkPicker/tests/LinkPickerMenu-test.js b/client/src/components/LinkPicker/tests/LinkPickerMenu-test.js new file mode 100644 index 00000000..1e3fcec5 --- /dev/null +++ b/client/src/components/LinkPicker/tests/LinkPickerMenu-test.js @@ -0,0 +1,76 @@ +/* global jest, test */ + +import React from 'react'; +import { render, fireEvent, act } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { LinkFieldContext } from 'components/LinkField/LinkField'; +import LinkPickerMenu from '../LinkPickerMenu'; + +function makeProps(obj = {}) { + return { + types: [ + { key: 'sitetree', title: 'Page', icon: 'font-icon-page', allowed: true }, + { key: 'external', title: 'External URL', icon: 'font-icon-link', allowed: true }, + { key: 'email', title: 'Email', icon: 'font-icon-email', allowed: true }, + { key: 'phone', title: 'Phone', icon: 'font-icon-phone', allowed: true }, + ], + onSelect: jest.fn(), + onKeyDownEdit: jest.fn(), + ...obj + }; +} +describe('Render LinkPickerMenu element', () => { + test('LinkPickerMenu render() should display link list', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.dropdown-item')).toHaveLength(4); + expect(container.querySelectorAll('.dropdown-item')[0]).toHaveTextContent('Page'); + expect(container.querySelectorAll('.dropdown-item')[1]).toHaveTextContent('External URL'); + expect(container.querySelectorAll('.dropdown-item')[2]).toHaveTextContent('Email'); + expect(container.querySelectorAll('.dropdown-item')[3]).toHaveTextContent('Phone'); + }); + + test('LinkPickerMenu render() should display link list with allowed SiteTreeLink and EmailLink', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.dropdown-item')).toHaveLength(2); + expect(container.querySelectorAll('.dropdown-item')[0]).toHaveTextContent('Page'); + expect(container.querySelectorAll('.dropdown-item')[0].firstChild).toHaveClass('font-icon-page'); + expect(container.querySelectorAll('.dropdown-item')[1]).toHaveTextContent('Email'); + expect(container.querySelectorAll('.dropdown-item')[1].firstChild).toHaveClass('font-icon-email'); + }); +}); + +describe('Handle user event on LinkPickerMenu element', () => { + test('LinkPickerMenu onSelect() should call onSelect with selected type', async () => { + const onSelect = jest.fn(); + const { container } = render( + + ); + await act(async () => { + await fireEvent.click(container.querySelectorAll('.dropdown-item')[1]); + }); + expect(onSelect).toHaveBeenCalledTimes(1); + expect(onSelect).toHaveBeenCalledWith('external'); + }); + + test('LinkPickerMenu onKeyDownEdit() should call onKeyDownEdit with selected type', async () => { + const onKeyDownEdit = jest.fn(); + const { container } = render( + + ); + await act(async () => { + await fireEvent.keyDown(container.querySelector('.dropdown-item'), { key: 'Enter', code: 'Enter', charCode: 13 }); + }); + expect(onKeyDownEdit).toHaveBeenCalledTimes(1); + }); +}); diff --git a/client/src/components/LinkPicker/tests/LinkPickerTitle-test.js b/client/src/components/LinkPicker/tests/LinkPickerTitle-test.js index 03e74c7b..02bfeb23 100644 --- a/client/src/components/LinkPicker/tests/LinkPickerTitle-test.js +++ b/client/src/components/LinkPicker/tests/LinkPickerTitle-test.js @@ -2,6 +2,7 @@ import React, { createRef } from 'react'; import { render, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; import { LinkFieldContext } from 'components/LinkField/LinkField'; import LinkPickerTitle from '../LinkPickerTitle'; @@ -30,110 +31,167 @@ function makeProps(obj = {}) { }; } -test('LinkPickerTitle render() should display clear button if can delete', () => { - const { container } = render( - - ); - expect(container.querySelectorAll('.link-picker__delete')).toHaveLength(1); - expect(container.querySelectorAll('.font-icon-phone')).toHaveLength(1); +describe('LinkPickerTitle render()', () => { + test('LinkPickerTitle render() should display link type title and link type icon', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__title')).toHaveLength(1); + expect(container.querySelector('.link-picker__title')).toHaveTextContent('My title'); + expect(container.querySelectorAll('.font-icon-phone')).toHaveLength(1); + expect(container.querySelector('.link-picker__type')).toHaveTextContent('Phone'); + expect(container.querySelector('.link-picker__url')).toHaveTextContent('My description'); + expect(container.querySelector('.link-picker__title > .badge')).toHaveTextContent('Draft'); + expect(container.querySelectorAll('.link-picker__title > .status-draft')).toHaveLength(1); + }); }); -test('LinkPickerTitle render() should not display clear button if cannot delete', () => { - const { container } = render( - - ); - expect(container.querySelectorAll('.link-picker__delete')).toHaveLength(0); -}); +describe('Render LinkPickerTitle delete button', () => { + test('LinkPickerTitle render() should display clear button if can delete', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__delete')).toHaveLength(1); + expect(container.querySelector('.link-picker__delete')).toHaveTextContent('Archive'); + expect(container.querySelector('.link-picker__delete').getAttribute('aria-label')).toBe('Archive'); + expect(container.querySelectorAll('.font-icon-phone')).toHaveLength(1); + }); -test('LinkPickerTitle render() should not display clear button if readonly', () => { - const { container } = render( - - ); - expect(container.querySelectorAll('.link-picker__delete')).toHaveLength(0); -}); + test('LinkPickerTitle render() should not display clear button if cannot delete', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__delete')).toHaveLength(0); + }); -test('LinkPickerTitle render() should not display clear button if disabled', () => { - const { container } = render( - - ); - expect(container.querySelectorAll('.link-picker__delete')).toHaveLength(0); -}); + test('LinkPickerTitle render() should not display clear button if readonly', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__delete')).toHaveLength(0); + }); -test('LinkPickerTitle render() should display link type icon', () => { - const { container } = render( - - ); - expect(container.querySelectorAll('.font-icon-phone')).toHaveLength(1); + test('LinkPickerTitle render() should not display clear button if disabled', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__delete')).toHaveLength(0); + }); }); -test('LinkPickerTitle delete button should fire the onDelete callback when not loading', async () => { - const mockOnDelete = jest.fn(); - const { container } = render( - - ); - fireEvent.click(container.querySelector('.link-picker__delete')); - expect(mockOnDelete).toHaveBeenCalledTimes(1); -}); +describe('LinkPickerTitle loading', () => { + test('LinkPickerTitle delete button should fire the onDelete callback when not loading', async () => { + const mockOnDelete = jest.fn(); + const { container } = render( + + ); + fireEvent.click(container.querySelector('.link-picker__delete')); + expect(mockOnDelete).toHaveBeenCalledTimes(1); + }); -test('LinkPickerTitle delete button should not fire the onDelete callback while loading', () => { - const mockOnDelete = jest.fn(); - const { container } = render( - - ); - fireEvent.click(container.querySelector('.link-picker__delete')); - expect(mockOnDelete).toHaveBeenCalledTimes(0); -}); + test('LinkPickerTitle delete button should not fire the onDelete callback while loading', () => { + const mockOnDelete = jest.fn(); + const { container } = render( + + ); + fireEvent.click(container.querySelector('.link-picker__delete')); + expect(mockOnDelete).toHaveBeenCalledTimes(0); + }); -test('LinkPickerTitle main button should fire the onClick callback when not loading', async () => { - const mockOnClick = jest.fn(); - const { container } = render( - - ); - fireEvent.click(container.querySelector('.link-picker__button')); - expect(mockOnClick).toHaveBeenCalledTimes(1); -}); + test('LinkPickerTitle main button should fire the onClick callback when not loading', async () => { + const mockOnClick = jest.fn(); + const { container } = render( + + ); + fireEvent.click(container.querySelector('.link-picker__button')); + expect(mockOnClick).toHaveBeenCalledTimes(1); + }); -test('LinkPickerTitle main button should not fire the onClick callback while loading', async () => { - const mockOnClick = jest.fn(); - const { container } = render( - - ); - fireEvent.click(container.querySelector('.link-picker__button')); - expect(mockOnClick).toHaveBeenCalledTimes(0); + test('LinkPickerTitle main button should not fire the onClick callback while loading', async () => { + const mockOnClick = jest.fn(); + const { container } = render( + + ); + fireEvent.click(container.querySelector('.link-picker__button')); + expect(mockOnClick).toHaveBeenCalledTimes(0); + }); }); -test('LinkPickerTitle render() should have readonly class if set to readonly', () => { - const { container } = render( - - ); - expect(container.querySelectorAll('.link-picker__link--readonly')).toHaveLength(1); -}); +describe('LinkPickerTitle is disabled or readonly', () => { + test('LinkPickerTitle render() should have readonly class if set to readonly', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__link--readonly')).toHaveLength(1); + }); -test('LinkPickerTitle render() should not have readonly class if set to readonly', () => { - const { container } = render( - - ); - expect(container.querySelectorAll('.link-picker__link--readonly')).toHaveLength(0); -}); + test('LinkPickerTitle render() should not have readonly class if set to readonly', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__link--readonly')).toHaveLength(0); + }); + + test('LinkPickerTitle render() should have disabled class if set to disabled', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__link--disabled')).toHaveLength(1); + }); -test('LinkPickerTitle render() should have disabled class if set to disabled', () => { - const { container } = render( - - ); - expect(container.querySelectorAll('.link-picker__link--disabled')).toHaveLength(1); + test('LinkPickerTitle render() should not have disabled class if set to disabled', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__link--disabled')).toHaveLength(0); + }); }); -test('LinkPickerTitle render() should not have disabled class if set to disabled', () => { - const { container } = render( - - ); - expect(container.querySelectorAll('.link-picker__link--disabled')).toHaveLength(0); +describe('Handle event on drag and drop element', () => { + test('dnd handler is displayed on LinkPickerTitle on MultiLinkField', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__drag-handle')).toHaveLength(1); + }); + + test('dnd handler is not displayed if link field is disabled', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__drag-handle')).toHaveLength(0); + }); + + test('dnd handler is not displayed if link field is readonly', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__drag-handle')).toHaveLength(0); + }); + + test('dnd handler is not displayed if link field is not MultiLinkField', () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__drag-handle')).toHaveLength(0); + }); + + test('keydown on dnd handler', async () => { + const { container } = render( + + ); + expect(container.querySelectorAll('.link-picker__drag-handle')).toHaveLength(1); + container.querySelector('.link-picker__drag-handle').focus(); + fireEvent.keyDown(document.activeElement || document.body, { key: 'Enter', code: 'Enter', charCode: 13 }); + expect(container.querySelector('.link-picker__drag-handle').getAttribute('aria-pressed')).toBe('true'); + expect(container.querySelector('.link-picker__drag-handle').getAttribute('aria-label')).toBe('Sort Links'); + }); }); diff --git a/client/src/tests/sample-test.js b/client/src/tests/sample-test.js deleted file mode 100644 index 47bc3714..00000000 --- a/client/src/tests/sample-test.js +++ /dev/null @@ -1,9 +0,0 @@ -/* global jest, describe, it, expect */ -/* eslint-disable */ - -describe('sample tests', () => { - it('sample test', () => { - const css = 'sample css'; - expect(css).toBe('sample css'); - }); -});