chore(ui): improve BreadcrumbItem#1756
Conversation
Signed-off-by: I531348 <guoda.puidokaite@sap.com>
🦋 Changeset detectedLatest commit: 9ae51ac The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
There was a problem hiding this comment.
Pull request overview
This PR refactors BreadcrumbItem in @cloudoperators/juno-ui-components to render semantically correct elements (<a>, <button>, <span>) based on href, onClick, active, and disabled, and documents the breaking change via a major changeset.
Changes:
- Update
BreadcrumbItemrendering logic (remove defaulthref="#", add button path foronClick-only, addaria-current/aria-disabled). - Update unit tests to reflect the new rendering semantics and accessibility attributes.
- Update Storybook stories and add a major changeset entry for the breaking change.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| packages/ui-components/src/components/BreadcrumbItem/BreadcrumbItem.component.tsx | Implements the new semantic rendering matrix and ARIA attributes for BreadcrumbItem. |
| packages/ui-components/src/components/BreadcrumbItem/BreadcrumbItem.test.tsx | Updates/extends tests to match new render paths and accessibility expectations. |
| packages/ui-components/src/components/BreadcrumbItem/BreadcrumbItem.stories.tsx | Adjusts stories and adds explicit Link/Button examples for the new behaviors. |
| .changeset/chilly-trams-jam.md | Declares a major bump and documents the breaking behavior/DOM changes. |
| const onClickSpy = vi.fn() | ||
| render(<BreadcrumbItem href="#" disabled data-testid="breadcrumbitem" onClick={onClickSpy} />) | ||
| expect(screen.getByTestId("breadcrumbitem")).toBeInTheDocument() | ||
| expect(screen.getByTestId("breadcrumbitem")).toHaveClass("juno-breadcrumb-item-disabled") | ||
| const element = screen.getByTestId("breadcrumbitem") | ||
| expect(element).toBeInTheDocument() | ||
| expect(element).toHaveAttribute("aria-disabled", "true") |
There was a problem hiding this comment.
I actually agree with this comment. We should assert that disabled items don't render as links to catch potential regressions, since the acceptance criteria states that in the disabled case we don't render a link at all.
Signed-off-by: I531348 <guoda.puidokaite@sap.com>
Signed-off-by: I531348 <guoda.puidokaite@sap.com>
| /** | ||
| * The URL that the breadcrumb item points to for navigation. | ||
| * @default "#" | ||
| * @default undefined |
edda
left a comment
There was a problem hiding this comment.
Blocker comment to give me time to review
| <a href={href} className={breadcrumbLinkBaseStyles} aria-label={ariaLabel || label} onClick={handleLinkClick}> | ||
| if (href) { | ||
| // Render the breadcrumb item as a link if href is provided and apply breadcrumbLinkBaseStyles | ||
| const linkClassName = `${combinedClassName} ${breadcrumbLinkBaseStyles}` |
There was a problem hiding this comment.
For the button type we render only combinedClassName. We should do the same here. Looking at the contents of breadcrumbLinkBaseStyles they are essentially duplicates of styles already present in combineClassName. In order to ensure the link and button styles don't drift apart let's remove the breadcrumbLinkBaseStyles constant and the application of them here and use only combinedClassName
There was a problem hiding this comment.
Agree. Also, cobinedClassName sets jn:flex, which is then overwritten with jn:inline-flex when breadcrumbLinkBaseStyles is applied, so merging these two seems to clarify things here as well.
| const imgElement = screen.getByRole("img") | ||
| expect(imgElement).toBeInTheDocument() | ||
| expect(imgElement).toHaveAttribute("alt", "help") | ||
| }) |
There was a problem hiding this comment.
Why did you remove this assertion? It's good to verify that the icon is rendered if we specifically use the icon prop for this test
| const onClickSpy = vi.fn() | ||
| render(<BreadcrumbItem href="#" disabled data-testid="breadcrumbitem" onClick={onClickSpy} />) | ||
| expect(screen.getByTestId("breadcrumbitem")).toBeInTheDocument() | ||
| expect(screen.getByTestId("breadcrumbitem")).toHaveClass("juno-breadcrumb-item-disabled") | ||
| const element = screen.getByTestId("breadcrumbitem") | ||
| expect(element).toBeInTheDocument() | ||
| expect(element).toHaveAttribute("aria-disabled", "true") |
There was a problem hiding this comment.
I actually agree with this comment. We should assert that disabled items don't render as links to catch potential regressions, since the acceptance criteria states that in the disabled case we don't render a link at all.
edda
left a comment
There was a problem hiding this comment.
Additonally, if you look at storybook the Breadcrumb page makes no mention of the different options for breadcrumb items, so if you only look at the main story and never at the BreadcrumbItem stories, you 'll miss this completely. As we did for other components that have children, let's add at least one story to the Breadcrumb page which contains a bunch of different types of breadcrumb items and in its description refers to the BreadcrumbItems stories.
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: "A breadcrumb item rendered as a <a> with provided href", |
There was a problem hiding this comment.
Html tags in the story are actually rendered as html. So the text of the story when you look at it in storybook actually reads: "A breadcrumb item rendered as a using onClick for interaction." Replace the tag with html entities to make it render as intended.
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: "A breadcrumb item rendered as a <button> using onClick for interaction.", |
There was a problem hiding this comment.
Same as link story above, replace tag with html entities to make it render correctly.
| <a href={href} className={breadcrumbLinkBaseStyles} aria-label={ariaLabel || label} onClick={handleLinkClick}> | ||
| if (href) { | ||
| // Render the breadcrumb item as a link if href is provided and apply breadcrumbLinkBaseStyles | ||
| const linkClassName = `${combinedClassName} ${breadcrumbLinkBaseStyles}` |
There was a problem hiding this comment.
Agree. Also, cobinedClassName sets jn:flex, which is then overwritten with jn:inline-flex when breadcrumbLinkBaseStyles is applied, so merging these two seems to clarify things here as well.
| expect(screen.getByTestId("breadcrumbitem")).toBeInTheDocument() | ||
| expect(screen.getByTestId("breadcrumbitem")).toHaveClass("juno-breadcrumb-item-disabled") | ||
| const element = screen.getByTestId("breadcrumbitem") | ||
| expect(element).toBeInTheDocument() |
There was a problem hiding this comment.
When assigning the element to a constas in the previous line, a matching element either does exist in the document, or the test will fail right away when attempting const assignment, so checking expect(element).toBeInTheDocument() is obsolete – line 92 can safely be removed.
| expect(screen.queryByRole("link")).not.toBeInTheDocument() | ||
| expect(screen.getByTestId("breadcrumbitem")).toBeInTheDocument() | ||
| }) | ||
|
|
There was a problem hiding this comment.
According to the logic, the href branch fires first in case both href and onClick are passed (which is correct/as designed). We should explicitly test this behaviour by adding a new test ("renders a link if both href and onClick are passed") in order to catch any regressions, and as it is not totally obvious.
Also the onClick handler should be tested for this case explicitly.
Summary
Updated
BreadcrumbItemwith BREAKING CHANGES:Changes Made
See ACs to verify changes made.
Related Issues
Closes #1717
Checklist
PR Manifesto
Review the PR Manifesto for best practises.