Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
08e23f5
refactor(breadcrumbs): convert to TypeScript
devin-ai-integration[bot] Feb 21, 2025
b84d8ba
refactor(breadcrumbs): convert to TypeScript and RTL tests
devin-ai-integration[bot] Feb 21, 2025
07f4fcb
chore: update dependencies and tsconfig
devin-ai-integration[bot] Feb 21, 2025
4c2a6c6
chore: cleanup old files
devin-ai-integration[bot] Feb 21, 2025
7b74be6
chore: restore original files
devin-ai-integration[bot] Feb 24, 2025
dfb99b6
refactor(breadcrumbs): update BreadcrumbDelimiter to use Gray50 token
devin-ai-integration[bot] Feb 24, 2025
ce8acb1
refactor(breadcrumbs): update BreadcrumbDropdown
devin-ai-integration[bot] Feb 24, 2025
082aecf
refactor(breadcrumbs): update InlineBreadcrumbs type and props
devin-ai-integration[bot] Feb 24, 2025
e11c56e
fix(breadcrumbs): fix syntax and type errors
devin-ai-integration[bot] Feb 24, 2025
5042f62
test(breadcrumbs): remove type assertions and jest-dom import
devin-ai-integration[bot] Feb 24, 2025
0f78325
chore: remove unnecessary index.ts file
devin-ai-integration[bot] Feb 24, 2025
2cfdaa4
chore: restore tsconfig.json to original state
devin-ai-integration[bot] Feb 24, 2025
87f7ac9
test: add polyfills for setImmediate and TextEncoder
devin-ai-integration[bot] Feb 24, 2025
46c7771
test: fix duplicate ResizeObserver class
devin-ai-integration[bot] Feb 24, 2025
755faf8
test: add matchMedia polyfill for tests
devin-ai-integration[bot] Feb 24, 2025
52c86bc
fix(breadcrumbs): fix aria-label and onCrumbClick handler
devin-ai-integration[bot] Feb 24, 2025
df8ca1b
chore: remove auto-generated testing-library.js
devin-ai-integration[bot] Feb 24, 2025
09f59fa
fix(breadcrumbs): remove unused imports and props
devin-ai-integration[bot] Feb 24, 2025
5f17b7d
chore(i18n): update translations
devin-ai-integration[bot] Feb 24, 2025
3b6f12f
chore(i18n): update translations from build
devin-ai-integration[bot] Feb 24, 2025
0bca636
fix(breadcrumbs): restore code patterns and fix TS errors
devin-ai-integration[bot] Feb 24, 2025
9bda568
fix(breadcrumbs): update dropdown button aria-label
devin-ai-integration[bot] Feb 24, 2025
ce7225b
fix(breadcrumbs): restore original code patterns and fix test
devin-ai-integration[bot] Feb 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions scripts/jest/jest-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,37 @@
import '@testing-library/jest-dom';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restore this file

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restore the change made in jest-setup.ts file

import util from 'util';

// Polyfill setImmediate
global.setImmediate = callback => setTimeout(callback, 0);

// Polyfill TextEncoder
Object.defineProperty(global, 'TextEncoder', {
value: util.TextEncoder,
});

// Mock ResizeObserver since it's not available in JSDOM
/* eslint-disable @typescript-eslint/no-empty-function */
class ResizeObserver {
observe() {}

unobserve() {}

disconnect() {}
}
/* eslint-enable @typescript-eslint/no-empty-function */
global.ResizeObserver = ResizeObserver;

// Mock matchMedia since it's not available in JSDOM
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});

global.setImmediate = cb => {
setTimeout(cb, 0);
};

Object.defineProperty(global, 'TextEncoder', {
value: util.TextEncoder,
});

global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}));
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
/**
* @flow
* @file Clickable breadcrumb component
* @author Box
*/

import * as React from 'react';
import { TextButton } from '@box/blueprint-web';
import BreadcrumbDelimiter from './BreadcrumbDelimiter';
import type { Delimiter } from '../../../common/types/core';

import './Breadcrumb.scss';

type Props = {
delimiter?: Delimiter,
isLast?: boolean,
name: string,
onClick?: Function,
};
export interface BreadcrumbProps {
delimiter?: Delimiter;
isLast?: boolean;
name: string;
onClick?: () => void;
}

const Breadcrumb = ({ name = '', onClick, isLast, delimiter }: Props) => {
const Breadcrumb = ({ name = '', onClick, isLast, delimiter }: BreadcrumbProps) => {
const title = onClick ? (
<TextButton className="bdl-Breadcrumb-title" inheritFont onClick={onClick}>
{name}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
/**
* @flow
* @file Clickable breadcrumb component
* @author Box
*/

import * as React from 'react';
import { PointerChevronRight } from '@box/blueprint-web-assets/icons/Fill';
import { Gray50, Size3 } from '@box/blueprint-web-assets/tokens/tokens';
import type { Delimiter } from '../../../common/types/core';
import { DELIMITER_CARET } from '../../../constants';

type Props = {
delimiter?: Delimiter,
};
export interface BreadcrumbDelimiterProps {
delimiter?: Delimiter;
}

const BreadcrumbDelimiter = ({ delimiter }: Props) =>
const BreadcrumbDelimiter = ({ delimiter }: BreadcrumbDelimiterProps) =>
delimiter === DELIMITER_CARET ? (
<PointerChevronRight
className="be-breadcrumb-seperator"
Expand All @@ -24,7 +18,7 @@ const BreadcrumbDelimiter = ({ delimiter }: Props) =>
width={Size3}
/>
) : (
<span>/</span>
<span className="be-breadcrumb-seperator">/</span>
);

export default BreadcrumbDelimiter;
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
/**
* @flow
* @file Drop down part of breadcrumbs
* @author Box
*/

import * as React from 'react';
import { DropdownMenu, IconButton } from '@box/blueprint-web';
import { Ellipsis } from '@box/blueprint-web-assets/icons/Fill';
import type { Crumb } from '../../../common/types/core';

import messages from '../messages';

type Props = {
className: string,
crumbs: Crumb[],
onCrumbClick: Function,
};
export interface BreadcrumbDropdownProps {
crumbs: Crumb[];
onCrumbClick: (id: string) => void;
}

const BreadcrumbDropdown = ({ crumbs, onCrumbClick }: Props) => (
const BreadcrumbDropdown = ({ crumbs, onCrumbClick }: BreadcrumbDropdownProps) => (
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<IconButton aria-label={messages.breadcrumbLabel.defaultMessage} icon={Ellipsis} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
/**
* @flow
* @file Component that creates breadcumbs for both the header and name details
* @author Box
*/

import * as React from 'react';
import { useIntl } from 'react-intl';

import Breadcrumb from './Breadcrumb';
import BreadcrumbDropdown from './BreadcrumbDropdown';
import BreadcrumbDelimiter from './BreadcrumbDelimiter';
import type { Delimiter, Crumb } from '../../../common/types/core';
import { DELIMITER_CARET, DEFAULT_ROOT, DELIMITER_SLASH } from '../../../constants';

import './Breadcrumbs.scss';

import type { Crumb, Delimiter } from '../../../common/types/core';
import { DEFAULT_ROOT } from '../../../constants';
import messages from '../messages';
import './Breadcrumbs.scss';

type Props = {
crumbs: Crumb[],
delimiter: Delimiter,
isSmall?: boolean,
onCrumbClick: Function,
rootId: string,
};
export interface BreadcrumbsProps {
crumbs: Crumb[];
delimiter?: Delimiter;
isSmall?: boolean;
onCrumbClick: (crumb: Crumb) => void;
rootId: string;
}

/**
* Filters out ancestors to root from the crumbs.
Expand All @@ -47,26 +37,24 @@ function filterCrumbs(rootId: string, crumbs: Crumb[]): Crumb[] {
* @param {boolean} isLast is this the last crumb
* @return {Element}
*/
function getBreadcrumb(crumbs: Crumb | Crumb[], isLast: boolean, onCrumbClick: Function, delimiter: Delimiter) {
function getBreadcrumb(
crumbs: Crumb | Crumb[],
isLast: boolean,
onCrumbClick: (crumb: Crumb) => void,
delimiter: Delimiter,
) {
if (Array.isArray(crumbs)) {
const condensed = delimiter !== DELIMITER_CARET;
return (
<span className="be-breadcrumb-more">
<BreadcrumbDropdown
className={condensed ? 'be-breadcrumbs-condensed' : ''}
crumbs={crumbs}
onCrumbClick={onCrumbClick}
/>
<BreadcrumbDelimiter delimiter={condensed ? DELIMITER_SLASH : DELIMITER_CARET} />
<BreadcrumbDropdown crumbs={crumbs} onCrumbClick={id => onCrumbClick(crumbs.find(c => c.id === id)!)} />
</span>
);
}

const { id, name } = crumbs;
return <Breadcrumb delimiter={delimiter} isLast={isLast} name={name} onClick={() => onCrumbClick(id)} />;
return <Breadcrumb delimiter={delimiter} isLast={isLast} name={crumbs.name} onClick={() => onCrumbClick(crumbs)} />;
}

const Breadcrumbs = ({ rootId, crumbs, onCrumbClick, delimiter, isSmall = false }: Props) => {
const Breadcrumbs = ({ crumbs = [], delimiter, isSmall = false, onCrumbClick, rootId }: BreadcrumbsProps) => {
const { formatMessage } = useIntl();

if (!rootId || crumbs.length === 0) {
Expand All @@ -86,27 +74,27 @@ const Breadcrumbs = ({ rootId, crumbs, onCrumbClick, delimiter, isSmall = false

// Always show the last/leaf breadcrumb.
const crumb = filteredCrumbs[length - 1];
const onClick = crumb.id ? () => onCrumbClick(crumb.id) : undefined;
const onClick = crumb.id ? () => onCrumbClick(crumb) : undefined;
const lastBreadcrumb = <Breadcrumb isLast name={crumb.name} onClick={onClick} />;

// Always show the second last/parent breadcrumb when there are at least 2 crumbs.
const secondLastBreadcrumb =
length > 1 ? getBreadcrumb(filteredCrumbs[length - 2], false, onCrumbClick, delimiter) : null;

// Only show the more dropdown when there were at least 4 crumbs.
// Only show the more dropdown when there were at least 3 crumbs.
const moreBreadcrumbs =
length > 3 ? getBreadcrumb(filteredCrumbs.slice(1, length - 2), false, onCrumbClick, delimiter) : null;
length > 2 ? getBreadcrumb([...filteredCrumbs.slice(0, length - 1)], false, onCrumbClick, delimiter) : null;

// Only show the root breadcrumb when there are at least 3 crumbs.
const firstBreadcrumb = length > 2 ? getBreadcrumb(filteredCrumbs[0], false, onCrumbClick, delimiter) : null;

return (
<div className="be-breadcrumbs">
<nav aria-label="Folder path" className={`be-breadcrumbs${isSmall ? ' is-small' : ''}`}>
{isSmall ? null : firstBreadcrumb}
{isSmall ? null : moreBreadcrumbs}
{secondLastBreadcrumb}
{lastBreadcrumb}
</div>
</nav>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
/**
* @flow
* @file Component for the details for the item name
* @author Box
*/

import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import messages from '../messages';
Expand All @@ -12,13 +6,13 @@ import { DELIMITER_SLASH } from '../../../constants';
import type { BoxItem } from '../../../common/types/core';
import './InlineBreadcrumbs.scss';

type Props = {
item: BoxItem,
onItemClick: Function,
rootId: string,
};
export interface InlineBreadcrumbsProps {
item: BoxItem;
onItemClick: (item: BoxItem | string) => void;
rootId: string;
}

const InlineBreadcrumbs = ({ item, onItemClick, rootId }: Props) => {
const InlineBreadcrumbs = ({ item, onItemClick, rootId }: InlineBreadcrumbsProps) => {
const { path_collection }: BoxItem = item;
const { entries: breadcrumbs = [] } = path_collection || {};
return (
Expand Down
14 changes: 0 additions & 14 deletions src/elements/common/breadcrumbs/__tests__/Breadcrumbs.test.js

This file was deleted.

40 changes: 40 additions & 0 deletions src/elements/common/breadcrumbs/__tests__/Breadcrumbs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from 'react';
import { render, screen } from '../../../../test-utils/testing-library';
import Breadcrumbs from '../Breadcrumbs';
import messages from '../../messages';

describe('elements/common/breadcrumbs/Breadcrumbs', () => {
const renderComponent = (props = {}) => {
const defaultProps = {
crumbs: [{ id: '0', name: 'All Files' }],
delimiter: 'caret' as const,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update above two lines to

crumbs: [{ id: '0', name: 'All Files' }],
delimiter: 'caret',

onCrumbClick: jest.fn(),
rootId: '123123',
};
render(<Breadcrumbs {...defaultProps} {...props} />);
};

test('should render "All Files" breadcrumb', () => {
renderComponent();
expect(screen.getByRole('button', { name: 'All Files' })).toBeInTheDocument();
});

test('should render dropdown when there are three or more crumbs', () => {
const crumbs = [
{ id: '0', name: 'All Files' },
{ id: '1', name: 'Folder 1' },
{ id: '2', name: 'Folder 2' },
];
renderComponent({ crumbs });
expect(screen.getByRole('button', { name: messages.breadcrumbLabel.defaultMessage })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Folder 2' })).toBeInTheDocument();
});

test('should call onCrumbClick when clicking a breadcrumb', () => {
const onCrumbClick = jest.fn();
const crumbs = [{ id: '0', name: 'All Files' }];
renderComponent({ crumbs, onCrumbClick });
screen.getByRole('button', { name: 'All Files' }).click();
expect(onCrumbClick).toHaveBeenCalledWith(crumbs[0]);
});
});
28 changes: 17 additions & 11 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5559,26 +5559,25 @@
lz-string "^1.5.0"
pretty-format "^27.0.2"

"@testing-library/jest-dom@6.4.5":
version "6.4.5"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.5.tgz#badb40296477149136dabef32b572ddd3b56adf1"
integrity sha512-AguB9yvTXmCnySBP1lWjfNNUwpbElsaQ567lt2VdGqAdHtpieLgjmcVyv1q7PMIvLbgpDdkWV5Ydv3FEejyp2A==
"@testing-library/jest-dom@*", "@testing-library/jest-dom@^6.6.3":
version "6.6.3"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz#26ba906cf928c0f8172e182c6fe214eb4f9f2bd2"
integrity sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==
dependencies:
"@adobe/css-tools" "^4.3.2"
"@babel/runtime" "^7.9.2"
"@adobe/css-tools" "^4.4.0"
aria-query "^5.0.0"
chalk "^3.0.0"
css.escape "^1.5.1"
dom-accessibility-api "^0.6.3"
lodash "^4.17.21"
redent "^3.0.0"

"@testing-library/jest-dom@^6.4.6":
version "6.4.6"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.6.tgz#ec1df8108651bed5475534955565bed88c6732ce"
integrity sha512-8qpnGVincVDLEcQXWaHOf6zmlbwTKc6Us6PPu4CRnPXCzo2OGBS5cwgMMOWdxDpEz1mkbvXHpEy99M5Yvt682w==
"@testing-library/jest-dom@6.4.5":
version "6.4.5"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.5.tgz#badb40296477149136dabef32b572ddd3b56adf1"
integrity sha512-AguB9yvTXmCnySBP1lWjfNNUwpbElsaQ567lt2VdGqAdHtpieLgjmcVyv1q7PMIvLbgpDdkWV5Ydv3FEejyp2A==
dependencies:
"@adobe/css-tools" "^4.4.0"
"@adobe/css-tools" "^4.3.2"
"@babel/runtime" "^7.9.2"
aria-query "^5.0.0"
chalk "^3.0.0"
Expand Down Expand Up @@ -6244,6 +6243,13 @@
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.12.tgz#bc2cab12e87978eee89fb21576b670350d6d86ab"
integrity sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q==

"@types/testing-library__jest-dom@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-6.0.0.tgz#b558b64b80a72130714be1f505c6df482d453690"
integrity sha512-bnreXCgus6IIadyHNlN/oI5FfX4dWgvGhOPvpr7zzCYDGAPIfvyIoAozMBINmhmsVuqV0cncejF2y5KC7ScqOg==
dependencies:
"@testing-library/jest-dom" "*"

"@types/tough-cookie@*":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304"
Expand Down