[tooltip] Vue 3: ancestor component attributes leak onto tooltip directive's rendered element
Summary
When v-gl-tooltip is used as a directive on an element inside a component, the tooltip's rendered <div> unexpectedly receives attributes from ancestor components that have nothing to do with the tooltip. This happens because templateAttributes() in bv-tooltip-template.js spreads this.bvParent.bvParent.$attrs — and on Vue 3, attributes fall through component hierarchies when components do not declare those attributes as props.
Steps to reproduce / Evidence
In gitlab-org/gitlab (a gitlab-ui consumer):
work_item_labels.vuepassesdata-testid="work-item-labels"to<work-item-sidebar-dropdown-widget>.- That component renders only
<work-item-sidebar-widget>as its root child, so thedata-testidfalls through intoWorkItemSidebarWidget's$attrs. WorkItemSidebarWidgetcontains aGlButtonwithv-gl-tooltip="Change label...".- The tooltip's rendered
<div>receivesdata-testid="work-item-labels"from the fallthrough. - A test using
within('[data-testid="work-item-labels"]')unexpectedly matched the tooltip's<div>(which bootstrap-vue renders to<body>), causing test assertion failures.
This triggered a real CI failure: https://gitlab.com/gitlab-org/gitlab/-/jobs/16018296488 (linked from gitlab-org/gitlab!250548 (comment 3722820268)).
A workaround was applied in gitlab-org/gitlab at spec/features/work_items/issues/issue_sidebar_spec.rb (around line 160): tests now qualify the selector as within('section[data-testid="work-item-labels"]') to avoid matching the tooltip's <div>.
Why this happens
The templateAttributes() getter in packages/gitlab-ui/src/vendor/bootstrap-vue/src/components/tooltip/helpers/bv-tooltip-template.js (lines 69-83) spreads this.bvParent.bvParent.$attrs into the tooltip's rendered attributes.
When v-gl-tooltip is used as a directive, bvParent resolves (via getInstanceFromDirective in packages/gitlab-ui/src/vendor/bootstrap-vue/src/directives/tooltip/tooltip.js) to the Vue component instance that owns the directive's host element — not to a <gl-tooltip> component instance.
On Vue 3, when a component receives an attribute it does not declare as a prop, that attribute enters $attrs. If that component's template renders a single root child component, Vue 3 automatically forwards those attributes ("fallthrough") into the child's $attrs, cascading through single-root component chains. This is documented at https://vuejs.org/guide/components/attrs.html#nested-component-inheritance.
Result: the tooltip directive reads $attrs from an arbitrary ancestor's Vue instance (whichever component instance owns the element carrying the directive), inheriting attributes that were meant for completely different elements.
Impact and risk
Scope: This is not specific to work items. Any component using v-gl-tooltip as a directive, where that component receives undeclared attributes from further-up parents (extremely common — testids, aria labels, data attributes passed through wrapper components), is affected on Vue 3. Every gitlab-ui consumer migrating to Vue 3 will encounter this.
Confirmed impact today: In gitlab-org/gitlab, the only attribute observed leaking so far is data-testid. This only affects tests, not real users, and both known cases have workarounds (see above).
Unconfirmed but likely impact today: The leak is not limited to data-testid. Any data-* attribute could leak the same way, and could change application behavior if that data attribute is read by other code (for example, JavaScript that queries elements by a data-* attribute, or CSS that styles by attribute selector). Any aria-* attribute could also leak, causing accessibility bugs (assistive technology could announce the wrong label or role for a tooltip). Because these leaks only show up when a page is rendered and inspected closely, and are not test-only like data-testid, they could exist elsewhere in gitlab-org/gitlab or in other gitlab-ui consumers without anyone noticing yet.
Future impact: This will worsen when consumers drop Vue 2 compat mode. Currently, gitlab-org/gitlab sets INSTANCE_ATTRS_CLASS_STYLE: 'suppress-warning' in its Vue 3 compat config, which keeps class and style out of $attrs. Once compat mode is removed, class and style also become part of $attrs (see https://v3-migration.vuejs.org/breaking-changes/attrs-includes-class-style.html). At that point, ancestor class and style values will also leak onto tooltip elements, which will very likely produce visible, user-facing bugs (wrong styling or layout on tooltips), not just test or accessibility issues.
The bvParent.bvParent.$attrs spread logic was written for component usage, where bvParent.bvParent predictably refers to the wrapping tooltip component. When used as a directive, bvParent resolves to whatever component owns the directive's host element, making it an unpredictable and uncontrolled source of attributes to copy.
A fix likely needs to reconsider what bvParent.bvParent.$attrs should mean for the directive usage pattern, and whether those attributes should be copied to the tooltip's rendered element at all in that case.
Raised as a non-blocking review comment by @kivikakk on an approved merge request: gitlab-org/gitlab!250548 (comment 3722820268)
Observed with @gitlab/ui 136.3.1.