Skip to content

Commit

Permalink
fix(docgen-sidebar): add collapsible component to hide nested tags (#…
Browse files Browse the repository at this point in the history
…3626)

* fix(docgen-sidebar): add collapsible component to hide nested tags

* fix(docgen-sidebar): nesting of tags in tag tree

* fix(docgen-tags): update styles to use blueprint tokens

* fix(docgen-tags): replace bdl tokens with blueprint tokens

* fix(docgen-tags): convert test from enzyme to rtl

* fix(docgen-tags): remove snapshots, reuse blueprint loader

* fix(docgen-tags): address code review comments

* fix(docgen-tags): remove the sidebar test file

* fix(docgen-tags): file with updated name

* Update src/elements/content-sidebar/__tests__/DocGenSidebar.test.tsx

Co-authored-by: greg-in-a-box <103291617+greg-in-a-box@users.noreply.github.com>

* Update src/elements/content-sidebar/__tests__/DocGenSidebar.test.tsx

Co-authored-by: greg-in-a-box <103291617+greg-in-a-box@users.noreply.github.com>

* fix(docgen-sidebar): address code review comments

* fix(docgen-sidebar): address code review comments

* fix(docgen-sidebar): remove unused mock

---------

Co-authored-by: greg-in-a-box <103291617+greg-in-a-box@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 16, 2024
1 parent 7b0e0fb commit 3480efb
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 1,450 deletions.
61 changes: 44 additions & 17 deletions src/elements/content-sidebar/DocGenSidebar/DocGenSidebar.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
@use '@box/blueprint-web-assets/tokens/tokens.scss';

@import '../../common/variables';

.bcs-DocGenSidebar {
padding: 0 24px 0 24px;
padding: 0 tokens.$space-6 0 tokens.$space-6;
}

.bcs-DocGen--center {
Expand All @@ -10,26 +12,51 @@
}

.bcs-TagsSection {
margin-top: 16px;
padding-bottom: 16px;
border-bottom: 1px solid $bdl-gray-10;
margin-top: tokens.$space-6;
padding-bottom: tokens.$space-6;
border-bottom: tokens.$border-1 solid tokens.$gray-10;
}

.bcs-TagsSection-header {
font-size: $bdl-fontSize-minimum;
line-height: 14px;
font-size: tokens.$caption-default-font-size;
line-height: tokens.$caption-default-line-height;
}

.bcs-DocGen-accordion {
padding: 0;

&:first-child {
padding: 0;
}
}

.bcs-TagsSection-accordion-wrapper {
width: '100%';
min-width: 'fit-content';
height: '100%';
overflow-x: 'auto';
overflow-y: 'auto';
}

.bcs-DocGen-accordion * {
width: auto;
padding: 0;
font-size: tokens.$body-default-bold-font-size;

.bcs-DocGen-tagPath {
padding-left: 0;
font-weight: bold;
}
}

.bcs-DocGen-tagPath {
display: block;
height: 32px;
padding: 6px 12px;
font-weight: bold;
font-size: $bdl-fontSize--dejaBlue;
.bcs-DocGen-collapsible {
padding-top: tokens.$space-3;
padding-right: tokens.$space-6;
border: none;
}

.bcs-DocGenSidebar-loading {
margin-top: 16px;
margin-top: tokens.$space-4;
}

.bcs-DocGen-emptyState {
Expand All @@ -39,8 +66,8 @@
}

.bcs-DocGen-emptyState--icon {
margin-top: 60px;
margin-bottom: 15px;
margin-top: tokens.$space-15;
margin-bottom: tokens.$space-4;
}

.bcs-DocGen-error-state {
Expand All @@ -49,7 +76,7 @@
align-items: center;

&--icon {
margin-top: 60px;
margin-bottom: 15px;
margin-top: tokens.$space-15;
margin-bottom: tokens.$space-4;
}
}
29 changes: 17 additions & 12 deletions src/elements/content-sidebar/DocGenSidebar/DocGenSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as React from 'react';
import classNames from 'classnames';
import flow from 'lodash/flow';
import { injectIntl, IntlShape } from 'react-intl';
import { useIntl } from 'react-intl';

import { LoadingIndicator } from '@box/blueprint-web';

// @ts-ignore: no ts definition
// eslint-disable-next-line import/named
Expand All @@ -12,7 +14,6 @@ import { withAPIContext } from '../../common/api-context';
// @ts-ignore: no ts definition
// eslint-disable-next-line import/named
import { withErrorBoundary } from '../../common/error-boundary';
import LoadingIndicator from '../../../components/loading-indicator';
// @ts-ignore: no ts definition
// eslint-disable-next-line import/named
import { withLogger } from '../../common/logger';
Expand All @@ -27,6 +28,7 @@ import messages from './messages';
import { ErrorContextProps } from '../../../common/types/api';
// @ts-ignore: no ts definition
import { WithLoggerProps } from '../../../common/types/logging';
import commonMessages from '../../common/messages';

import './DocGenSidebar.scss';
import { DocGenTag, DocGenTemplateTagsResponse, JsonPathsMap } from './types';
Expand All @@ -38,11 +40,7 @@ type ExternalProps = {
checkDocGenTemplate: void;
};

type Props = {
intl: IntlShape;
} & ExternalProps &
ErrorContextProps &
WithLoggerProps;
type Props = ExternalProps & ErrorContextProps & WithLoggerProps;

type TagState = {
text: DocGenTag[];
Expand All @@ -54,7 +52,9 @@ type JsonPathsState = {
imageTree: JsonPathsMap;
};

const DocGenSidebar = ({ intl, getDocGenTags }: Props) => {
const DocGenSidebar = ({ getDocGenTags }: Props) => {
const { formatMessage } = useIntl();

const [hasError, setHasError] = React.useState<boolean>(false);
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [tags, setTags] = React.useState<TagState>({
Expand Down Expand Up @@ -124,10 +124,15 @@ const DocGenSidebar = ({ intl, getDocGenTags }: Props) => {
const isEmpty = tags.image.length + tags.text.length === 0;

return (
<SidebarContent sidebarView={SIDEBAR_VIEW_DOCGEN} title={intl.formatMessage(messages.docGenTags)}>
<SidebarContent sidebarView={SIDEBAR_VIEW_DOCGEN} title={formatMessage(messages.docGenTags)}>
<div className={classNames('bcs-DocGenSidebar', { center: isEmpty || hasError || isLoading })}>
{hasError && <Error onClick={loadTags} />}
{isLoading && <LoadingIndicator className="bcs-DocGenSidebar-loading" />}
{isLoading && (
<LoadingIndicator
aria-label={formatMessage(commonMessages.loading)}
className="bcs-DocGenSidebar-loading"
/>
)}
{!hasError && !isLoading && isEmpty && <EmptyTags />}
{!hasError && !isLoading && !isEmpty && (
<>
Expand All @@ -142,6 +147,6 @@ const DocGenSidebar = ({ intl, getDocGenTags }: Props) => {

export type DocGenSidebarProps = ExternalProps;
export { DocGenSidebar as DocGenSidebarComponent };
export default injectIntl(
flow([withLogger(ORIGIN_DOCGEN_SIDEBAR), withErrorBoundary(ORIGIN_DOCGEN_SIDEBAR), withAPIContext])(DocGenSidebar),
export default flow([withLogger(ORIGIN_DOCGEN_SIDEBAR), withErrorBoundary(ORIGIN_DOCGEN_SIDEBAR), withAPIContext])(
DocGenSidebar,
);
2 changes: 1 addition & 1 deletion src/elements/content-sidebar/DocGenSidebar/Error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type Props = {
};

const Error = ({ onClick }: Props) => (
<div className="bcs-DocGen-error-state">
<div className="bcs-DocGen-error-state" data-testid="docgen-sidebar-error">
<RefreshIcon className="bcs-DocGen-error-state--icon" />
<div>
<FormattedMessage {...messages.errorCouldNotLoad} />
Expand Down
28 changes: 20 additions & 8 deletions src/elements/content-sidebar/DocGenSidebar/TagTree.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import * as React from 'react';
import isEmpty from 'lodash/isEmpty';
import { Accordion } from '@box/blueprint-web';

import './DocGenSidebar.scss';
import { JsonPathsMap } from './types';

Expand All @@ -13,16 +16,25 @@ const TagTree = ({ data, level = 0 }: TagTreeProps) => {
}

return (
<div>
<Accordion
type='multiple'
className="bcs-DocGen-accordion"
>
{Object.keys(data)
.sort()
.map(key => (
<div key={`${key}-${level}`} style={{ paddingLeft: `${level * 12}px` }}>
<span className="bcs-DocGen-tagPath">{key}</span>
{data[key] && <TagTree data={data[key]} level={level + 1} />}
</div>
))}
</div>
.map(key => {
if (isEmpty(data[key])) {
return(
<Accordion.Item value={key} key={`${key}-${level}`} style={{ paddingLeft: `${level * 12}px` }} fixed className="bcs-DocGen-collapsible">
<span className="bcs-DocGen-tagPath">{key}</span>
</Accordion.Item>
)
}
return (<Accordion.Item value={key} title={key} key={`${key}-${level}`} style={{ paddingLeft: `${level * 12}px` }} className="bcs-DocGen-collapsible">
{data[key] && <TagTree data={data[key]} level={level + 1} />}
</Accordion.Item>)
})}
</Accordion>
);
};

Expand Down
7 changes: 4 additions & 3 deletions src/elements/content-sidebar/DocGenSidebar/TagsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ const TagsSection = ({ data, message }: Props) => {
}

return (
<div className="bcs-TagsSection">
<div className="bcs-TagsSection" data-testid="bcs-TagsSection">
<span className="bcs-TagsSection-header">
<FormattedMessage {...message} />
</span>

<TagTree data={data} />
<div className="bcs-TagsSection-accordion-wrapper">
<TagTree data={data} />
</div>
</div>
);
};
Expand Down
105 changes: 105 additions & 0 deletions src/elements/content-sidebar/__tests__/DocGenSidebar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from 'react';
import { render, screen, waitFor, fireEvent } from '../../../test-utils/testing-library';

import { DocGenSidebarComponent as DocGenSidebar } from '../DocGenSidebar/DocGenSidebar';
import mockData from '../__mocks__/DocGenSidebar.mock';

const docGenSidebarProps = {
getDocGenTags: jest.fn().mockReturnValue(
Promise.resolve({
pagination: {},
data: mockData,
}),
),
};

const noTagsMock = jest.fn().mockReturnValue(Promise.resolve({ data: [] }));
const errorTagsMock = jest.fn().mockRejectedValue([]);
const noDataMock = jest.fn().mockReturnValue(Promise.resolve({}));


describe('elements/content-sidebar/DocGenSidebar', () => {
const renderComponent = (props = {}) =>
render(<DocGenSidebar logger={{ onReadyMetric: jest.fn() }} {...docGenSidebarProps} {...props} />);

test('componentDidMount() should call fetch tags', async () => {
renderComponent();
await waitFor(() => expect(docGenSidebarProps.getDocGenTags).toHaveBeenCalled());
});

test('should render DocGen sidebar component correctly with tags list', async () => {
renderComponent();
const tagList = await screen.findAllByTestId('bcs-TagsSection');
expect(tagList).toHaveLength(2);
});

test('should render DocGen sidebar component correctly with tags list', async () => {
renderComponent();
const parentTag = await screen.findByText('about');
let nestedTag = await screen.queryByText('name');

expect(parentTag).toBeInTheDocument();
expect(nestedTag).not.toBeInTheDocument();

fireEvent.click(parentTag);

nestedTag = await screen.findByText('name');
expect(nestedTag).toBeInTheDocument();
});

test('should render empty state when there are no tags', async () => {
renderComponent({
getDocGenTags: noTagsMock,
});

const emptyState = await screen.findByText('This document has no tags');
expect(emptyState).toBeInTheDocument();
});

test('should render loading state', async () => {
const mockGetDocGenTags = jest.fn().mockReturnValue(
new Promise((resolve) => {
setTimeout(() => {
resolve({
data: mockData,
});
}, 1000);
})
);

renderComponent({
getDocGenTags: mockGetDocGenTags,
});

await waitFor(() => {
expect(screen.getByRole('status', { name: 'Loading' })).toBeInTheDocument();
});

await waitFor(() => {
expect(screen.queryByRole('status', { name: 'Loading' })).not.toBeInTheDocument();
});
});

test('should re-trigger getDocGenTags on click on refresh button', async () => {
renderComponent({
getDocGenTags: errorTagsMock,
});

const errorState = await screen.findByTestId('docgen-sidebar-error');
expect(errorState).toBeInTheDocument();

const refreshButton = screen.getByRole('button', { name: 'Refresh' });
fireEvent.click(refreshButton);

await waitFor(() => expect(errorTagsMock).toBeCalledTimes(2));
});

test('should handle undefined data', async () => {
renderComponent({
getDocGenTags: noDataMock,
});

const emptyState = await screen.findByText("We couldn't load the tags");
expect(emptyState).toBeInTheDocument();
});
});
Loading

0 comments on commit 3480efb

Please sign in to comment.