diff --git a/packages/dashboard-frontend/src/components/EditorSelector/Definition/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/components/EditorSelector/Definition/__tests__/__snapshots__/index.spec.tsx.snap index b2248222c..9bb4eb089 100644 --- a/packages/dashboard-frontend/src/components/EditorSelector/Definition/__tests__/__snapshots__/index.spec.tsx.snap +++ b/packages/dashboard-frontend/src/components/EditorSelector/Definition/__tests__/__snapshots__/index.spec.tsx.snap @@ -26,6 +26,29 @@ exports[`EditorDefinition snapshot w/o initial values 1`] = ` Editor Definition Field +
+
+ + How to specify and use a custom editor + +
+
`; @@ -55,5 +78,28 @@ exports[`EditorDefinition snapshot with initial values 1`] = ` Editor Definition Field +
+
+ + How to specify and use a custom editor + +
+
`; diff --git a/packages/dashboard-frontend/src/components/EditorSelector/Definition/index.tsx b/packages/dashboard-frontend/src/components/EditorSelector/Definition/index.tsx index ab461ed27..642e63a18 100644 --- a/packages/dashboard-frontend/src/components/EditorSelector/Definition/index.tsx +++ b/packages/dashboard-frontend/src/components/EditorSelector/Definition/index.tsx @@ -15,6 +15,7 @@ import React from 'react'; import { EditorDefinitionField } from '@/components/EditorSelector/Definition/DefinitionField'; import { EditorImageField } from '@/components/EditorSelector/Definition/ImageField'; +import { DocsLink } from '@/components/EditorSelector/DocsLink'; export type Props = { editorDefinition: string | undefined; @@ -58,6 +59,8 @@ export class EditorDefinition extends React.PureComponent { onChange={editorDefinition => this.handleEditorDefinition(editorDefinition)} /> this.handleEditorImage(editorImage)} /> + + ); } diff --git a/packages/dashboard-frontend/src/components/EditorSelector/DocsLink/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/components/EditorSelector/DocsLink/__tests__/__snapshots__/index.spec.tsx.snap new file mode 100644 index 000000000..328e2ce69 --- /dev/null +++ b/packages/dashboard-frontend/src/components/EditorSelector/DocsLink/__tests__/__snapshots__/index.spec.tsx.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DocsLink snapshot 1`] = ` +
+
+ + How to specify and use a custom editor + +
+
+`; diff --git a/packages/dashboard-frontend/src/components/EditorSelector/DocsLink/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/EditorSelector/DocsLink/__tests__/index.spec.tsx new file mode 100644 index 000000000..e4f8c1687 --- /dev/null +++ b/packages/dashboard-frontend/src/components/EditorSelector/DocsLink/__tests__/index.spec.tsx @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import React from 'react'; + +import { DocsLink } from '@/components/EditorSelector/DocsLink'; +import getComponentRenderer from '@/services/__mocks__/getComponentRenderer'; + +const { createSnapshot } = getComponentRenderer(getComponent); + +describe('DocsLink', () => { + test('snapshot', () => { + const snapshot = createSnapshot(); + expect(snapshot.toJSON()).toMatchSnapshot(); + }); +}); + +function getComponent() { + return ; +} diff --git a/packages/dashboard-frontend/src/components/EditorSelector/DocsLink/index.tsx b/packages/dashboard-frontend/src/components/EditorSelector/DocsLink/index.tsx new file mode 100644 index 000000000..40804d8b9 --- /dev/null +++ b/packages/dashboard-frontend/src/components/EditorSelector/DocsLink/index.tsx @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { Button, Flex, FlexItem } from '@patternfly/react-core'; +import React from 'react'; + +const DOCS_DEFINING_A_COMMON_IDE = + 'https://eclipse.dev/che/docs/stable/end-user-guide/defining-a-common-ide/'; + +export class DocsLink extends React.PureComponent { + public render() { + return ( + + + + + + ); + } +} diff --git a/packages/dashboard-frontend/src/components/EditorSelector/Gallery/Entry/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/components/EditorSelector/Gallery/Entry/__tests__/__snapshots__/index.spec.tsx.snap index 5ed83617c..455d027b2 100644 --- a/packages/dashboard-frontend/src/components/EditorSelector/Gallery/Entry/__tests__/__snapshots__/index.spec.tsx.snap +++ b/packages/dashboard-frontend/src/components/EditorSelector/Gallery/Entry/__tests__/__snapshots__/index.spec.tsx.snap @@ -28,6 +28,49 @@ exports[`Editor Selector Entry snapshot 1`] = ` className="editorIcon" src="/v3/images/vscode.svg" /> +
+
+ +
    +
  • + + + insiders + + +
  • +
  • + +   + +
  • +
+
+
@@ -78,15 +121,6 @@ exports[`Editor Selector Entry snapshot 1`] = ` > VS Code - Open Source - - - insiders - -
, ] diff --git a/packages/dashboard-frontend/src/components/EditorSelector/Gallery/Entry/index.tsx b/packages/dashboard-frontend/src/components/EditorSelector/Gallery/Entry/index.tsx index 6a4c0a5f8..1ad7da009 100644 --- a/packages/dashboard-frontend/src/components/EditorSelector/Gallery/Entry/index.tsx +++ b/packages/dashboard-frontend/src/components/EditorSelector/Gallery/Entry/index.tsx @@ -18,12 +18,13 @@ import { Dropdown, DropdownItem, KebabToggle, + LabelGroup, } from '@patternfly/react-core'; import { CheckIcon } from '@patternfly/react-icons'; import React from 'react'; import styles from '@/components/EditorSelector/Gallery/Entry/index.module.css'; -import TagLabel from '@/components/TagLabel'; +import { TagLabel } from '@/components/TagLabel'; import { che } from '@/services/models'; export type Props = { @@ -144,6 +145,20 @@ export class EditorSelectorEntry extends React.PureComponent { const titleClassName = isSelectedGroup ? styles.activeCard : ''; + const hasTechPreviewTag = + (activeEditor.tags || []).includes('tech-preview') === true || + /idea/i.test(activeEditor.id) === true; + const tagsGroup = ( + + + {hasTechPreviewTag ? ( + + ) : ( +   + )} + + ); + return ( { > + {tagsGroup} { {groupName} - ); diff --git a/packages/dashboard-frontend/src/components/EditorSelector/Gallery/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/components/EditorSelector/Gallery/__tests__/__snapshots__/index.spec.tsx.snap index 1cb580cf3..a121397fe 100644 --- a/packages/dashboard-frontend/src/components/EditorSelector/Gallery/__tests__/__snapshots__/index.spec.tsx.snap +++ b/packages/dashboard-frontend/src/components/EditorSelector/Gallery/__tests__/__snapshots__/index.spec.tsx.snap @@ -5,8 +5,8 @@ exports[`EditorGallery snapshot 1`] = ` className="pf-l-gallery pf-m-gutter" style={ { - "--pf-l-gallery--GridTemplateColumns--max": "170px", - "--pf-l-gallery--GridTemplateColumns--min": "170px", + "--pf-l-gallery--GridTemplateColumns--max": "210px", + "--pf-l-gallery--GridTemplateColumns--min": "210px", } } > diff --git a/packages/dashboard-frontend/src/components/EditorSelector/Gallery/index.tsx b/packages/dashboard-frontend/src/components/EditorSelector/Gallery/index.tsx index 605ca17f1..5998653fd 100644 --- a/packages/dashboard-frontend/src/components/EditorSelector/Gallery/index.tsx +++ b/packages/dashboard-frontend/src/components/EditorSelector/Gallery/index.tsx @@ -125,7 +125,7 @@ export class EditorGallery extends React.PureComponent { public render() { return ( - + {this.buildEditorCards()} ); diff --git a/packages/dashboard-frontend/src/components/EditorSelector/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/components/EditorSelector/__tests__/__snapshots__/index.spec.tsx.snap index 7db150492..2365fdee0 100644 --- a/packages/dashboard-frontend/src/components/EditorSelector/__tests__/__snapshots__/index.spec.tsx.snap +++ b/packages/dashboard-frontend/src/components/EditorSelector/__tests__/__snapshots__/index.spec.tsx.snap @@ -35,6 +35,103 @@ exports[`Editor Selector snapshot 1`] = ` + + +

+

- diff --git a/packages/dashboard-frontend/src/components/EditorSelector/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/EditorSelector/__tests__/index.spec.tsx index fa9aa80ae..0c5b97240 100644 --- a/packages/dashboard-frontend/src/components/EditorSelector/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/EditorSelector/__tests__/index.spec.tsx @@ -41,31 +41,69 @@ describe('Editor Selector', () => { expect(snapshot.toJSON()).toMatchSnapshot(); }); - test('accordion content toggling', async () => { + test('accordion content toggling', () => { renderComponent(); + const defaultEditorButton = screen.getByRole('button', { name: 'Use a Default Editor' }); const editorGalleryButton = screen.getByRole('button', { name: 'Choose an Editor' }); const editorDefinitionButton = screen.getByRole('button', { name: 'Use an Editor Definition' }); - // initially the gallery is visible and the definition is not - await expect(screen.findByTestId('editor-gallery-content')).resolves.toBeInTheDocument(); + // initially the default editor section is visible; the selector and definition sections are not + expect(screen.getByTestId('default-editor-content')).not.toHaveAttribute('hidden'); + expect(screen.getByTestId('editor-gallery-content')).toHaveAttribute('hidden'); + expect(screen.getByTestId('editor-definition-content')).toHaveAttribute('hidden'); + + /* switch to the editor gallery section */ + + userEvent.click(editorGalleryButton); + + // now the gallery is visible + expect(screen.getByTestId('editor-gallery-content')).not.toHaveAttribute('hidden'); + expect(screen.getByTestId('editor-definition-content')).toHaveAttribute('hidden'); + expect(screen.getByTestId('default-editor-content')).toHaveAttribute('hidden'); + + /* switch to the editor definition section */ userEvent.click(editorDefinitionButton); - // now the gallery is not visible and the definition is - await expect(screen.findByTestId('editor-definition-content')).resolves.toBeInTheDocument(); + // now the editor definition is visible + expect(screen.getByTestId('editor-definition-content')).not.toHaveAttribute('hidden'); + expect(screen.getByTestId('editor-gallery-content')).toHaveAttribute('hidden'); + expect(screen.getByTestId('default-editor-content')).toHaveAttribute('hidden'); - userEvent.click(editorGalleryButton); + /* switch back to the default editor section */ + + userEvent.click(defaultEditorButton); - // now the gallery is visible and the definition is not - await expect(screen.findByTestId('editor-gallery-content')).resolves.toBeInTheDocument(); + // now the default editor section is visible + expect(screen.getByTestId('default-editor-content')).not.toHaveAttribute('hidden'); + expect(screen.getByTestId('editor-gallery-content')).toHaveAttribute('hidden'); + expect(screen.getByTestId('editor-definition-content')).toHaveAttribute('hidden'); }); - test('select editor from gallery', () => { + test('use a default editor', () => { renderComponent(); - const editorGallery = screen.queryByTestId('editor-gallery-content')!; - expect(editorGallery).not.toBeNull(); + expect(screen.getByTestId('default-editor-content')).not.toHaveAttribute('hidden'); + + expect(mockOnSelect).not.toHaveBeenCalled(); + + const defaultEditorButton = screen.getByRole('button', { name: 'Use a Default Editor' }); + + userEvent.click(defaultEditorButton); + expect(mockOnSelect).toHaveBeenCalledWith(undefined, undefined); + }); + + test('select editor from gallery', () => { + renderComponent({ + expandedId: 'selector', + selectorEditorValue: 'some/editor/id', + definitionEditorValue: undefined, + definitionImageValue: undefined, + }); + + const editorGallery = screen.getByTestId('editor-gallery-content'); + expect(editorGallery).not.toHaveAttribute('hidden'); // initial default editor ID const defaultEditor = within(editorGallery).getByTestId('default-editor-id'); @@ -73,7 +111,7 @@ describe('Editor Selector', () => { // initial selected editor ID const selectedEditor = within(editorGallery).getByTestId('selected-editor-id'); - expect(selectedEditor).toHaveTextContent(''); + expect(selectedEditor).toHaveTextContent('some/editor/id'); const selectEditorButton = within(editorGallery).getByRole('button', { name: 'Select Editor', @@ -97,16 +135,16 @@ describe('Editor Selector', () => { definitionImageValue: undefined, }); - const editorDefinitionPanel = screen.queryByTestId('editor-definition-content')!; - expect(editorDefinitionPanel).not.toBeNull(); + const editorDefinitionPanel = screen.getByTestId('editor-definition-content'); + expect(editorDefinitionPanel).not.toHaveAttribute('hidden'); // initial editor definition state const editorDefinition = within(editorDefinitionPanel).getByTestId('editor-definition'); - expect(editorDefinition).toHaveTextContent(''); + expect(editorDefinition).toBeEmptyDOMElement(); // initial editor image const editorImage = within(editorDefinitionPanel).getByTestId('editor-image'); - expect(editorImage).toHaveTextContent(''); + expect(editorImage).toBeEmptyDOMElement(); const changeDefinitionButton = within(editorDefinitionPanel).getByRole('button', { name: 'Editor Definition Change', diff --git a/packages/dashboard-frontend/src/components/EditorSelector/index.tsx b/packages/dashboard-frontend/src/components/EditorSelector/index.tsx index 8efbb5347..cf7533406 100644 --- a/packages/dashboard-frontend/src/components/EditorSelector/index.tsx +++ b/packages/dashboard-frontend/src/components/EditorSelector/index.tsx @@ -15,27 +15,24 @@ import { AccordionContent, AccordionItem, AccordionToggle, - Button, - Flex, - FlexItem, Panel, PanelHeader, PanelMain, PanelMainBody, + Text, + TextVariants, Title, } from '@patternfly/react-core'; import React from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { EditorDefinition } from '@/components/EditorSelector/Definition'; +import { DocsLink } from '@/components/EditorSelector/DocsLink'; import { EditorGallery } from '@/components/EditorSelector/Gallery'; import { AppState } from '@/store'; import { selectEditors } from '@/store/Plugins/chePlugins/selectors'; -const DOCS_DEFINING_A_COMMON_IDE = - 'https://eclipse.dev/che/docs/stable/end-user-guide/defining-a-common-ide/'; - -type AccordionId = 'selector' | 'definition'; +type AccordionId = 'default' | 'selector' | 'definition'; export type Props = MappedProps & { defaultEditorId: string; @@ -60,13 +57,18 @@ class EditorSelector extends React.PureComponent { selectorEditorValue: '', - expandedId: 'selector', + expandedId: 'default', }; } private handleSelectorValueChange(editorId: string): void { this.setState({ selectorEditorValue: editorId }); - this.props.onSelect(editorId, undefined); + + // propagate the change to the parent component + // only if the selector is expanded + if (this.state.expandedId === 'selector') { + this.props.onSelect(editorId, undefined); + } } private handleDefinitionValueChange( @@ -77,7 +79,12 @@ class EditorSelector extends React.PureComponent { definitionEditorValue: editorDefinition, definitionImageValue: editorImage, }); - this.props.onSelect(editorDefinition, editorImage); + + // propagate the change to the parent component + // only if the definition is expanded + if (this.state.expandedId === 'definition') { + this.props.onSelect(editorDefinition, editorImage); + } } private handleToggle(expandedId: AccordionId): void { @@ -89,7 +96,9 @@ class EditorSelector extends React.PureComponent { const { definitionEditorValue, definitionImageValue, selectorEditorValue } = this.state; - if (expandedId === 'selector') { + if (expandedId === 'default') { + onSelect(undefined, undefined); + } else if (expandedId === 'selector') { onSelect(selectorEditorValue, undefined); } else { onSelect(definitionEditorValue, definitionImageValue); @@ -109,6 +118,35 @@ class EditorSelector extends React.PureComponent { + + { + this.handleToggle('default'); + }} + isExpanded={expandedId === 'default'} + id="accordion-item-default" + > + Use a Default Editor + + + + + + + + The editor defined as a query parameter, at the repository level or at the + Custom Resource level will be used. + + + + + + + + { @@ -170,19 +208,6 @@ class EditorSelector extends React.PureComponent { - - - - - diff --git a/packages/dashboard-frontend/src/components/TagLabel/__tests__/TagLabel.spec.tsx b/packages/dashboard-frontend/src/components/TagLabel/__tests__/TagLabel.spec.tsx deleted file mode 100644 index 2737df602..000000000 --- a/packages/dashboard-frontend/src/components/TagLabel/__tests__/TagLabel.spec.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2018-2024 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ - -import React from 'react'; -import renderer, { ReactTestRendererJSON } from 'react-test-renderer'; - -import TagLabel from '..'; - -describe('TagLabel component', () => { - it('should render latest latest correctly', () => { - const component = ; - - expect(getComponentSnapshot(component)).toMatchSnapshot(); - }); - - it('should render next tag correctly', () => { - const component = ; - - expect(getComponentSnapshot(component)).toMatchSnapshot(); - }); -}); - -function getComponentSnapshot( - component: React.ReactElement, -): null | ReactTestRendererJSON | ReactTestRendererJSON[] { - return renderer.create(component).toJSON(); -} diff --git a/packages/dashboard-frontend/src/components/TagLabel/__tests__/__snapshots__/TagLabel.spec.tsx.snap b/packages/dashboard-frontend/src/components/TagLabel/__tests__/__snapshots__/TagLabel.spec.tsx.snap deleted file mode 100644 index e811241c3..000000000 --- a/packages/dashboard-frontend/src/components/TagLabel/__tests__/__snapshots__/TagLabel.spec.tsx.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TagLabel component should render latest latest correctly 1`] = ` - - - latest - - -`; - -exports[`TagLabel component should render next tag correctly 1`] = ` - - - next - - -`; diff --git a/packages/dashboard-frontend/src/components/TagLabel/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/components/TagLabel/__tests__/__snapshots__/index.spec.tsx.snap new file mode 100644 index 000000000..8513e83cf --- /dev/null +++ b/packages/dashboard-frontend/src/components/TagLabel/__tests__/__snapshots__/index.spec.tsx.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TagLabel component snapshot type tag 1`] = ` + + + Tech Preview + + +`; + +exports[`TagLabel component snapshot type version 1`] = ` + + + latest + + +`; diff --git a/packages/dashboard-frontend/src/components/TagLabel/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/TagLabel/__tests__/index.spec.tsx new file mode 100644 index 000000000..214ac37fb --- /dev/null +++ b/packages/dashboard-frontend/src/components/TagLabel/__tests__/index.spec.tsx @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import React from 'react'; + +import { Props, TagLabel } from '@/components/TagLabel'; +import getComponentRenderer from '@/services/__mocks__/getComponentRenderer'; + +const { createSnapshot } = getComponentRenderer(getComponent); + +describe('TagLabel component', () => { + test('snapshot type version', () => { + const snapshot = createSnapshot({ text: 'latest', type: 'version' }); + + expect(snapshot.toJSON()).toMatchSnapshot(); + }); + + test('snapshot type tag', () => { + const snapshot = createSnapshot({ text: 'Tech Preview', type: 'tag' }); + expect(snapshot.toJSON()).toMatchSnapshot(); + }); +}); + +function getComponent(props: Props) { + return ; +} diff --git a/packages/dashboard-frontend/src/components/TagLabel/index.module.css b/packages/dashboard-frontend/src/components/TagLabel/index.module.css index 9e8828ece..e4d5db28e 100644 --- a/packages/dashboard-frontend/src/components/TagLabel/index.module.css +++ b/packages/dashboard-frontend/src/components/TagLabel/index.module.css @@ -10,7 +10,7 @@ * Red Hat, Inc. - initial API and implementation */ -.versionLabel { +.label { margin-left: 5px; padding: 0 5px; @@ -21,6 +21,10 @@ background-color: var(--pf-global--BackgroundColor--100); } -.versionLabel .pf-c-label__content::before { +.tag .pf-c-label__content::before { + border-color: var(--pf-global--warning-color--100); +} + +.version .pf-c-label__content::before { border-color: var(--pf-global--link--Color); } diff --git a/packages/dashboard-frontend/src/components/TagLabel/index.tsx b/packages/dashboard-frontend/src/components/TagLabel/index.tsx index 9013e1139..dceac368c 100644 --- a/packages/dashboard-frontend/src/components/TagLabel/index.tsx +++ b/packages/dashboard-frontend/src/components/TagLabel/index.tsx @@ -15,20 +15,23 @@ import React from 'react'; import styles from '@/components/TagLabel/index.module.css'; -type Props = { - version: string; +export type Props = { + text: string; + type: 'version' | 'tag' | 'placeholder'; }; -class TagLabel extends React.PureComponent { +export class TagLabel extends React.PureComponent { public render(): React.ReactElement { - const { version } = this.props; + const { text } = this.props; + + const className = `${styles.label} ${this.props.type === 'tag' ? styles.tag : styles.version}`; + + const color = this.props.type === 'tag' ? 'orange' : 'blue'; return ( -