From 621584daaaaab1e2c7256978350d539824f0b919 Mon Sep 17 00:00:00 2001 From: Szymon Oleksy Date: Tue, 3 Dec 2024 17:11:34 +0100 Subject: [PATCH 01/13] feat: dimmed as default disable method --- ui/src/components/ConfigurationFormView.jsx | 2 +- ui/src/components/DeleteModal/DeleteModal.tsx | 4 ++-- ui/src/components/EntityModal/EntityModal.tsx | 4 ++-- ui/src/components/EntityPage/EntityPage.tsx | 4 ++-- ui/src/components/InteractAllStatusButton.tsx | 4 ++-- ui/src/components/TextComponent/TextComponent.tsx | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ui/src/components/ConfigurationFormView.jsx b/ui/src/components/ConfigurationFormView.jsx index 8bab71111..06a67d6f0 100644 --- a/ui/src/components/ConfigurationFormView.jsx +++ b/ui/src/components/ConfigurationFormView.jsx @@ -93,7 +93,7 @@ function ConfigurationFormView({ serviceName }) { appearance="primary" label={isSubmitting ? : _('Save')} onClick={handleSubmit} - disabled={isSubmitting} + disabled={isSubmitting && 'dimmed'} /> diff --git a/ui/src/components/DeleteModal/DeleteModal.tsx b/ui/src/components/DeleteModal/DeleteModal.tsx index d989d1ac2..436450d5a 100644 --- a/ui/src/components/DeleteModal/DeleteModal.tsx +++ b/ui/src/components/DeleteModal/DeleteModal.tsx @@ -113,13 +113,13 @@ class DeleteModal extends Component { appearance="secondary" onClick={this.handleRequestClose} label={_('Cancel')} - disabled={this.state.isDeleting} + disabled={this.state.isDeleting && 'dimmed'} /> : _('Delete')} onClick={this.handleDelete} - disabled={this.state.isDeleting} + disabled={this.state.isDeleting && 'dimmed'} /> diff --git a/ui/src/components/EntityModal/EntityModal.tsx b/ui/src/components/EntityModal/EntityModal.tsx index dabe627e8..abf60ad71 100644 --- a/ui/src/components/EntityModal/EntityModal.tsx +++ b/ui/src/components/EntityModal/EntityModal.tsx @@ -102,14 +102,14 @@ class EntityModal extends Component { appearance="secondary" onClick={this.handleRequestClose} label={_('Cancel')} - disabled={this.state.isSubmititng} + disabled={this.state.isSubmititng && 'dimmed'} /> : this.buttonText} onClick={this.handleSubmit} - disabled={this.state.isSubmititng} + disabled={this.state.isSubmititng && 'dimmed'} /> diff --git a/ui/src/components/EntityPage/EntityPage.tsx b/ui/src/components/EntityPage/EntityPage.tsx index 181d6b0fd..36508940d 100644 --- a/ui/src/components/EntityPage/EntityPage.tsx +++ b/ui/src/components/EntityPage/EntityPage.tsx @@ -112,7 +112,7 @@ function EntityPage({ appearance="secondary" onClick={handleRequestClose} label={_('Cancel')} - disabled={isSubmitting} + disabled={isSubmitting && 'dimmed'} style={{ width: '80px' }} /> : buttonText} onClick={handleSubmit} - disabled={isSubmitting} + disabled={isSubmitting && 'dimmed'} style={{ width: '80px' }} /> diff --git a/ui/src/components/InteractAllStatusButton.tsx b/ui/src/components/InteractAllStatusButton.tsx index a5b53704c..49631fd2d 100644 --- a/ui/src/components/InteractAllStatusButton.tsx +++ b/ui/src/components/InteractAllStatusButton.tsx @@ -57,7 +57,7 @@ export function InteractAllStatusButtons(props: DisableAllStatusButtonProps) { setIsDisabling(false); }} role="button" - disabled={props.dataRows.length < 1} + disabled={props.dataRows.length < 1 && 'dimmed'} > Activate all @@ -68,7 +68,7 @@ export function InteractAllStatusButtons(props: DisableAllStatusButtonProps) { setIsDisabling(true); }} role="button" - disabled={props.dataRows.length < 1} + disabled={props.dataRows.length < 1 && 'dimmed'} > Deactivate all diff --git a/ui/src/components/TextComponent/TextComponent.tsx b/ui/src/components/TextComponent/TextComponent.tsx index ede7349ce..70e63e8f0 100755 --- a/ui/src/components/TextComponent/TextComponent.tsx +++ b/ui/src/components/TextComponent/TextComponent.tsx @@ -29,7 +29,7 @@ class TextComponent extends Component { inline error={this.props.error} className={this.props.field} - disabled={this.props.disabled} + disabled={this.props.disabled && 'dimmed'} value={ this.props.value === null || typeof this.props.value === 'undefined' ? '' From bed3bb7bf8cc01323fcc1f51969a066fc7e52b56 Mon Sep 17 00:00:00 2001 From: Szymon Oleksy Date: Tue, 3 Dec 2024 17:42:33 +0100 Subject: [PATCH 02/13] test: align tests to dimmed disabled version --- .../EntityModal/EntityModal.test.tsx | 21 ++++++++++++------- .../FormModifications.test.tsx | 12 +++++++---- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/ui/src/components/EntityModal/EntityModal.test.tsx b/ui/src/components/EntityModal/EntityModal.test.tsx index c18ddf671..1a7f3c867 100644 --- a/ui/src/components/EntityModal/EntityModal.test.tsx +++ b/ui/src/components/EntityModal/EntityModal.test.tsx @@ -62,7 +62,8 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).not.toHaveAttribute('disabled'); + expect(oauthTextBox).not.toHaveAttribute('readonly'); + expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('false'); }); it('Oauth Oauth - disableonEdit = true, oauth field disabled on edit', async () => { @@ -82,7 +83,8 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toHaveAttribute('disabled'); + expect(oauthTextBox).toHaveAttribute('readonly'); + expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('true'); }); it('Oauth Basic - Enable field equal false, so field disabled', async () => { @@ -102,7 +104,8 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { const oauthTextBox = getDisabledBasicField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toHaveAttribute('disabled'); + expect(oauthTextBox).toHaveAttribute('readonly'); + expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('true'); }); it('if oauth field not disabled with create after disableonEdit true', async () => { @@ -120,7 +123,8 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledBasicField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).not.toHaveAttribute('disabled'); + expect(oauthTextBox).not.toHaveAttribute('readonly'); + expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('false'); }); }); @@ -163,7 +167,8 @@ describe('Options - Enable field property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toHaveAttribute('disabled'); + expect(oauthTextBox).toHaveAttribute('readonly'); + expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('true'); }); it('Oauth Basic - Enable field equal false, so field disabled', async () => { @@ -181,7 +186,8 @@ describe('Options - Enable field property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toHaveAttribute('disabled'); + expect(oauthTextBox).toHaveAttribute('readonly'); + expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('true'); }); it('Oauth Basic - Fully enabled field, enabled: true, disableonEdit: false', async () => { @@ -199,7 +205,8 @@ describe('Options - Enable field property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).not.toHaveAttribute('disabled'); + expect(oauthTextBox).not.toHaveAttribute('readonly'); + expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('false'); }); }); diff --git a/ui/src/components/FormModifications/FormModifications.test.tsx b/ui/src/components/FormModifications/FormModifications.test.tsx index 5a0f21d12..697cc714e 100644 --- a/ui/src/components/FormModifications/FormModifications.test.tsx +++ b/ui/src/components/FormModifications/FormModifications.test.tsx @@ -127,18 +127,22 @@ it('verify modification after text components change', async () => { expect(parentElement).toHaveTextContent(mods.label); }; - expect(componentInput).toBeDisabled(); + expect(componentInput).toHaveAttribute('readonly'); + expect(componentInput.getAttribute('aria-disabled')).toBe('true'); verifyAllProps(componentParentElement, componentInput, mods1Field1); - expect(component2Input).toBeDisabled(); + expect(component2Input).toHaveAttribute('readonly'); + expect(component2Input.getAttribute('aria-disabled')).toBe('true'); verifyAllProps(component2ParentElement, component2Input, mods1Field2); await userEvent.type(componentMakingModsTextBox1, secondValueToInput); - expect(componentInput).toBeEnabled(); + expect(componentInput).not.toHaveAttribute('readonly'); + expect(componentInput.getAttribute('aria-disabled')).toBe('false'); verifyAllProps(componentParentElement, componentInput, mods2Field1); - expect(component2Input).toBeEnabled(); + expect(component2Input).not.toHaveAttribute('readonly'); + expect(component2Input.getAttribute('aria-disabled')).toBe('false'); verifyAllProps(component2ParentElement, component2Input, mods2Field2); }); From bb9dbeb7872b8ffd71a91b8980df4ccfae0ba27b Mon Sep 17 00:00:00 2001 From: Szymon Oleksy Date: Tue, 3 Dec 2024 17:57:12 +0100 Subject: [PATCH 03/13] test: add storybook with text field prosp true --- .../TextComponent/stories/TextComponent.stories.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ui/src/components/TextComponent/stories/TextComponent.stories.tsx b/ui/src/components/TextComponent/stories/TextComponent.stories.tsx index c3eb48128..f6331a5bf 100644 --- a/ui/src/components/TextComponent/stories/TextComponent.stories.tsx +++ b/ui/src/components/TextComponent/stories/TextComponent.stories.tsx @@ -38,3 +38,14 @@ export const Base: Story = { disabled: false, }, }; + +export const AllPropsTrue: Story = { + args: { + handleChange: fn(), + value: 'default value', + field: 'field', + error: true, + encrypted: true, + disabled: true, + }, +}; From cf2fa848659d02002a5c412a33bdb222c7907992 Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:01:20 +0000 Subject: [PATCH 04/13] update screenshots --- .../__images__/TextComponent-all-props-true-chromium.png | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 ui/src/components/TextComponent/stories/__images__/TextComponent-all-props-true-chromium.png diff --git a/ui/src/components/TextComponent/stories/__images__/TextComponent-all-props-true-chromium.png b/ui/src/components/TextComponent/stories/__images__/TextComponent-all-props-true-chromium.png new file mode 100644 index 000000000..3daaf24fb --- /dev/null +++ b/ui/src/components/TextComponent/stories/__images__/TextComponent-all-props-true-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3f3fdd40742f1150cc38601eef1ebf351e47595c3a184834a246e5805ba6c24 +size 3722 From d7b8eade4fd1b76478e41cd71571020dd88fed5b Mon Sep 17 00:00:00 2001 From: Szymon Oleksy Date: Tue, 3 Dec 2024 18:02:47 +0100 Subject: [PATCH 05/13] ci: trigger From 199ee82d52f8a22b2ac4340a18c1a1f3bda99da2 Mon Sep 17 00:00:00 2001 From: Szymon Oleksy Date: Thu, 5 Dec 2024 01:48:53 +0100 Subject: [PATCH 06/13] chore: new button component --- ui/src/components/AcceptModal/AcceptModal.tsx | 9 +++--- ui/src/components/Button/Button.tsx | 29 +++++++++++++++++++ ui/src/components/ConfigurationFormView.jsx | 10 +++---- .../DashboardInfoModal/DashboardInfoModal.tsx | 7 ++--- ui/src/components/DeleteModal/DeleteModal.tsx | 16 +++++----- ui/src/components/EntityModal/EntityModal.tsx | 20 ++++++------- ui/src/components/EntityPage/EntityPage.tsx | 15 +++++----- ui/src/components/ErrorModal/ErrorModal.tsx | 4 +-- ui/src/components/InteractAllStatusButton.tsx | 4 +-- ui/src/components/MenuInput/MenuInput.tsx | 15 +++------- ui/src/components/table/TableHeader.jsx | 10 ++----- ui/src/pages/Dashboard/DataIngestionModal.tsx | 6 ++-- 12 files changed, 76 insertions(+), 69 deletions(-) create mode 100644 ui/src/components/Button/Button.tsx diff --git a/ui/src/components/AcceptModal/AcceptModal.tsx b/ui/src/components/AcceptModal/AcceptModal.tsx index 27b5f088e..c353f3ecc 100644 --- a/ui/src/components/AcceptModal/AcceptModal.tsx +++ b/ui/src/components/AcceptModal/AcceptModal.tsx @@ -2,7 +2,8 @@ import React from 'react'; import Modal from '@splunk/react-ui/Modal'; import Message from '@splunk/react-ui/Message'; import styled from 'styled-components'; -import { StyledButton } from '../../pages/EntryPageStyle'; + +import { UCCButton } from '../Button/Button'; const ModalWrapper = styled(Modal)` width: 600px; @@ -30,13 +31,11 @@ function AcceptModal(props: AcceptModalProps) { - props.handleRequestClose(false)} label={props.declineBtnLabel || 'Cancel'} /> - props.handleRequestClose(true)} label={props.acceptBtnLabel || 'OK'} /> diff --git a/ui/src/components/Button/Button.tsx b/ui/src/components/Button/Button.tsx new file mode 100644 index 000000000..cf708ab2b --- /dev/null +++ b/ui/src/components/Button/Button.tsx @@ -0,0 +1,29 @@ +import React, { ComponentProps } from 'react'; + +import Button from '@splunk/react-ui/Button'; +import WaitSpinner from '@splunk/react-ui/WaitSpinner'; +import styled from 'styled-components'; + +const StyledButton = styled(Button)` + min-width: 80px; +`; + +type Props = { + label: string; + appearance?: 'default' | 'secondary' | 'primary' | 'destructive' | 'pill' | 'toggle' | 'flat'; + disabled?: boolean; + loading?: boolean; +} & Partial>; + +export const UCCButton = React.forwardRef( + ({ disabled, loading, appearance, ...rest }, ref) => ( + : rest.icon} + label={loading ? '' : rest.label} // do not display text nor icon when loading + appearance={appearance || 'primary'} + disabled={(disabled || loading) && 'dimmed'} + /> // disable when loading + ) +); diff --git a/ui/src/components/ConfigurationFormView.jsx b/ui/src/components/ConfigurationFormView.jsx index 06a67d6f0..e5abf7901 100644 --- a/ui/src/components/ConfigurationFormView.jsx +++ b/ui/src/components/ConfigurationFormView.jsx @@ -3,10 +3,9 @@ import PropTypes from 'prop-types'; import { _ } from '@splunk/ui-utils/i18n'; import styled from 'styled-components'; -import WaitSpinner from '@splunk/react-ui/WaitSpinner'; import BaseFormView from './BaseFormView/BaseFormView'; -import { StyledButton } from '../pages/EntryPageStyle'; +import { UCCButton } from './Button/Button'; import { getRequest, generateEndPointUrl } from '../util/api'; import { MODE_CONFIG } from '../constants/modes'; import { WaitSpinnerWrapper } from './table/CustomTableStyle'; @@ -88,12 +87,11 @@ function ConfigurationFormView({ serviceName }) { )} - : _('Save')} + label={_('Save')} onClick={handleSubmit} - disabled={isSubmitting && 'dimmed'} + loading={isSubmitting} /> diff --git a/ui/src/components/DashboardInfoModal/DashboardInfoModal.tsx b/ui/src/components/DashboardInfoModal/DashboardInfoModal.tsx index 7745c79d6..aa894d099 100644 --- a/ui/src/components/DashboardInfoModal/DashboardInfoModal.tsx +++ b/ui/src/components/DashboardInfoModal/DashboardInfoModal.tsx @@ -6,7 +6,7 @@ import Heading from '@splunk/react-ui/Heading'; import P from '@splunk/react-ui/Paragraph'; import QuestionCircle from '@splunk/react-icons/QuestionCircle'; -import { StyledButton } from '../../pages/EntryPageStyle'; +import { UCCButton } from '../Button/Button'; const ModalWrapper = styled(Modal)` width: 700px; @@ -48,15 +48,14 @@ function DashboardInfoModal(props: DashboardInfoModalProps) { {props?.troubleshootingButton?.link ? ( // to do change it into troubleshooting link - } to={props?.troubleshootingButton?.link} label={props.troubleshootingButton?.label || 'Troubleshooting {add-on}'} openInNewContext /> ) : null} - props.handleRequestClose()} label={props.closeBtnLabel || 'Close'} /> diff --git a/ui/src/components/DeleteModal/DeleteModal.tsx b/ui/src/components/DeleteModal/DeleteModal.tsx index 436450d5a..52a194da9 100644 --- a/ui/src/components/DeleteModal/DeleteModal.tsx +++ b/ui/src/components/DeleteModal/DeleteModal.tsx @@ -2,17 +2,16 @@ import React, { Component } from 'react'; import Modal from '@splunk/react-ui/Modal'; import Message from '@splunk/react-ui/Message'; import styled from 'styled-components'; -import WaitSpinner from '@splunk/react-ui/WaitSpinner'; import update from 'immutability-helper'; import { _ } from '@splunk/ui-utils/i18n'; -import { generateToast } from '../../util/util'; -import { StyledButton } from '../../pages/EntryPageStyle'; +import { generateToast } from '../../util/util'; import { deleteRequest, generateEndPointUrl } from '../../util/api'; import TableContext from '../../context/TableContext'; import { parseErrorMsg, getFormattedMessage } from '../../util/messageUtil'; import { PAGE_INPUT } from '../../constants/pages'; import { StandardPages } from '../../types/components/shareableTypes'; +import { UCCButton } from '../Button/Button'; const ModalWrapper = styled(Modal)` width: 800px; @@ -109,17 +108,16 @@ class DeleteModal extends Component {

{deleteMsg}

- - : _('Delete')} + diff --git a/ui/src/components/EntityModal/EntityModal.tsx b/ui/src/components/EntityModal/EntityModal.tsx index abf60ad71..10442dffa 100644 --- a/ui/src/components/EntityModal/EntityModal.tsx +++ b/ui/src/components/EntityModal/EntityModal.tsx @@ -1,15 +1,14 @@ -import React, { Component, ReactElement } from 'react'; +import React, { Component } from 'react'; import Modal from '@splunk/react-ui/Modal'; import styled from 'styled-components'; -import WaitSpinner from '@splunk/react-ui/WaitSpinner'; import { _ } from '@splunk/ui-utils/i18n'; - import { ButtonClickHandler } from '@splunk/react-ui/Button'; + import { Mode, MODE_CLONE, MODE_CREATE, MODE_EDIT } from '../../constants/modes'; -import { StyledButton } from '../../pages/EntryPageStyle'; import BaseFormView from '../BaseFormView/BaseFormView'; import { StandardPages } from '../../types/components/shareableTypes'; import PageContext from '../../context/PageContext'; +import { UCCButton } from '../Button/Button'; const ModalWrapper = styled(Modal)` width: 800px; @@ -33,7 +32,7 @@ interface EntityModalState { class EntityModal extends Component { form: React.RefObject; - buttonText: string | ReactElement; + buttonText: string; constructor(props: EntityModalProps) { super(props); @@ -98,18 +97,17 @@ class EntityModal extends Component { - - : this.buttonText} + label={this.buttonText} + loading={this.state.isSubmititng} onClick={this.handleSubmit} - disabled={this.state.isSubmititng && 'dimmed'} /> diff --git a/ui/src/components/EntityPage/EntityPage.tsx b/ui/src/components/EntityPage/EntityPage.tsx index 36508940d..1d8e5a9a0 100644 --- a/ui/src/components/EntityPage/EntityPage.tsx +++ b/ui/src/components/EntityPage/EntityPage.tsx @@ -1,7 +1,6 @@ import React, { memo, useRef, useState } from 'react'; import Link from '@splunk/react-ui/Link'; -import WaitSpinner from '@splunk/react-ui/WaitSpinner'; import ColumnLayout from '@splunk/react-ui/ColumnLayout'; import { _ } from '@splunk/ui-utils/i18n'; import { variables } from '@splunk/themes'; @@ -9,13 +8,14 @@ import { variables } from '@splunk/themes'; import Heading from '@splunk/react-ui/Heading'; import styled from 'styled-components'; import { ButtonClickHandler } from '@splunk/react-ui/Button'; + import { MODE_CLONE, MODE_CREATE, MODE_EDIT, Mode } from '../../constants/modes'; import BaseFormView from '../BaseFormView/BaseFormView'; import { SubTitleComponent } from '../../pages/Input/InputPageStyle'; import { PAGE_INPUT } from '../../constants/pages'; -import { StyledButton } from '../../pages/EntryPageStyle'; import { StandardPages } from '../../types/components/shareableTypes'; import PageContext from '../../context/PageContext'; +import { UCCButton } from '../Button/Button'; interface EntityPageProps { handleRequestClose: () => void; @@ -108,19 +108,18 @@ function EntityPage({ - - : buttonText} + label={buttonText} onClick={handleSubmit} - disabled={isSubmitting && 'dimmed'} + loading={isSubmitting} style={{ width: '80px' }} /> diff --git a/ui/src/components/ErrorModal/ErrorModal.tsx b/ui/src/components/ErrorModal/ErrorModal.tsx index 6153609f9..72da21ff4 100644 --- a/ui/src/components/ErrorModal/ErrorModal.tsx +++ b/ui/src/components/ErrorModal/ErrorModal.tsx @@ -4,7 +4,7 @@ import Message from '@splunk/react-ui/Message'; import styled from 'styled-components'; import { getFormattedMessage } from '../../util/messageUtil'; -import { StyledButton } from '../../pages/EntryPageStyle'; +import { UCCButton } from '../Button/Button'; const ModalWrapper = styled(Modal)` width: 600px; @@ -31,7 +31,7 @@ function ErrorModal(props: ErrorModalProps) { - + ); diff --git a/ui/src/components/InteractAllStatusButton.tsx b/ui/src/components/InteractAllStatusButton.tsx index 49631fd2d..a5b53704c 100644 --- a/ui/src/components/InteractAllStatusButton.tsx +++ b/ui/src/components/InteractAllStatusButton.tsx @@ -57,7 +57,7 @@ export function InteractAllStatusButtons(props: DisableAllStatusButtonProps) { setIsDisabling(false); }} role="button" - disabled={props.dataRows.length < 1 && 'dimmed'} + disabled={props.dataRows.length < 1} > Activate all @@ -68,7 +68,7 @@ export function InteractAllStatusButtons(props: DisableAllStatusButtonProps) { setIsDisabling(true); }} role="button" - disabled={props.dataRows.length < 1 && 'dimmed'} + disabled={props.dataRows.length < 1} > Deactivate all diff --git a/ui/src/components/MenuInput/MenuInput.tsx b/ui/src/components/MenuInput/MenuInput.tsx index 430cd5665..f6c9fce45 100644 --- a/ui/src/components/MenuInput/MenuInput.tsx +++ b/ui/src/components/MenuInput/MenuInput.tsx @@ -8,13 +8,14 @@ import ChevronLeft from '@splunk/react-icons/ChevronLeft'; import { _ as i18n } from '@splunk/ui-utils/i18n'; import styled from 'styled-components'; import { variables } from '@splunk/themes'; + import { getFormattedMessage } from '../../util/messageUtil'; import { getUnifiedConfigs } from '../../util/util'; import CustomMenu from '../CustomMenu'; -import { StyledButton } from '../../pages/EntryPageStyle'; import { invariant } from '../../util/invariant'; import { usePageContext } from '../../context/usePageContext'; import { shouldHideForPlatform } from '../../util/pageContext'; +import { UCCButton } from '../Button/Button'; const CustomSubTitle = styled.span` color: ${variables.brandColorD20}; @@ -67,14 +68,7 @@ function MenuInput({ handleRequestOpen }: MenuInputProps) { }, [inputs.services, pageContext.platform]); const closeReasons = ['clickAway', 'escapeKey', 'offScreen', 'toggleClick']; - const toggle = ( - - ); + const toggle = ; useEffect(() => { if (!isSubMenu) { @@ -211,9 +205,8 @@ function MenuInput({ handleRequestOpen }: MenuInputProps) { // Making a dropdown if we have one service const makeInputButton = () => ( - { handleRequestOpen({ serviceName: services[0].name }); diff --git a/ui/src/components/table/TableHeader.jsx b/ui/src/components/table/TableHeader.jsx index ff3daf6c0..6d3f823b9 100644 --- a/ui/src/components/table/TableHeader.jsx +++ b/ui/src/components/table/TableHeader.jsx @@ -7,10 +7,10 @@ import { Typography } from '@splunk/react-ui/Typography'; import styled from 'styled-components'; import { _ } from '@splunk/ui-utils/i18n'; +import { UCCButton } from '../Button/Button'; import TableFilter from './TableFilter'; import { TableSelectBoxWrapper } from './CustomTableStyle'; import { PAGE_INPUT } from '../../constants/pages'; -import { StyledButton } from '../../pages/EntryPageStyle'; import { InteractAllStatusButtons } from '../InteractAllStatusButton'; import { useTableContext } from '../../context/useTableContext'; @@ -124,13 +124,7 @@ function TableHeader({ alwaysShowLastPageLink totalPages={Math.ceil(totalElement / pageSize)} /> - {isTabs && ( - - )} + {isTabs && } - From d28572937c0f8381732742c5e138edebcd567d61 Mon Sep 17 00:00:00 2001 From: Szymon Oleksy Date: Thu, 5 Dec 2024 02:11:56 +0100 Subject: [PATCH 07/13] test: review --- .../EntityModal/EntityModal.test.tsx | 33 +++++++++++-------- .../FormModifications.test.tsx | 10 +++--- .../MultiInputComponent.test.tsx | 2 +- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/ui/src/components/EntityModal/EntityModal.test.tsx b/ui/src/components/EntityModal/EntityModal.test.tsx index 1a7f3c867..2cdc15f2a 100644 --- a/ui/src/components/EntityModal/EntityModal.test.tsx +++ b/ui/src/components/EntityModal/EntityModal.test.tsx @@ -27,6 +27,18 @@ import { StandardPages } from '../../types/components/shareableTypes'; import { server } from '../../mocks/server'; import { invariant } from '../../util/invariant'; +const isTextDisabled = (field: HTMLElement | null | Element) => { + invariant(field); + expect(field).toHaveAttribute('readonly'); + expect(field).toHaveAttribute('aria-disabled', 'true'); +}; + +const isTextEnabled = (field: HTMLElement | null | Element) => { + invariant(field); + expect(field).not.toHaveAttribute('readonly'); + expect(field).toHaveAttribute('aria-disabled', 'false'); +}; + describe('Oauth field disabled on edit - diableonEdit property', () => { const handleRequestClose = jest.fn(); @@ -62,8 +74,7 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).not.toHaveAttribute('readonly'); - expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('false'); + isTextEnabled(oauthTextBox); }); it('Oauth Oauth - disableonEdit = true, oauth field disabled on edit', async () => { @@ -83,8 +94,7 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toHaveAttribute('readonly'); - expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('true'); + isTextDisabled(oauthTextBox); }); it('Oauth Basic - Enable field equal false, so field disabled', async () => { @@ -104,8 +114,7 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { const oauthTextBox = getDisabledBasicField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toHaveAttribute('readonly'); - expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('true'); + isTextDisabled(oauthTextBox); }); it('if oauth field not disabled with create after disableonEdit true', async () => { @@ -123,8 +132,7 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledBasicField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).not.toHaveAttribute('readonly'); - expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('false'); + isTextEnabled(oauthTextBox); }); }); @@ -167,8 +175,7 @@ describe('Options - Enable field property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toHaveAttribute('readonly'); - expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('true'); + isTextDisabled(oauthTextBox); }); it('Oauth Basic - Enable field equal false, so field disabled', async () => { @@ -186,8 +193,7 @@ describe('Options - Enable field property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toHaveAttribute('readonly'); - expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('true'); + isTextDisabled(oauthTextBox); }); it('Oauth Basic - Fully enabled field, enabled: true, disableonEdit: false', async () => { @@ -205,8 +211,7 @@ describe('Options - Enable field property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).not.toHaveAttribute('readonly'); - expect(oauthTextBox?.getAttribute('aria-disabled')).toBe('false'); + isTextEnabled(oauthTextBox); }); }); diff --git a/ui/src/components/FormModifications/FormModifications.test.tsx b/ui/src/components/FormModifications/FormModifications.test.tsx index 697cc714e..e348f6492 100644 --- a/ui/src/components/FormModifications/FormModifications.test.tsx +++ b/ui/src/components/FormModifications/FormModifications.test.tsx @@ -128,21 +128,23 @@ it('verify modification after text components change', async () => { }; expect(componentInput).toHaveAttribute('readonly'); - expect(componentInput.getAttribute('aria-disabled')).toBe('true'); + expect(componentInput).toHaveAttribute('aria-disabled', 'true'); + verifyAllProps(componentParentElement, componentInput, mods1Field1); expect(component2Input).toHaveAttribute('readonly'); - expect(component2Input.getAttribute('aria-disabled')).toBe('true'); + expect(component2Input).toHaveAttribute('aria-disabled', 'true'); + verifyAllProps(component2ParentElement, component2Input, mods1Field2); await userEvent.type(componentMakingModsTextBox1, secondValueToInput); expect(componentInput).not.toHaveAttribute('readonly'); - expect(componentInput.getAttribute('aria-disabled')).toBe('false'); + expect(componentInput).toHaveAttribute('aria-disabled', 'false'); verifyAllProps(componentParentElement, componentInput, mods2Field1); expect(component2Input).not.toHaveAttribute('readonly'); - expect(component2Input.getAttribute('aria-disabled')).toBe('false'); + expect(component2Input).toHaveAttribute('aria-disabled', 'false'); verifyAllProps(component2ParentElement, component2Input, mods2Field2); }); diff --git a/ui/src/components/MultiInputComponent/MultiInputComponent.test.tsx b/ui/src/components/MultiInputComponent/MultiInputComponent.test.tsx index 30958e470..fed1af8a3 100644 --- a/ui/src/components/MultiInputComponent/MultiInputComponent.test.tsx +++ b/ui/src/components/MultiInputComponent/MultiInputComponent.test.tsx @@ -76,7 +76,7 @@ it('renders as disabled correctly', () => { renderFeature({ disabled: true }); const inputComponent = screen.getByTestId('multiselect'); expect(inputComponent).toBeInTheDocument(); - expect(inputComponent.getAttribute('aria-disabled')).toEqual('true'); + expect(inputComponent).toHaveAttribute('aria-disabled', 'true'); }); it.each(defaultInputProps.controlOptions.items)('handler called correctly', async (item) => { From b98bac8b7ba74bc5f304ae6e10981f660f6393eb Mon Sep 17 00:00:00 2001 From: Szymon Oleksy Date: Thu, 5 Dec 2024 07:56:57 +0100 Subject: [PATCH 08/13] chore: rename button component --- ui/src/components/AcceptModal/AcceptModal.tsx | 2 +- ui/src/components/ConfigurationFormView.jsx | 2 +- ui/src/components/DashboardInfoModal/DashboardInfoModal.tsx | 2 +- ui/src/components/DeleteModal/DeleteModal.tsx | 2 +- ui/src/components/EntityModal/EntityModal.tsx | 2 +- ui/src/components/EntityPage/EntityPage.tsx | 2 +- ui/src/components/ErrorModal/ErrorModal.tsx | 2 +- ui/src/components/MenuInput/MenuInput.tsx | 2 +- .../components/{Button/Button.tsx => UCCButton/UCCButton.tsx} | 0 ui/src/components/table/TableHeader.jsx | 2 +- ui/src/pages/Dashboard/DataIngestionModal.tsx | 2 +- 11 files changed, 10 insertions(+), 10 deletions(-) rename ui/src/components/{Button/Button.tsx => UCCButton/UCCButton.tsx} (100%) diff --git a/ui/src/components/AcceptModal/AcceptModal.tsx b/ui/src/components/AcceptModal/AcceptModal.tsx index c353f3ecc..675f697bd 100644 --- a/ui/src/components/AcceptModal/AcceptModal.tsx +++ b/ui/src/components/AcceptModal/AcceptModal.tsx @@ -3,7 +3,7 @@ import Modal from '@splunk/react-ui/Modal'; import Message from '@splunk/react-ui/Message'; import styled from 'styled-components'; -import { UCCButton } from '../Button/Button'; +import { UCCButton } from '../UCCButton/UCCButton'; const ModalWrapper = styled(Modal)` width: 600px; diff --git a/ui/src/components/ConfigurationFormView.jsx b/ui/src/components/ConfigurationFormView.jsx index e5abf7901..99667e260 100644 --- a/ui/src/components/ConfigurationFormView.jsx +++ b/ui/src/components/ConfigurationFormView.jsx @@ -5,7 +5,7 @@ import { _ } from '@splunk/ui-utils/i18n'; import styled from 'styled-components'; import BaseFormView from './BaseFormView/BaseFormView'; -import { UCCButton } from './Button/Button'; +import { UCCButton } from './UCCButton/UCCButton'; import { getRequest, generateEndPointUrl } from '../util/api'; import { MODE_CONFIG } from '../constants/modes'; import { WaitSpinnerWrapper } from './table/CustomTableStyle'; diff --git a/ui/src/components/DashboardInfoModal/DashboardInfoModal.tsx b/ui/src/components/DashboardInfoModal/DashboardInfoModal.tsx index aa894d099..ad0309033 100644 --- a/ui/src/components/DashboardInfoModal/DashboardInfoModal.tsx +++ b/ui/src/components/DashboardInfoModal/DashboardInfoModal.tsx @@ -6,7 +6,7 @@ import Heading from '@splunk/react-ui/Heading'; import P from '@splunk/react-ui/Paragraph'; import QuestionCircle from '@splunk/react-icons/QuestionCircle'; -import { UCCButton } from '../Button/Button'; +import { UCCButton } from '../UCCButton/UCCButton'; const ModalWrapper = styled(Modal)` width: 700px; diff --git a/ui/src/components/DeleteModal/DeleteModal.tsx b/ui/src/components/DeleteModal/DeleteModal.tsx index 52a194da9..c9487ecf0 100644 --- a/ui/src/components/DeleteModal/DeleteModal.tsx +++ b/ui/src/components/DeleteModal/DeleteModal.tsx @@ -11,7 +11,7 @@ import TableContext from '../../context/TableContext'; import { parseErrorMsg, getFormattedMessage } from '../../util/messageUtil'; import { PAGE_INPUT } from '../../constants/pages'; import { StandardPages } from '../../types/components/shareableTypes'; -import { UCCButton } from '../Button/Button'; +import { UCCButton } from '../UCCButton/UCCButton'; const ModalWrapper = styled(Modal)` width: 800px; diff --git a/ui/src/components/EntityModal/EntityModal.tsx b/ui/src/components/EntityModal/EntityModal.tsx index 10442dffa..d7447b19d 100644 --- a/ui/src/components/EntityModal/EntityModal.tsx +++ b/ui/src/components/EntityModal/EntityModal.tsx @@ -8,7 +8,7 @@ import { Mode, MODE_CLONE, MODE_CREATE, MODE_EDIT } from '../../constants/modes' import BaseFormView from '../BaseFormView/BaseFormView'; import { StandardPages } from '../../types/components/shareableTypes'; import PageContext from '../../context/PageContext'; -import { UCCButton } from '../Button/Button'; +import { UCCButton } from '../UCCButton/UCCButton'; const ModalWrapper = styled(Modal)` width: 800px; diff --git a/ui/src/components/EntityPage/EntityPage.tsx b/ui/src/components/EntityPage/EntityPage.tsx index 1d8e5a9a0..dff3b1cd3 100644 --- a/ui/src/components/EntityPage/EntityPage.tsx +++ b/ui/src/components/EntityPage/EntityPage.tsx @@ -15,7 +15,7 @@ import { SubTitleComponent } from '../../pages/Input/InputPageStyle'; import { PAGE_INPUT } from '../../constants/pages'; import { StandardPages } from '../../types/components/shareableTypes'; import PageContext from '../../context/PageContext'; -import { UCCButton } from '../Button/Button'; +import { UCCButton } from '../UCCButton/UCCButton'; interface EntityPageProps { handleRequestClose: () => void; diff --git a/ui/src/components/ErrorModal/ErrorModal.tsx b/ui/src/components/ErrorModal/ErrorModal.tsx index 72da21ff4..e41fe2260 100644 --- a/ui/src/components/ErrorModal/ErrorModal.tsx +++ b/ui/src/components/ErrorModal/ErrorModal.tsx @@ -4,7 +4,7 @@ import Message from '@splunk/react-ui/Message'; import styled from 'styled-components'; import { getFormattedMessage } from '../../util/messageUtil'; -import { UCCButton } from '../Button/Button'; +import { UCCButton } from '../UCCButton/UCCButton'; const ModalWrapper = styled(Modal)` width: 600px; diff --git a/ui/src/components/MenuInput/MenuInput.tsx b/ui/src/components/MenuInput/MenuInput.tsx index f6c9fce45..2d6b0442d 100644 --- a/ui/src/components/MenuInput/MenuInput.tsx +++ b/ui/src/components/MenuInput/MenuInput.tsx @@ -15,7 +15,7 @@ import CustomMenu from '../CustomMenu'; import { invariant } from '../../util/invariant'; import { usePageContext } from '../../context/usePageContext'; import { shouldHideForPlatform } from '../../util/pageContext'; -import { UCCButton } from '../Button/Button'; +import { UCCButton } from '../UCCButton/UCCButton'; const CustomSubTitle = styled.span` color: ${variables.brandColorD20}; diff --git a/ui/src/components/Button/Button.tsx b/ui/src/components/UCCButton/UCCButton.tsx similarity index 100% rename from ui/src/components/Button/Button.tsx rename to ui/src/components/UCCButton/UCCButton.tsx diff --git a/ui/src/components/table/TableHeader.jsx b/ui/src/components/table/TableHeader.jsx index 6d3f823b9..27bf140a8 100644 --- a/ui/src/components/table/TableHeader.jsx +++ b/ui/src/components/table/TableHeader.jsx @@ -7,7 +7,7 @@ import { Typography } from '@splunk/react-ui/Typography'; import styled from 'styled-components'; import { _ } from '@splunk/ui-utils/i18n'; -import { UCCButton } from '../Button/Button'; +import { UCCButton } from '../UCCButton/UCCButton'; import TableFilter from './TableFilter'; import { TableSelectBoxWrapper } from './CustomTableStyle'; import { PAGE_INPUT } from '../../constants/pages'; diff --git a/ui/src/pages/Dashboard/DataIngestionModal.tsx b/ui/src/pages/Dashboard/DataIngestionModal.tsx index e0e15d266..2a2924cc6 100644 --- a/ui/src/pages/Dashboard/DataIngestionModal.tsx +++ b/ui/src/pages/Dashboard/DataIngestionModal.tsx @@ -10,7 +10,7 @@ import Checkmark from '@splunk/react-icons/Checkmark'; import P from '@splunk/react-ui/Paragraph'; import { makeVisualAdjustmentsOnDataIngestionModal } from './utils'; -import { UCCButton } from '../../components/Button/Button'; +import { UCCButton } from '../../components/UCCButton/UCCButton'; const ModalWrapper = styled(Modal)` width: 60vw; From 6ca09257a11ccb0721a8817f7825c03716a51e4c Mon Sep 17 00:00:00 2001 From: Szymon Oleksy Date: Thu, 5 Dec 2024 10:21:56 +0100 Subject: [PATCH 09/13] chore: extend expect --- ui/jest.setup.ts | 13 ++++-- .../EntityModal/EntityModal.test.tsx | 26 +++-------- .../FormModifications.test.tsx | 14 +++--- ui/src/tests/expectExtenders.ts | 44 +++++++++++++++++++ 4 files changed, 66 insertions(+), 31 deletions(-) create mode 100644 ui/src/tests/expectExtenders.ts diff --git a/ui/jest.setup.ts b/ui/jest.setup.ts index 72eba6f54..abd1df6bf 100644 --- a/ui/jest.setup.ts +++ b/ui/jest.setup.ts @@ -1,8 +1,9 @@ import '@testing-library/jest-dom'; import '@testing-library/jest-dom/jest-globals'; - import { configure } from '@testing-library/react'; + import { server } from './src/mocks/server'; +import { toBeDisabled, toBeEnabled } from './src/tests/expectExtenders'; /** * Configure test attributes @@ -12,11 +13,15 @@ configure({ testIdAttribute: 'data-test' }); /** * MSW mocking */ -beforeAll(() => +beforeAll(() => { server.listen({ onUnhandledRequest: 'warn', - }) -); + }); + expect.extend({ + toBeDisabled, + toBeEnabled, + }); +}); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); diff --git a/ui/src/components/EntityModal/EntityModal.test.tsx b/ui/src/components/EntityModal/EntityModal.test.tsx index 2cdc15f2a..d9cce8b54 100644 --- a/ui/src/components/EntityModal/EntityModal.test.tsx +++ b/ui/src/components/EntityModal/EntityModal.test.tsx @@ -27,18 +27,6 @@ import { StandardPages } from '../../types/components/shareableTypes'; import { server } from '../../mocks/server'; import { invariant } from '../../util/invariant'; -const isTextDisabled = (field: HTMLElement | null | Element) => { - invariant(field); - expect(field).toHaveAttribute('readonly'); - expect(field).toHaveAttribute('aria-disabled', 'true'); -}; - -const isTextEnabled = (field: HTMLElement | null | Element) => { - invariant(field); - expect(field).not.toHaveAttribute('readonly'); - expect(field).toHaveAttribute('aria-disabled', 'false'); -}; - describe('Oauth field disabled on edit - diableonEdit property', () => { const handleRequestClose = jest.fn(); @@ -74,7 +62,7 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - isTextEnabled(oauthTextBox); + expect(oauthTextBox).toBeEnabled(); }); it('Oauth Oauth - disableonEdit = true, oauth field disabled on edit', async () => { @@ -94,7 +82,7 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - isTextDisabled(oauthTextBox); + expect(oauthTextBox).toBeDisabled(); }); it('Oauth Basic - Enable field equal false, so field disabled', async () => { @@ -114,7 +102,7 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { const oauthTextBox = getDisabledBasicField(); expect(oauthTextBox).toBeInTheDocument(); - isTextDisabled(oauthTextBox); + expect(oauthTextBox).toBeDisabled(); }); it('if oauth field not disabled with create after disableonEdit true', async () => { @@ -132,7 +120,7 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledBasicField(); expect(oauthTextBox).toBeInTheDocument(); - isTextEnabled(oauthTextBox); + expect(oauthTextBox).toBeEnabled(); }); }); @@ -175,7 +163,7 @@ describe('Options - Enable field property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - isTextDisabled(oauthTextBox); + expect(oauthTextBox).toBeDisabled(); }); it('Oauth Basic - Enable field equal false, so field disabled', async () => { @@ -193,7 +181,7 @@ describe('Options - Enable field property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - isTextDisabled(oauthTextBox); + expect(oauthTextBox).toBeDisabled(); }); it('Oauth Basic - Fully enabled field, enabled: true, disableonEdit: false', async () => { @@ -211,7 +199,7 @@ describe('Options - Enable field property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - isTextEnabled(oauthTextBox); + expect(oauthTextBox).toBeEnabled(); }); }); diff --git a/ui/src/components/FormModifications/FormModifications.test.tsx b/ui/src/components/FormModifications/FormModifications.test.tsx index e348f6492..427f29418 100644 --- a/ui/src/components/FormModifications/FormModifications.test.tsx +++ b/ui/src/components/FormModifications/FormModifications.test.tsx @@ -127,24 +127,22 @@ it('verify modification after text components change', async () => { expect(parentElement).toHaveTextContent(mods.label); }; - expect(componentInput).toHaveAttribute('readonly'); - expect(componentInput).toHaveAttribute('aria-disabled', 'true'); + expect(componentInput).toBeDisabled(); verifyAllProps(componentParentElement, componentInput, mods1Field1); - expect(component2Input).toHaveAttribute('readonly'); - expect(component2Input).toHaveAttribute('aria-disabled', 'true'); + expect(component2Input).toBeDisabled(); verifyAllProps(component2ParentElement, component2Input, mods1Field2); await userEvent.type(componentMakingModsTextBox1, secondValueToInput); - expect(componentInput).not.toHaveAttribute('readonly'); - expect(componentInput).toHaveAttribute('aria-disabled', 'false'); + expect(component2Input).toBeEnabled(); + verifyAllProps(componentParentElement, componentInput, mods2Field1); - expect(component2Input).not.toHaveAttribute('readonly'); - expect(component2Input).toHaveAttribute('aria-disabled', 'false'); + expect(component2Input).toBeEnabled(); + verifyAllProps(component2ParentElement, component2Input, mods2Field2); }); diff --git a/ui/src/tests/expectExtenders.ts b/ui/src/tests/expectExtenders.ts new file mode 100644 index 000000000..7f262bdfc --- /dev/null +++ b/ui/src/tests/expectExtenders.ts @@ -0,0 +1,44 @@ +import { invariant } from '../util/invariant'; + +export const toBeEnabled = (field: HTMLElement | null | Element) => { + invariant(field); + + if (field.getAttribute('readonly')) { + return { pass: false, message: () => 'Field contains "readonly" attribute' }; + } + + const ariaDisabled = field.getAttribute('aria-disabled'); + + if (ariaDisabled === 'false') { + return { pass: true, message: () => 'Field is enabled' }; + } + + return { + pass: false, + message: () => + `Attribute "aria-disabled" is incorrect expected "false", got ${ariaDisabled}`, + }; +}; + +export const toBeDisabled = (field: HTMLElement | null | Element) => { + invariant(field); + + if (field.getAttribute('readonly') === null) { + return { + pass: false, + message: () => `Field "readonly" attribute is null`, + }; + } + + const ariaDisabled = field.getAttribute('aria-disabled'); + + if (ariaDisabled === 'true') { + return { pass: true, message: () => 'Field is disabled' }; + } + + return { + pass: false, + message: () => + `Attribute "aria-disabled" is incorrect expected "true", got ${ariaDisabled}`, + }; +}; From 17bf7a1a3fa7275ff00bf1ea05b07dbd1c87b474 Mon Sep 17 00:00:00 2001 From: Szymon Oleksy Date: Thu, 5 Dec 2024 13:30:40 +0100 Subject: [PATCH 10/13] chore: review --- ui/jest.setup.ts | 6 +- .../EntityModal/EntityModal.test.tsx | 14 ++-- .../FormModifications.test.tsx | 8 +- ui/src/tests/expectExtenders.ts | 75 +++++++++++-------- 4 files changed, 55 insertions(+), 48 deletions(-) diff --git a/ui/jest.setup.ts b/ui/jest.setup.ts index abd1df6bf..ec772fd28 100644 --- a/ui/jest.setup.ts +++ b/ui/jest.setup.ts @@ -3,7 +3,7 @@ import '@testing-library/jest-dom/jest-globals'; import { configure } from '@testing-library/react'; import { server } from './src/mocks/server'; -import { toBeDisabled, toBeEnabled } from './src/tests/expectExtenders'; +import './src/tests/expectExtenders'; /** * Configure test attributes @@ -17,10 +17,6 @@ beforeAll(() => { server.listen({ onUnhandledRequest: 'warn', }); - expect.extend({ - toBeDisabled, - toBeEnabled, - }); }); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); diff --git a/ui/src/components/EntityModal/EntityModal.test.tsx b/ui/src/components/EntityModal/EntityModal.test.tsx index d9cce8b54..a43d8a556 100644 --- a/ui/src/components/EntityModal/EntityModal.test.tsx +++ b/ui/src/components/EntityModal/EntityModal.test.tsx @@ -62,7 +62,7 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toBeEnabled(); + expect(oauthTextBox).toBeVisuallyEnabled(); }); it('Oauth Oauth - disableonEdit = true, oauth field disabled on edit', async () => { @@ -82,7 +82,7 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toBeDisabled(); + expect(oauthTextBox).toBeVisuallyDisabled(); }); it('Oauth Basic - Enable field equal false, so field disabled', async () => { @@ -102,7 +102,7 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { const oauthTextBox = getDisabledBasicField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toBeDisabled(); + expect(oauthTextBox).toBeVisuallyDisabled(); }); it('if oauth field not disabled with create after disableonEdit true', async () => { @@ -120,7 +120,7 @@ describe('Oauth field disabled on edit - diableonEdit property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledBasicField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toBeEnabled(); + expect(oauthTextBox).toBeVisuallyEnabled(); }); }); @@ -163,7 +163,7 @@ describe('Options - Enable field property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toBeDisabled(); + expect(oauthTextBox).toBeVisuallyDisabled(); }); it('Oauth Basic - Enable field equal false, so field disabled', async () => { @@ -181,7 +181,7 @@ describe('Options - Enable field property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toBeDisabled(); + expect(oauthTextBox).toBeVisuallyDisabled(); }); it('Oauth Basic - Fully enabled field, enabled: true, disableonEdit: false', async () => { @@ -199,7 +199,7 @@ describe('Options - Enable field property', () => { renderModalWithProps(props); const oauthTextBox = getDisabledOauthField(); expect(oauthTextBox).toBeInTheDocument(); - expect(oauthTextBox).toBeEnabled(); + expect(oauthTextBox).toBeVisuallyEnabled(); }); }); diff --git a/ui/src/components/FormModifications/FormModifications.test.tsx b/ui/src/components/FormModifications/FormModifications.test.tsx index 427f29418..5e139b23e 100644 --- a/ui/src/components/FormModifications/FormModifications.test.tsx +++ b/ui/src/components/FormModifications/FormModifications.test.tsx @@ -127,21 +127,21 @@ it('verify modification after text components change', async () => { expect(parentElement).toHaveTextContent(mods.label); }; - expect(componentInput).toBeDisabled(); + expect(componentInput).toBeVisuallyDisabled(); verifyAllProps(componentParentElement, componentInput, mods1Field1); - expect(component2Input).toBeDisabled(); + expect(component2Input).toBeVisuallyDisabled(); verifyAllProps(component2ParentElement, component2Input, mods1Field2); await userEvent.type(componentMakingModsTextBox1, secondValueToInput); - expect(component2Input).toBeEnabled(); + expect(component2Input).toBeVisuallyEnabled(); verifyAllProps(componentParentElement, componentInput, mods2Field1); - expect(component2Input).toBeEnabled(); + expect(component2Input).toBeVisuallyEnabled(); verifyAllProps(component2ParentElement, component2Input, mods2Field2); }); diff --git a/ui/src/tests/expectExtenders.ts b/ui/src/tests/expectExtenders.ts index 7f262bdfc..05afce0f0 100644 --- a/ui/src/tests/expectExtenders.ts +++ b/ui/src/tests/expectExtenders.ts @@ -1,44 +1,55 @@ import { invariant } from '../util/invariant'; -export const toBeEnabled = (field: HTMLElement | null | Element) => { - invariant(field); +expect.extend({ + toBeVisuallyEnabled(field: HTMLElement | null | Element) { + invariant(field); - if (field.getAttribute('readonly')) { - return { pass: false, message: () => 'Field contains "readonly" attribute' }; - } - - const ariaDisabled = field.getAttribute('aria-disabled'); - - if (ariaDisabled === 'false') { - return { pass: true, message: () => 'Field is enabled' }; - } + if (field.getAttribute('readonly')) { + return { pass: false, message: () => 'Field contains "readonly" attribute' }; + } - return { - pass: false, - message: () => - `Attribute "aria-disabled" is incorrect expected "false", got ${ariaDisabled}`, - }; -}; + const ariaDisabled = field.getAttribute('aria-disabled'); -export const toBeDisabled = (field: HTMLElement | null | Element) => { - invariant(field); + if (ariaDisabled === 'false') { + return { pass: true, message: () => 'Field is enabled' }; + } - if (field.getAttribute('readonly') === null) { return { pass: false, - message: () => `Field "readonly" attribute is null`, + message: () => + `Attribute "aria-disabled" is incorrect expected "false", got "${ariaDisabled}"`, }; - } + }, + toBeVisuallyDisabled(field: HTMLElement | null | Element) { + invariant(field); - const ariaDisabled = field.getAttribute('aria-disabled'); + if (field.getAttribute('readonly') === null) { + return { + pass: false, + message: () => `Field "readonly" attribute is null`, + }; + } - if (ariaDisabled === 'true') { - return { pass: true, message: () => 'Field is disabled' }; - } + const ariaDisabled = field.getAttribute('aria-disabled'); - return { - pass: false, - message: () => - `Attribute "aria-disabled" is incorrect expected "true", got ${ariaDisabled}`, - }; -}; + if (ariaDisabled === 'true') { + return { pass: true, message: () => 'Field is disabled' }; + } + + return { + pass: false, + message: () => + `Attribute "aria-disabled" is incorrect expected "true", got ${ariaDisabled}`, + }; + }, +}); + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace jest { + interface Matchers { + toBeVisuallyDisabled(): R; + toBeVisuallyEnabled(): R; + } + } +} From e41291bae12a9fbc9cf8e16c1f8b808b28df0d02 Mon Sep 17 00:00:00 2001 From: Szymon Oleksy Date: Thu, 5 Dec 2024 18:10:07 +0100 Subject: [PATCH 11/13] chore: toMatchImageSnapshot types --- ui/.storybook/test-runner.ts | 4 ---- ui/package.json | 1 + ui/src/tests/expectExtenders.ts | 3 +++ ui/src/types/modules.d.ts | 8 -------- ui/yarn.lock | 18 +++++++++++++++++- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/ui/.storybook/test-runner.ts b/ui/.storybook/test-runner.ts index e3ba6b780..e96ed2877 100644 --- a/ui/.storybook/test-runner.ts +++ b/ui/.storybook/test-runner.ts @@ -1,10 +1,6 @@ import { getStoryContext, TestRunnerConfig } from '@storybook/test-runner'; -import { toMatchImageSnapshot } from 'jest-image-snapshot'; const config: TestRunnerConfig = { - setup() { - expect.extend({ toMatchImageSnapshot }); - }, async preVisit(page, context) { const storyContext = await getStoryContext(page, context); const parameters = storyContext.parameters; diff --git a/ui/package.json b/ui/package.json index 536bdd914..28d9f03cd 100644 --- a/ui/package.json +++ b/ui/package.json @@ -74,6 +74,7 @@ "@testing-library/react": "^12.1.5", "@testing-library/user-event": "^14.5.2", "@types/jest": "^29.5.14", + "@types/jest-image-snapshot": "^6.4.0", "@types/js-yaml": "^4.0.9", "@types/node": "^20.17.6", "@types/react": "^16.14.62", diff --git a/ui/src/tests/expectExtenders.ts b/ui/src/tests/expectExtenders.ts index 05afce0f0..d00f2e998 100644 --- a/ui/src/tests/expectExtenders.ts +++ b/ui/src/tests/expectExtenders.ts @@ -1,3 +1,5 @@ +import { toMatchImageSnapshot } from 'jest-image-snapshot'; + import { invariant } from '../util/invariant'; expect.extend({ @@ -42,6 +44,7 @@ expect.extend({ `Attribute "aria-disabled" is incorrect expected "true", got ${ariaDisabled}`, }; }, + toMatchImageSnapshot, }); declare global { diff --git a/ui/src/types/modules.d.ts b/ui/src/types/modules.d.ts index 16698a4a1..65d0329d1 100644 --- a/ui/src/types/modules.d.ts +++ b/ui/src/types/modules.d.ts @@ -84,11 +84,3 @@ declare module '@splunk/search-job'; declare module '@splunk/ui-utils/i18n'; declare module 'uuid'; - -declare global { - namespace jest { - interface Matchers { - toMatchImageSnapshot(): R; - } - } -} diff --git a/ui/yarn.lock b/ui/yarn.lock index 9ffbb60f7..21f4729ef 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -4511,7 +4511,16 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.5.14": +"@types/jest-image-snapshot@^6.4.0": + version "6.4.0" + resolved "https://registry.npmjs.org/@types/jest-image-snapshot/-/jest-image-snapshot-6.4.0.tgz#641054d2fa2ff130a49c844ee7a9a68f281b6017" + integrity sha512-8TQ/EgqFCX0UWSpH488zAc21fCkJNpZPnnp3xWFMqElxApoJV5QOoqajnVRV7AhfF0rbQWTVyc04KG7tXnzCPA== + dependencies: + "@types/jest" "*" + "@types/pixelmatch" "*" + ssim.js "^3.1.1" + +"@types/jest@*", "@types/jest@^29.5.14": version "29.5.14" resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== @@ -4613,6 +4622,13 @@ resolved "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz#a9495a58d8c75be4ffe9a0bd749a307715c07404" integrity sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA== +"@types/pixelmatch@*": + version "5.2.6" + resolved "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.6.tgz#fba6de304ac958495f27d85989f5c6bb7499a686" + integrity sha512-wC83uexE5KGuUODn6zkm9gMzTwdY5L0chiK+VrKcDfEjzxh1uadlWTvOmAbCpnM9zx/Ww3f8uKlYQVnO/TrqVg== + dependencies: + "@types/node" "*" + "@types/prop-types@*": version "15.7.13" resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451" From 1548f4fd02f248f5aea29305ef69eb965d9c3a3d Mon Sep 17 00:00:00 2001 From: Szymon Oleksy Date: Thu, 5 Dec 2024 18:20:36 +0100 Subject: [PATCH 12/13] chore: add except extenders to storybook --- ui/.storybook/test-runner.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/.storybook/test-runner.ts b/ui/.storybook/test-runner.ts index e96ed2877..99f429f66 100644 --- a/ui/.storybook/test-runner.ts +++ b/ui/.storybook/test-runner.ts @@ -1,4 +1,5 @@ import { getStoryContext, TestRunnerConfig } from '@storybook/test-runner'; +import '../src/tests/expectExtenders'; const config: TestRunnerConfig = { async preVisit(page, context) { From 3020b4bacb78d85356449bb13118b4b77991cb9a Mon Sep 17 00:00:00 2001 From: Szymon Oleksy Date: Thu, 5 Dec 2024 18:27:26 +0100 Subject: [PATCH 13/13] chore: revert toMatchImageSnapshot extension change --- ui/.storybook/test-runner.ts | 5 ++++- ui/jest.setup.ts | 6 +++--- ui/src/tests/expectExtenders.ts | 3 --- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/.storybook/test-runner.ts b/ui/.storybook/test-runner.ts index 99f429f66..e3ba6b780 100644 --- a/ui/.storybook/test-runner.ts +++ b/ui/.storybook/test-runner.ts @@ -1,7 +1,10 @@ import { getStoryContext, TestRunnerConfig } from '@storybook/test-runner'; -import '../src/tests/expectExtenders'; +import { toMatchImageSnapshot } from 'jest-image-snapshot'; const config: TestRunnerConfig = { + setup() { + expect.extend({ toMatchImageSnapshot }); + }, async preVisit(page, context) { const storyContext = await getStoryContext(page, context); const parameters = storyContext.parameters; diff --git a/ui/jest.setup.ts b/ui/jest.setup.ts index ec772fd28..54ea9a007 100644 --- a/ui/jest.setup.ts +++ b/ui/jest.setup.ts @@ -13,11 +13,11 @@ configure({ testIdAttribute: 'data-test' }); /** * MSW mocking */ -beforeAll(() => { +beforeAll(() => server.listen({ onUnhandledRequest: 'warn', - }); -}); + }) +); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); diff --git a/ui/src/tests/expectExtenders.ts b/ui/src/tests/expectExtenders.ts index d00f2e998..05afce0f0 100644 --- a/ui/src/tests/expectExtenders.ts +++ b/ui/src/tests/expectExtenders.ts @@ -1,5 +1,3 @@ -import { toMatchImageSnapshot } from 'jest-image-snapshot'; - import { invariant } from '../util/invariant'; expect.extend({ @@ -44,7 +42,6 @@ expect.extend({ `Attribute "aria-disabled" is incorrect expected "true", got ${ariaDisabled}`, }; }, - toMatchImageSnapshot, }); declare global {