Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions app/src/__tests__/analog-inspector-pages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,20 @@ describe('Analog project load', () => {
expect(fixture.componentInstance.project()).not.toBeNull();
});
});

describe('Analog route filter', () => {
it('clears on Escape', async () => {
const fixture = await render(client());
const box = (fixture.nativeElement as HTMLElement).querySelector<HTMLInputElement>(
'#route-filter',
)!;
box.value = 'products';
box.dispatchEvent(new Event('input'));
await settle(fixture);
expect(fixture.componentInstance.filter()).toBe('products');
box.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
await settle(fixture);
expect(fixture.componentInstance.filter()).toBe('');
expect(box.value).toBe('');
});
});
66 changes: 66 additions & 0 deletions app/src/__tests__/component-tree-expand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,69 @@ describe('ComponentTree expand and collapse all', () => {
expect(visible(fixture)).toEqual(['app', 'header', 'nav', 'main', 'list', 'item', 'footer']);
});
});

describe('ComponentTree Clear filter focus', () => {
beforeEach(() => {
vi.stubGlobal('CSS', { escape: (value: string) => value });
Element.prototype.scrollIntoView ??= () => {};
});

afterEach(() => {
document.body.innerHTML = '';
vi.unstubAllGlobals();
});

const search = (fixture: ComponentFixture<ComponentTree>) =>
host(fixture).querySelector<HTMLInputElement>('input[type="search"]');

it('keeps focus on the search box after Clear filter in the live tree', async () => {
const fixture = await setup();
fixture.componentInstance.setFilter('zzz');
await settle(fixture);
const clear = button(fixture, 'Clear filter');
clear.focus();
clear.click();
await settle(fixture);
expect(visible(fixture)).toContain('app');
expect(document.activeElement).toBe(search(fixture));
});

it('keeps focus on the search box after Clear filter in the source scan', async () => {
const rpc = {
call: (name: string) =>
Promise.resolve(
name === 'get-components'
? [
{
selector: 'app-cart',
className: 'Cart',
kind: 'component',
file: 'src/cart.ts',
line: 1,
inputs: [],
outputs: [],
isStandalone: true,
},
]
: [],
),
callEvent: () => Promise.resolve(),
sharedState: () => Promise.resolve({ value: () => null, on: () => () => {} }),
};
const fixture = TestBed.createComponent(ComponentTree);
fixture.componentRef.setInput('rpc', {
connectionMeta: {},
scope: () => ({ rpc }),
} as unknown as DevframeRpcClient);
document.body.appendChild(fixture.nativeElement);
await settle(fixture);
fixture.componentInstance.filter.set('zzz');
await settle(fixture);
const clear = button(fixture, 'Clear filter');
clear.focus();
clear.click();
await settle(fixture);
expect(host(fixture).textContent).toContain('app-cart');
expect(document.activeElement).toBe(search(fixture));
});
});
61 changes: 61 additions & 0 deletions app/src/__tests__/di-inspector-pages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,67 @@ describe('injector tree page selection', () => {
});
});

describe('injector search box focus', () => {
afterEach(() => {
document.body.innerHTML = '';
});

function clickNamed(host: HTMLElement, name: string) {
const button = Array.from(host.querySelectorAll('button')).find(
(b) => b.textContent?.trim() === name,
);
if (!button) throw new Error(`No button named ${name}`);
button.focus();
button.click();
}

it('keeps focus on the search box after Clear search in the live tree', async () => {
const fixture = await render({
roots: [
{
injector: { id: 'a', type: 'element', name: 'A', providerCount: 0, component: 'A' },
providers: [],
children: [],
},
],
environment: [],
});
const host = fixture.nativeElement as HTMLElement;
document.body.append(host);
fixture.componentInstance.query.set('zzz');
await fixture.whenStable();
clickNamed(host, 'Clear search');
await fixture.whenStable();
expect(fixture.componentInstance.query()).toBe('');
expect(document.activeElement).toBe(
host.querySelector('input[aria-label="Find a token, component or injector"]'),
);
});

it('keeps focus on the search box after Clear filter in the source scan', async () => {
const fixture = await render({});
const host = fixture.nativeElement as HTMLElement;
document.body.append(host);
fixture.componentInstance.sourceProviders.set([
{
token: 'CartService',
source: 'CartService',
file: 'src/cart.ts',
line: 1,
type: 'injectable',
},
]);
fixture.componentInstance.query.set('zzz');
await fixture.whenStable();
clickNamed(host, 'Clear filter');
await fixture.whenStable();
expect(host.textContent).toContain('CartService');
expect(document.activeElement).toBe(
host.querySelector('input[aria-label="Filter by token or file"]'),
);
});
});

describe('injector tree page picker', () => {
afterEach(() => {
scopeToPage(null);
Expand Down
17 changes: 17 additions & 0 deletions app/src/__tests__/forms-panels.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,23 @@ describe('FormsInspector fields', () => {
expect(host.querySelector('app-forms-field-detail')).toBeNull();
expect(document.activeElement).toBe(row);
});

it('clears the field path filter on Escape', async () => {
const fixture = TestBed.createComponent(FormsInspector);
fixture.componentRef.setInput('rpc', fakeClient(fail, [form]));
await settle(fixture);
const box = (fixture.nativeElement as HTMLElement).querySelector<HTMLInputElement>(
'input[aria-label="Filter fields by path"]',
)!;
box.value = 'email';
box.dispatchEvent(new Event('input'));
await settle(fixture);
expect(fixture.componentInstance.filter()).toBe('email');
box.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
await settle(fixture);
expect(fixture.componentInstance.filter()).toBe('');
expect(box.value).toBe('');
});
});

describe('FormsInspector status pills', () => {
Expand Down
49 changes: 49 additions & 0 deletions app/src/__tests__/pipes-record-page.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,52 @@ describe('pipe recording request', () => {
expect(requests).toEqual([{ on: false }]);
});
});

describe('pipes filter box', () => {
afterEach(() => {
document.body.innerHTML = '';
});

function withPipes() {
const rpc = {
call: (name: string) =>
Promise.resolve(
name === 'get-pipes'
? [
{
name: 'price',
className: 'PricePipe',
file: 'src/price.pipe.ts',
line: 4,
isStandalone: true,
isPure: true,
},
]
: [],
),
callEvent: () => Promise.resolve(),
sharedState: () =>
Promise.resolve({
value: () => ({ pipes: [], async: [], reportedAt: 0, instrumented: [] }),
on: () => () => {},
}),
};
return { connectionMeta: {}, scope: () => ({ rpc }) } as unknown as DevframeRpcClient;
}

it('keeps focus on the search box after Clear filters', async () => {
const fixture = await render(withPipes());
const host = fixture.nativeElement as HTMLElement;
document.body.append(host);
fixture.componentInstance.filter.set('zzz');
await fixture.whenStable();
const clear = Array.from(host.querySelectorAll('button')).find(
(b) => b.textContent?.trim() === 'Clear filters',
)!;
clear.focus();
clear.click();
await fixture.whenStable();
expect(host.textContent).not.toContain('No pipes match');
expect(document.activeElement).toBe(host.querySelector('input[type="search"]'));
});
});
56 changes: 56 additions & 0 deletions app/src/__tests__/router-panels.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { TestBed, type ComponentFixture } from '@angular/core/testing';
import type { DevframeRpcClient } from 'devframe/client';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { RouteCurrent } from '../pages/route-current';
import { RouteInspector } from '../pages/route-inspector';
import { RouteLint } from '../pages/route-lint';
import { RouteTimeline } from '../pages/route-timeline';
import { RouteTree } from '../pages/route-tree';
Expand Down Expand Up @@ -706,3 +707,58 @@ describe('RouteCurrent', () => {
expect(showButtons(fixture)).toHaveLength(0);
});
});

describe('Router filter boxes clear on Escape', () => {
async function typeThenEscape(
fixture: ComponentFixture<{ filter: () => string }>,
box: HTMLInputElement,
) {
expect(box.type).toBe('search');
box.value = 'users';
box.dispatchEvent(new Event('input'));
await settle(fixture);
expect(fixture.componentInstance.filter()).toBe('users');
box.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
await settle(fixture);
expect(fixture.componentInstance.filter()).toBe('');
expect(box.value).toBe('');
}

it('clears the Routes filter', async () => {
const fixture = mount(
RouteTree,
page({ config: [{ id: 'r1', path: 'users', fullPath: 'users', kind: 'component' }] }),
offline,
);
await settle(fixture);
await typeThenEscape(fixture, el(fixture).querySelector<HTMLInputElement>('input.filter')!);
});

it('clears the navigation URL filter', async () => {
const fixture = mount(RouteTimeline, page({ navigations: [nav(1, '/users')] }), offline);
await settle(fixture);
await typeThenEscape(
fixture,
el(fixture).querySelector<HTMLInputElement>('input[aria-label="Filter navigations by URL"]')!,
);
});

it('clears the source routes filter', async () => {
const rpc = {
call: () => Promise.resolve([]),
callEvent: () => Promise.resolve(),
sharedState: () => Promise.resolve({ value: () => null, on: () => () => {} }),
};
const fixture = TestBed.createComponent(RouteInspector);
fixture.componentRef.setInput('rpc', {
connectionMeta: {},
scope: () => ({ rpc }),
} as unknown as DevframeRpcClient);
document.body.append(el(fixture));
await settle(fixture);
await typeThenEscape(
fixture,
el(fixture).querySelector<HTMLInputElement>('input[aria-label="Filter source routes"]')!,
);
});
});
64 changes: 64 additions & 0 deletions app/src/__tests__/signal-inspector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,68 @@ describe('SignalInspector', () => {
await fixture.whenStable();
expect(host.textContent).toContain('user');
});

describe('filter box focus and Escape', () => {
afterEach(() => {
document.body.innerHTML = '';
});

function filterBox(host: HTMLElement): HTMLInputElement {
const box = host.querySelector<HTMLInputElement>(
'input[aria-label="Filter signals by name or kind"]',
);
if (!box) throw new Error('No filter box');
return box;
}

function clearButton(host: HTMLElement): HTMLButtonElement {
const clear = Array.from(host.querySelectorAll('button')).find(
(b) => b.textContent?.trim() === 'Clear filters',
);
if (!clear) throw new Error('No Clear filters button');
return clear;
}

it('keeps focus on the filter box after Clear filters in the live graph', async () => {
const fixture = await render(graph);
const host = fixture.nativeElement as HTMLElement;
document.body.append(host);
fixture.componentInstance.filter.set('zzz');
await fixture.whenStable();
clearButton(host).focus();
clearButton(host).click();
await fixture.whenStable();
expect(host.textContent).not.toContain('No signals match.');
expect(document.activeElement).toBe(filterBox(host));
});

it('keeps focus on the filter box after Clear filters in the source scan', async () => {
const fixture = await render(null as unknown as Record<string, unknown>);
const host = fixture.nativeElement as HTMLElement;
document.body.append(host);
fixture.componentInstance.sourceSignals.set([
{ name: 'count', kind: 'signal', file: 'src/app.ts', line: 3 },
]);
fixture.componentInstance.filter.set('zzz');
await fixture.whenStable();
clearButton(host).focus();
clearButton(host).click();
await fixture.whenStable();
expect(host.textContent).toContain('count');
expect(document.activeElement).toBe(filterBox(host));
});

it('clears the filter box on Escape', async () => {
const fixture = await render(graph);
const host = fixture.nativeElement as HTMLElement;
const box = filterBox(host);
expect(box.type).toBe('search');
fixture.componentInstance.filter.set('query');
await fixture.whenStable();
box.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
await fixture.whenStable();
expect(fixture.componentInstance.filter()).toBe('');
expect(box.value).toBe('');
});
});
});
Loading
Loading