Conversation
|
Visit the preview URL for this PR (updated for commit 5663bef): https://koobiq-next--prs-2081-h0440y0y.web.app (expires Fri, 25 Sep 2026 12:34:39 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: c9e37e518febda70d0317d07e8ceb35ac43c534c |
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
It deliberately overrides a consumer-set type on a public component, a documented behavior-change judgment call that warrants human sign-off.
Review effort: Balanced
Findings: None
What changed in this PR
This PR fixes a bug where KbqBreadcrumbButton, when applied to a <button> host, left the element at its implicit type="submit". Inside a <form>, this turned every breadcrumb button (including the library-rendered overflow … expand button) into a submit control — activating a breadcrumb submitted the form, and pressing Enter in a field triggered implicit submission through the expand button (the form's default button). The fix writes type="button" on <button> hosts from the directive constructor, leaving anchors untouched.
Changes:
KbqBreadcrumbButtonnow writestype="button"on a<button>host viaRenderer2.setAttribute, guarded onnodeName === 'BUTTON'so anchors are unaffected (SSR-safe).- Added Jest unit coverage (form-submission scenarios, static-type override, anchor exemption, and a non-vacuous control case) and an
E2eBreadcrumbsInFormfixture with a Playwright test for Enter-in-field implicit submission. - Updated the breadcrumbs public API golden file to reflect the new constructor.
| File | Description |
|---|---|
packages/components/breadcrumbs/breadcrumbs.ts |
Adds constructor that sets type="button" on <button> breadcrumb hosts; updates the directive JSDoc. |
packages/components/breadcrumbs/breadcrumbs.spec.ts |
Adds "inside a form" unit suite and the BreadcrumbsInForm test host with static-type and control cases. |
packages/components/breadcrumbs/e2e.ts |
Adds E2eBreadcrumbsInForm fixture with a form wrapping breadcrumbs and two input fields. |
packages/components/breadcrumbs/e2e.playwright-spec.ts |
Adds Playwright cases for Enter-in-field non-submission and type="button" on all breadcrumb buttons. |
packages/e2e/routes.ts |
Registers E2eBreadcrumbsInForm so it is reachable at /E2eBreadcrumbsInForm. |
tools/public_api_guard/components/breadcrumbs.api.md |
Records the new constructor() on KbqBreadcrumbButton. |
The implementation is correct and SSR-safe: it uses Renderer2.setAttribute rather than direct DOM access, guards on nodeName === 'BUTTON' so anchors (where type hints at the link target MIME) are untouched, and does not conflict with KbqButton, which never manipulates type. The unit and Playwright suites cover the click and Enter-key paths, the anchor exemption, the static-type override, and a control case proving the assertions are meaningful. I found no objective defects to flag.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
A breadcrumb navigates, but `KbqBreadcrumbButton` on a `<button>` host left the element at its implicit `type="submit"`. Inside a form that turned every breadcrumb into a submit control: clicking one submitted the form, and pressing Enter in a field activated the first of them as the form's default button — in practice the overflow expand button, which opened its dropdown. `KbqBreadcrumbButton` now writes `type="button"` on a `<button>` host, over any type already there. Anchors are left alone: on `<a>` the attribute hints at the MIME type of the link target. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
98a1a22 to
5663bef
Compare
|
|
||
| // A breadcrumb navigates, so it must never submit a surrounding form — but a `<button>` | ||
| // host defaults to `type="submit"`. Anchors take no `type`. | ||
| if (element.nodeName === 'BUTTON') { |
There was a problem hiding this comment.
почему просто для host элемента не установить type=button? решается 1 строкой, вместо 2 инжектов и доп проверок
There was a problem hiding this comment.
перепроверю, но установка атрибута на хост элементе позволяет его перезаписать, это поведение не нужно
| await expect(buttons).not.toHaveCount(0); | ||
|
|
||
| for (const button of await buttons.all()) { | ||
| await expect(button).toHaveAttribute('type', 'button'); |
There was a problem hiding this comment.
это уже проверяется в unit тестах
There was a problem hiding this comment.
этот кейс вынесен отдельно
jsdom does not implement implicit submission
Summary
A breadcrumb navigates, but
KbqBreadcrumbButtonon a<button>host left the element at its implicittype="submit". Inside a form that turned every breadcrumb into a submit control — reported from a topbar, where breadcrumbs sit inside a form.Two ways it showed up:
…) is the one the library itself renders, so this hit anyone usingmax.KbqBreadcrumbButtonnow writestype="button"on a<button>host, over any type already there — a breadcrumb that submits is a mistake, not an intent. Anchors are untouched: on<a>the attribute hints at the MIME type of the link target, and the default breadcrumb template renders anchors.The attribute is written once from the constructor rather than bound. A
[attr.type]host binding was tried first and rejected: it needs aprotectedmember to stay off anchors (public API surface), and it is order-dependent — it wins the first change detection and then goes quiet, so a consumer's own[attr.type]flip-flops. A statichost: { type: 'button' }was also measured and fails outright: it lands on anchors, and a template-writtentype="submit"beats it.List of notable changes:
KbqBreadcrumbButtonso a<button>breadcrumb never submits a surrounding formE2eBreadcrumbsInFormplus a Playwright case for Enter-in-a-field — jsdom does not implement implicit submission, so this only reproduces in a real browserKbqBreadcrumbButtonnow declares a constructorWhat should reviewers focus on?
typerather than respecting it.type="submit"written on a breadcrumb is now silently replaced. Deliberate, and covered byshould override a statically set type— but it is the one judgement call here.Renderer2.setAttributeguarded onnodeName === 'BUTTON'. A consumer who binds[attr.type]still takes it over from the first change detection; nothing in the library re-asserts it, and no test claims that as a contract.🤖 Generated with Claude Code