super-productivity/src/app/ui/split-button/split-button.component.spec.ts
Johannes Millan 245330bd68
fix(tasks): shorten reminder snooze button label to fit small screens (#8743)
The reminder dialog footer could not fit all actions in one row on small
screens. Show only the snooze duration (e.g. "10m") on the split-button's
main action instead of "Snooze 10m" — the snooze icon already conveys the
action — freeing horizontal space so the row fits.

Add a `mainLabel` input to the shared split-button so the abbreviated main
action keeps a full tooltip and aria-label for discoverability/accessibility.


Claude-Session: https://claude.ai/code/session_015797BREShEaTkBsA3tRBuX

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-03 23:36:05 +02:00

110 lines
3.6 KiB
TypeScript

import { Component, signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { MatMenuModule } from '@angular/material/menu';
import { SplitButtonComponent } from './split-button.component';
// Host wraps the component so the required `menu` input can be bound to a real
// <mat-menu> and the projected default-action content is provided. Signal-backed
// state so writes mark the host dirty under zoneless change detection.
@Component({
standalone: true,
imports: [SplitButtonComponent, MatMenuModule],
template: `
<split-button
[menu]="menu"
[disabled]="disabled()"
[triggerLabel]="triggerLabel()"
[mainLabel]="mainLabel()"
(mainClick)="onMainClick()"
>
<span class="projected-content">Snooze 10m</span>
</split-button>
<mat-menu #menu="matMenu">
<button mat-menu-item>Option</button>
</mat-menu>
`,
})
class HostComponent {
readonly disabled = signal(false);
readonly triggerLabel = signal('');
readonly mainLabel = signal('');
mainClickCount = 0;
onMainClick(): void {
this.mainClickCount++;
}
}
describe('SplitButtonComponent', () => {
let fixture: ComponentFixture<HostComponent>;
let host: HostComponent;
const mainBtn = (): HTMLButtonElement =>
fixture.nativeElement.querySelector('.split-btn-main');
const triggerBtn = (): HTMLButtonElement =>
fixture.nativeElement.querySelector('.split-btn-trigger');
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HostComponent, TranslateModule.forRoot()],
}).compileComponents();
fixture = TestBed.createComponent(HostComponent);
host = fixture.componentInstance;
fixture.detectChanges();
});
it('renders both halves and projects the default-action content', () => {
expect(mainBtn()).toBeTruthy();
expect(triggerBtn()).toBeTruthy();
expect(
fixture.nativeElement.querySelector('.projected-content')?.textContent,
).toContain('Snooze 10m');
});
it('emits mainClick when the default-action half is clicked', () => {
mainBtn().click();
expect(host.mainClickCount).toBe(1);
});
it('wires the trigger half to open the passed-in menu', () => {
const trigger = triggerBtn();
expect(trigger.getAttribute('aria-haspopup')).toBe('menu');
trigger.click();
fixture.detectChanges();
expect(trigger.getAttribute('aria-expanded')).toBe('true');
});
it('disables both halves when [disabled] is true', () => {
host.disabled.set(true);
fixture.detectChanges();
expect(mainBtn().disabled).toBe(true);
expect(triggerBtn().disabled).toBe(true);
});
it('exposes triggerLabel as the trigger aria-label, and omits it when empty', () => {
// No translations loaded, so the translate pipe returns the key verbatim.
host.triggerLabel.set('F.TASK.D_REMINDER_VIEW.MORE_ACTIONS');
fixture.detectChanges();
expect(triggerBtn().getAttribute('aria-label')).toBe(
'F.TASK.D_REMINDER_VIEW.MORE_ACTIONS',
);
host.triggerLabel.set('');
fixture.detectChanges();
expect(triggerBtn().getAttribute('aria-label')).toBeNull();
});
it('exposes the resolved mainLabel as the main aria-label, and omits it when empty', () => {
// mainLabel is a resolved string (caller translates), so it is bound as-is.
host.mainLabel.set('Snooze 10m');
fixture.detectChanges();
expect(mainBtn().getAttribute('aria-label')).toBe('Snooze 10m');
host.mainLabel.set('');
fixture.detectChanges();
expect(mainBtn().getAttribute('aria-label')).toBeNull();
});
});