mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[CL-879] use tooltip on icon button (#16576)
* Add tooltip to icon button to display label * remove legacy cdr variable * create overlay on focus or hover * attach describdedby ids * fix type errors * remove aria-describedby when not necessary * fix failing tests * implement Claude feedback * fixing broken specs * remove host attr binding * Simplify directive aria logic * Move id to statis number * do not render empty tooltip * pass id to tooltip component * remove pointer-events none to allow tooltip on normal buttons * exclude some tooltip stories * change describedby input name * add story with tooltip on regular button * enhanced tooltip docs * set model directly * change model to input
This commit is contained in:
@@ -92,7 +92,6 @@ export class ButtonComponent implements ButtonLikeAbstraction {
|
|||||||
"hover:!tw-text-muted",
|
"hover:!tw-text-muted",
|
||||||
"aria-disabled:tw-cursor-not-allowed",
|
"aria-disabled:tw-cursor-not-allowed",
|
||||||
"hover:tw-no-underline",
|
"hover:tw-no-underline",
|
||||||
"aria-disabled:tw-pointer-events-none",
|
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { setA11yTitleAndAriaLabel } from "../a11y/set-a11y-title-and-aria-label"
|
|||||||
import { ButtonLikeAbstraction } from "../shared/button-like.abstraction";
|
import { ButtonLikeAbstraction } from "../shared/button-like.abstraction";
|
||||||
import { FocusableElement } from "../shared/focusable-element";
|
import { FocusableElement } from "../shared/focusable-element";
|
||||||
import { SpinnerComponent } from "../spinner";
|
import { SpinnerComponent } from "../spinner";
|
||||||
|
import { TooltipDirective } from "../tooltip";
|
||||||
import { ariaDisableElement } from "../utils";
|
import { ariaDisableElement } from "../utils";
|
||||||
|
|
||||||
export type IconButtonType = "primary" | "danger" | "contrast" | "main" | "muted" | "nav-contrast";
|
export type IconButtonType = "primary" | "danger" | "contrast" | "main" | "muted" | "nav-contrast";
|
||||||
@@ -100,7 +101,10 @@ const sizes: Record<IconButtonSize, string[]> = {
|
|||||||
*/
|
*/
|
||||||
"[attr.bitIconButton]": "icon()",
|
"[attr.bitIconButton]": "icon()",
|
||||||
},
|
},
|
||||||
hostDirectives: [AriaDisableDirective],
|
hostDirectives: [
|
||||||
|
AriaDisableDirective,
|
||||||
|
{ directive: TooltipDirective, inputs: ["tooltipPosition"] },
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableElement {
|
export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableElement {
|
||||||
readonly icon = model.required<string>({ alias: "bitIconButton" });
|
readonly icon = model.required<string>({ alias: "bitIconButton" });
|
||||||
@@ -109,6 +113,9 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE
|
|||||||
|
|
||||||
readonly size = model<IconButtonSize>("default");
|
readonly size = model<IconButtonSize>("default");
|
||||||
|
|
||||||
|
private elementRef = inject(ElementRef);
|
||||||
|
private tooltip = inject(TooltipDirective, { host: true, optional: true });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* label input will be used to set the `aria-label` attributes on the button.
|
* label input will be used to set the `aria-label` attributes on the button.
|
||||||
* This is for accessibility purposes, as it provides a text alternative for the icon button.
|
* This is for accessibility purposes, as it provides a text alternative for the icon button.
|
||||||
@@ -186,8 +193,6 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE
|
|||||||
return this.elementRef.nativeElement;
|
return this.elementRef.nativeElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
private elementRef = inject(ElementRef);
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const element = this.elementRef.nativeElement;
|
const element = this.elementRef.nativeElement;
|
||||||
|
|
||||||
@@ -198,9 +203,15 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE
|
|||||||
effect(() => {
|
effect(() => {
|
||||||
setA11yTitleAndAriaLabel({
|
setA11yTitleAndAriaLabel({
|
||||||
element: this.elementRef.nativeElement,
|
element: this.elementRef.nativeElement,
|
||||||
title: originalTitle ?? this.label(),
|
title: undefined,
|
||||||
label: this.label(),
|
label: this.label(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tooltipContent: string = originalTitle || this.label();
|
||||||
|
|
||||||
|
if (tooltipContent) {
|
||||||
|
this.tooltip?.tooltipContent.set(tooltipContent);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<div
|
@if (tooltipData.content()) {
|
||||||
class="bit-tooltip-container"
|
<div
|
||||||
[attr.data-position]="tooltipData.tooltipPosition()"
|
class="bit-tooltip-container"
|
||||||
[attr.data-visible]="tooltipData.isVisible()"
|
[attr.data-position]="tooltipData.tooltipPosition()"
|
||||||
>
|
[attr.data-visible]="tooltipData.isVisible()"
|
||||||
<div role="tooltip" class="bit-tooltip">
|
>
|
||||||
<ng-content>{{ tooltipData.content() }}</ng-content>
|
<div role="tooltip" class="bit-tooltip" [id]="tooltipData.id()">
|
||||||
|
<ng-content>{{ tooltipData.content() }}</ng-content>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type TooltipData = {
|
|||||||
content: Signal<string>;
|
content: Signal<string>;
|
||||||
isVisible: Signal<boolean>;
|
isVisible: Signal<boolean>;
|
||||||
tooltipPosition: Signal<TooltipPosition>;
|
tooltipPosition: Signal<TooltipPosition>;
|
||||||
|
id: Signal<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TOOLTIP_DATA = new InjectionToken<TooltipData>("TOOLTIP_DATA");
|
export const TOOLTIP_DATA = new InjectionToken<TooltipData>("TOOLTIP_DATA");
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import {
|
|||||||
ElementRef,
|
ElementRef,
|
||||||
Injector,
|
Injector,
|
||||||
input,
|
input,
|
||||||
effect,
|
|
||||||
signal,
|
signal,
|
||||||
|
model,
|
||||||
|
computed,
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
|
|
||||||
import { TooltipPositionIdentifier, tooltipPositions } from "./tooltip-positions";
|
import { TooltipPositionIdentifier, tooltipPositions } from "./tooltip-positions";
|
||||||
@@ -26,30 +27,39 @@ import { TooltipComponent, TOOLTIP_DATA } from "./tooltip.component";
|
|||||||
"(mouseleave)": "hideTooltip()",
|
"(mouseleave)": "hideTooltip()",
|
||||||
"(focus)": "showTooltip()",
|
"(focus)": "showTooltip()",
|
||||||
"(blur)": "hideTooltip()",
|
"(blur)": "hideTooltip()",
|
||||||
|
"[attr.aria-describedby]": "resolvedDescribedByIds()",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export class TooltipDirective implements OnInit {
|
export class TooltipDirective implements OnInit {
|
||||||
|
private static nextId = 0;
|
||||||
/**
|
/**
|
||||||
* The value of this input is forwarded to the tooltip.component to render
|
* The value of this input is forwarded to the tooltip.component to render
|
||||||
*/
|
*/
|
||||||
readonly bitTooltip = input.required<string>();
|
readonly tooltipContent = model("", { alias: "bitTooltip" });
|
||||||
/**
|
/**
|
||||||
* The value of this input is forwarded to the tooltip.component to set its position explicitly.
|
* The value of this input is forwarded to the tooltip.component to set its position explicitly.
|
||||||
* @default "above-center"
|
* @default "above-center"
|
||||||
*/
|
*/
|
||||||
readonly tooltipPosition = input<TooltipPositionIdentifier>("above-center");
|
readonly tooltipPosition = input<TooltipPositionIdentifier>("above-center");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input so the consumer can choose to add the tooltip id to the aria-describedby attribute of the host element.
|
||||||
|
*/
|
||||||
|
readonly addTooltipToDescribedby = input<boolean>(false);
|
||||||
|
|
||||||
private readonly isVisible = signal(false);
|
private readonly isVisible = signal(false);
|
||||||
private overlayRef: OverlayRef | undefined;
|
private overlayRef: OverlayRef | undefined;
|
||||||
private elementRef = inject(ElementRef);
|
private elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
|
||||||
private overlay = inject(Overlay);
|
private overlay = inject(Overlay);
|
||||||
private viewContainerRef = inject(ViewContainerRef);
|
private viewContainerRef = inject(ViewContainerRef);
|
||||||
private injector = inject(Injector);
|
|
||||||
private positionStrategy = this.overlay
|
private positionStrategy = this.overlay
|
||||||
.position()
|
.position()
|
||||||
.flexibleConnectedTo(this.elementRef)
|
.flexibleConnectedTo(this.elementRef)
|
||||||
.withFlexibleDimensions(false)
|
.withFlexibleDimensions(false)
|
||||||
.withPush(true);
|
.withPush(true);
|
||||||
|
private tooltipId = `bit-tooltip-${TooltipDirective.nextId++}`;
|
||||||
|
private currentDescribedByIds =
|
||||||
|
this.elementRef.nativeElement.getAttribute("aria-describedby") || null;
|
||||||
|
|
||||||
private tooltipPortal = new ComponentPortal(
|
private tooltipPortal = new ComponentPortal(
|
||||||
TooltipComponent,
|
TooltipComponent,
|
||||||
@@ -59,23 +69,50 @@ export class TooltipDirective implements OnInit {
|
|||||||
{
|
{
|
||||||
provide: TOOLTIP_DATA,
|
provide: TOOLTIP_DATA,
|
||||||
useValue: {
|
useValue: {
|
||||||
content: this.bitTooltip,
|
content: this.tooltipContent,
|
||||||
isVisible: this.isVisible,
|
isVisible: this.isVisible,
|
||||||
tooltipPosition: this.tooltipPosition,
|
tooltipPosition: this.tooltipPosition,
|
||||||
|
id: signal(this.tooltipId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private destroyTooltip = () => {
|
||||||
|
this.overlayRef?.dispose();
|
||||||
|
this.overlayRef = undefined;
|
||||||
|
this.isVisible.set(false);
|
||||||
|
};
|
||||||
|
|
||||||
private showTooltip = () => {
|
private showTooltip = () => {
|
||||||
|
if (!this.overlayRef) {
|
||||||
|
this.overlayRef = this.overlay.create({
|
||||||
|
...this.defaultPopoverConfig,
|
||||||
|
positionStrategy: this.positionStrategy,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.overlayRef.attach(this.tooltipPortal);
|
||||||
|
}
|
||||||
this.isVisible.set(true);
|
this.isVisible.set(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
private hideTooltip = () => {
|
private hideTooltip = () => {
|
||||||
this.isVisible.set(false);
|
this.destroyTooltip();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly resolvedDescribedByIds = computed(() => {
|
||||||
|
if (this.addTooltipToDescribedby()) {
|
||||||
|
if (this.currentDescribedByIds) {
|
||||||
|
return `${this.currentDescribedByIds || ""} ${this.tooltipId}`;
|
||||||
|
} else {
|
||||||
|
return this.tooltipId;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.currentDescribedByIds;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
private computePositions(tooltipPosition: TooltipPositionIdentifier) {
|
private computePositions(tooltipPosition: TooltipPositionIdentifier) {
|
||||||
const chosenPosition = tooltipPositions.find((position) => position.id === tooltipPosition);
|
const chosenPosition = tooltipPositions.find((position) => position.id === tooltipPosition);
|
||||||
|
|
||||||
@@ -91,20 +128,5 @@ export class TooltipDirective implements OnInit {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.positionStrategy.withPositions(this.computePositions(this.tooltipPosition()));
|
this.positionStrategy.withPositions(this.computePositions(this.tooltipPosition()));
|
||||||
|
|
||||||
this.overlayRef = this.overlay.create({
|
|
||||||
...this.defaultPopoverConfig,
|
|
||||||
positionStrategy: this.positionStrategy,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.overlayRef.attach(this.tooltipPortal);
|
|
||||||
|
|
||||||
effect(
|
|
||||||
() => {
|
|
||||||
this.positionStrategy.withPositions(this.computePositions(this.tooltipPosition()));
|
|
||||||
this.overlayRef?.updatePosition();
|
|
||||||
},
|
|
||||||
{ injector: this.injector },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,20 @@ import { TooltipDirective } from "@bitwarden/components";
|
|||||||
<Title />
|
<Title />
|
||||||
<Description />
|
<Description />
|
||||||
|
|
||||||
NOTE: The `TooltipComponent` can't be used on its own. It must be applied via the `TooltipDirective`
|
### Tooltip usage
|
||||||
|
|
||||||
|
The `TooltipComponent` can't be used on its own. It must be applied via the `TooltipDirective`.
|
||||||
|
|
||||||
|
The `IconButtonComponent` will automatically apply a tooltip based on the component's `label` input.
|
||||||
|
|
||||||
|
#### Adding the tooltip to the host element's `aria-describedby` list
|
||||||
|
|
||||||
|
The `addTooltipToDescribedby="true"` model input can be used to add the tooltip id to the list of
|
||||||
|
the host element's `aria-describedby` element IDs.
|
||||||
|
|
||||||
|
NOTE: This behavior is not always necessary and could be redundant if the host element's aria
|
||||||
|
attributes already convey the same message as the tooltip. Use only when the tooltip is extra,
|
||||||
|
non-essential contextual information.
|
||||||
|
|
||||||
<Primary />
|
<Primary />
|
||||||
<Controls />
|
<Controls />
|
||||||
@@ -29,3 +42,7 @@ NOTE: The `TooltipComponent` can't be used on its own. It must be applied via th
|
|||||||
### On disabled element
|
### On disabled element
|
||||||
|
|
||||||
<Canvas of={stories.OnDisabledButton} />
|
<Canvas of={stories.OnDisabledButton} />
|
||||||
|
|
||||||
|
### On a Button
|
||||||
|
|
||||||
|
<Canvas of={stories.OnNonIconButton} />
|
||||||
|
|||||||
@@ -59,7 +59,14 @@ describe("TooltipDirective (visibility only)", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const overlayRefStub: OverlayRefStub = {
|
const overlayRefStub: OverlayRefStub = {
|
||||||
attach: jest.fn(() => ({})),
|
attach: jest.fn(() => ({
|
||||||
|
changeDetectorRef: { detectChanges: jest.fn() },
|
||||||
|
location: {
|
||||||
|
nativeElement: {
|
||||||
|
querySelector: jest.fn().mockReturnValue({ id: "tip-123" }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
updatePosition: jest.fn(),
|
updatePosition: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ type Story = StoryObj<TooltipDirective>;
|
|||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
bitTooltip: "This is a tooltip",
|
|
||||||
tooltipPosition: "above-center",
|
tooltipPosition: "above-center",
|
||||||
},
|
},
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
@@ -81,6 +80,7 @@ export const Default: Story = {
|
|||||||
<div class="tw-p-4">
|
<div class="tw-p-4">
|
||||||
<button
|
<button
|
||||||
bitIconButton="bwi-ellipsis-v"
|
bitIconButton="bwi-ellipsis-v"
|
||||||
|
label="Your tooltip content here"
|
||||||
${formatArgsForCodeSnippet<TooltipDirective>(args)}
|
${formatArgsForCodeSnippet<TooltipDirective>(args)}
|
||||||
>
|
>
|
||||||
Button label here
|
Button label here
|
||||||
@@ -98,26 +98,29 @@ export const Default: Story = {
|
|||||||
|
|
||||||
export const AllPositions: Story = {
|
export const AllPositions: Story = {
|
||||||
render: () => ({
|
render: () => ({
|
||||||
|
parameters: {
|
||||||
|
chromatic: { disableSnapshot: true },
|
||||||
|
},
|
||||||
template: `
|
template: `
|
||||||
<div class="tw-p-16 tw-grid tw-grid-cols-2 tw-gap-8 tw-place-items-center">
|
<div class="tw-p-16 tw-grid tw-grid-cols-2 tw-gap-8 tw-place-items-center">
|
||||||
<button
|
<button
|
||||||
bitIconButton="bwi-angle-up"
|
bitIconButton="bwi-angle-up"
|
||||||
bitTooltip="Top tooltip"
|
label="Top tooltip"
|
||||||
tooltipPosition="above-center"
|
tooltipPosition="above-center"
|
||||||
></button>
|
></button>
|
||||||
<button
|
<button
|
||||||
bitIconButton="bwi-angle-right"
|
bitIconButton="bwi-angle-right"
|
||||||
bitTooltip="Right tooltip"
|
label="Right tooltip"
|
||||||
tooltipPosition="right-center"
|
tooltipPosition="right-center"
|
||||||
></button>
|
></button>
|
||||||
<button
|
<button
|
||||||
bitIconButton="bwi-angle-left"
|
bitIconButton="bwi-angle-left"
|
||||||
bitTooltip="Left tooltip"
|
label="Left tooltip"
|
||||||
tooltipPosition="left-center"
|
tooltipPosition="left-center"
|
||||||
></button>
|
></button>
|
||||||
<button
|
<button
|
||||||
bitIconButton="bwi-angle-down"
|
bitIconButton="bwi-angle-down"
|
||||||
bitTooltip="Bottom tooltip"
|
label="Bottom tooltip"
|
||||||
tooltipPosition="below-center"
|
tooltipPosition="below-center"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,11 +130,14 @@ export const AllPositions: Story = {
|
|||||||
|
|
||||||
export const LongContent: Story = {
|
export const LongContent: Story = {
|
||||||
render: () => ({
|
render: () => ({
|
||||||
|
parameters: {
|
||||||
|
chromatic: { disableSnapshot: true },
|
||||||
|
},
|
||||||
template: `
|
template: `
|
||||||
<div class="tw-p-16 tw-flex tw-items-center tw-justify-center">
|
<div class="tw-p-16 tw-flex tw-items-center tw-justify-center">
|
||||||
<button
|
<button
|
||||||
bitIconButton="bwi-ellipsis-v"
|
bitIconButton="bwi-ellipsis-v"
|
||||||
bitTooltip="This is a very long tooltip that will wrap to multiple lines to demonstrate how the tooltip handles long content. This is not recommended for usability."
|
label="This is a very long tooltip that will wrap to multiple lines to demonstrate how the tooltip handles long content. This is not recommended for usability."
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
@@ -140,14 +146,34 @@ export const LongContent: Story = {
|
|||||||
|
|
||||||
export const OnDisabledButton: Story = {
|
export const OnDisabledButton: Story = {
|
||||||
render: () => ({
|
render: () => ({
|
||||||
|
parameters: {
|
||||||
|
chromatic: { disableSnapshot: true },
|
||||||
|
},
|
||||||
template: `
|
template: `
|
||||||
<div class="tw-p-16 tw-flex tw-items-center tw-justify-center">
|
<div class="tw-p-16 tw-flex tw-items-center tw-justify-center">
|
||||||
<button
|
<button
|
||||||
bitIconButton="bwi-ellipsis-v"
|
bitIconButton="bwi-ellipsis-v"
|
||||||
bitTooltip="Tooltip on disabled button"
|
label="Tooltip on disabled button"
|
||||||
[disabled]="true"
|
[disabled]="true"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const OnNonIconButton: Story = {
|
||||||
|
render: () => ({
|
||||||
|
parameters: {
|
||||||
|
chromatic: { disableSnapshot: true },
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div class="tw-p-16 tw-flex tw-items-center tw-justify-center">
|
||||||
|
<button
|
||||||
|
bitButton
|
||||||
|
addTooltipToDescribedby="true"
|
||||||
|
bitTooltip="Some additional tooltip text to describe the button"
|
||||||
|
>Button label</button>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ describe("DeleteAttachmentComponent", () => {
|
|||||||
it("renders delete button", () => {
|
it("renders delete button", () => {
|
||||||
const deleteButton = fixture.debugElement.query(By.css("button"));
|
const deleteButton = fixture.debugElement.query(By.css("button"));
|
||||||
|
|
||||||
expect(deleteButton.attributes["title"]).toBe("deleteAttachmentName");
|
expect(deleteButton.attributes["aria-label"]).toBe("deleteAttachmentName");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not delete when the user cancels the dialog", async () => {
|
it("does not delete when the user cancels the dialog", async () => {
|
||||||
|
|||||||
@@ -149,13 +149,17 @@ describe("UriOptionComponent", () => {
|
|||||||
expect(getMatchDetectionSelect()).not.toBeNull();
|
expect(getMatchDetectionSelect()).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update the match detection button title when the toggle is clicked", () => {
|
it("should update the match detection button aria-label when the toggle is clicked", () => {
|
||||||
component.writeValue({ uri: "https://example.com", matchDetection: UriMatchStrategy.Exact });
|
component.writeValue({ uri: "https://example.com", matchDetection: UriMatchStrategy.Exact });
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(getToggleMatchDetectionBtn().title).toBe("showMatchDetection https://example.com");
|
expect(getToggleMatchDetectionBtn().getAttribute("aria-label")).toBe(
|
||||||
|
"showMatchDetection https://example.com",
|
||||||
|
);
|
||||||
getToggleMatchDetectionBtn().click();
|
getToggleMatchDetectionBtn().click();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(getToggleMatchDetectionBtn().title).toBe("hideMatchDetection https://example.com");
|
expect(getToggleMatchDetectionBtn().getAttribute("aria-label")).toBe(
|
||||||
|
"hideMatchDetection https://example.com",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ describe("DownloadAttachmentComponent", () => {
|
|||||||
it("renders delete button", () => {
|
it("renders delete button", () => {
|
||||||
const deleteButton = fixture.debugElement.query(By.css("button"));
|
const deleteButton = fixture.debugElement.query(By.css("button"));
|
||||||
|
|
||||||
expect(deleteButton.attributes["title"]).toBe("downloadAttachmentName");
|
expect(deleteButton.attributes["aria-label"]).toBe("downloadAttachmentName");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("download attachment", () => {
|
describe("download attachment", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user