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
47 changes: 47 additions & 0 deletions goldens/aria/simple-combobox/testing/index.api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## API Report File for "@angular/aria_simple-combobox_testing"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

import { BaseHarnessFilters } from '@angular/cdk/testing';
import { ComponentHarness } from '@angular/cdk/testing';
import { ComponentHarnessConstructor } from '@angular/cdk/testing';
import { ContentContainerComponentHarness } from '@angular/cdk/testing';
import { HarnessLoader } from '@angular/cdk/testing';
import { HarnessPredicate } from '@angular/cdk/testing';

// @public
export class ComboboxHarness extends ContentContainerComponentHarness {
blur(): Promise<void>;
close(): Promise<void>;
focus(): Promise<void>;
getPlaceholder(): Promise<string | null>;
getPopupLoader(): Promise<HarnessLoader>;
getPopupWidget<T extends ComponentHarness>(type: ComponentHarnessConstructor<T> & {
with: (options?: {
selector?: string;
}) => HarnessPredicate<T>;
}): Promise<T>;
protected getRootHarnessLoader(): Promise<HarnessLoader>;
getValue(): Promise<string>;
// (undocumented)
static hostSelector: string;
isDisabled(): Promise<boolean>;
isFocused(): Promise<boolean>;
isOpen(): Promise<boolean>;
open(): Promise<void>;
setValue(value: string): Promise<void>;
static with(options?: ComboboxHarnessFilters): HarnessPredicate<ComboboxHarness>;
}

// @public
export interface ComboboxHarnessFilters extends BaseHarnessFilters {
disabled?: boolean;
placeholder?: string | RegExp;
value?: string | RegExp;
}

// (No @packageDocumentation comment for this package)

```
1 change: 1 addition & 0 deletions src/aria/config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ARIA_ENTRYPOINTS = [
"menu",
"menu/testing",
"simple-combobox",
"simple-combobox/testing",
"tabs",
"tabs/testing",
"toolbar",
Expand Down
43 changes: 43 additions & 0 deletions src/aria/simple-combobox/testing/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project")

package(default_visibility = ["//visibility:public"])

ts_project(
name = "testing",
srcs = glob(
["**/*.ts"],
exclude = ["**/*.spec.ts"],
),
deps = [
"//:node_modules/@angular/core",
"//src/cdk/testing",
],
)

filegroup(
name = "source-files",
srcs = glob(["**/*.ts"]),
)

ng_project(
name = "unit_tests_lib",
testonly = True,
srcs = glob(["**/*.spec.ts"]),
deps = [
":testing",
"//:node_modules/@angular/core",
"//src/aria/listbox",
"//src/aria/listbox/testing",
"//src/aria/simple-combobox",
"//src/cdk/overlay",
"//src/cdk/testing",
"//src/cdk/testing/testbed",
],
)

ng_web_test_suite(
name = "unit_tests",
deps = [
":unit_tests_lib",
],
)
19 changes: 19 additions & 0 deletions src/aria/simple-combobox/testing/combobox-harness-filters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import {BaseHarnessFilters} from '@angular/cdk/testing';

/** A set of criteria that can be used to filter a list of `ComboboxHarness` instances. */
export interface ComboboxHarnessFilters extends BaseHarnessFilters {
/** Only find instances whose placeholder matches the given value. */
placeholder?: string | RegExp;
/** Only find instances whose value matches the given value. */
value?: string | RegExp;
/** Only find instances with the given disabled state. */
disabled?: boolean;
}
200 changes: 200 additions & 0 deletions src/aria/simple-combobox/testing/combobox-harness.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import {Component, signal} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {Combobox, ComboboxPopup, ComboboxWidget} from '../index';
import {Listbox, Option} from '../../listbox';
import {ListboxHarness, ListboxOptionHarness} from '../../listbox/testing/listbox-harness';
import {ComboboxHarness} from './combobox-harness';
import {OverlayModule} from '@angular/cdk/overlay';

describe('ComboboxHarness', () => {
let fixture: ComponentFixture<any>;
let loader: HarnessLoader;

function setupTest(component: any) {
fixture = TestBed.createComponent(component);
fixture.detectChanges();
loader = TestbedHarnessEnvironment.loader(fixture);
}

describe('Basic usage', () => {
beforeEach(() => setupTest(ComboboxTestApp));

it('should load combobox harness', async () => {
await expectAsync(loader.getHarness(ComboboxHarness)).toBeResolved();
});

it('should get and set values', async () => {
const combobox = await loader.getHarness(ComboboxHarness);
await combobox.setValue('California');
fixture.detectChanges();

expect(await combobox.getValue()).toBe('California');
});

it('should correctly report disabled state', async () => {
const combobox = await loader.getHarness(ComboboxHarness);
expect(await combobox.isDisabled()).toBeFalse();

fixture.componentInstance.disabled.set(true);
fixture.detectChanges();

expect(await combobox.isDisabled()).toBeTrue();
});

it('should open and close the popup', async () => {
const combobox = await loader.getHarness(ComboboxHarness);
expect(await combobox.isOpen()).toBeFalse();

await combobox.open();
fixture.detectChanges();
expect(await combobox.isOpen()).toBeTrue();

await combobox.close();
fixture.detectChanges();
expect(await combobox.isOpen()).toBeFalse();
});

it('should allow loading nested harnesses within the popup content via unified container API', async () => {
const combobox = await loader.getHarness(ComboboxHarness);
await combobox.open();
fixture.detectChanges();

// We access the main widget harness using getPopupWidget.
const listbox = await combobox.getPopupWidget(ListboxHarness);
const options = await listbox.getOptions();
expect(options.length).toBe(3);
});

it('should fail to resolve nested items when closed', async () => {
const combobox = await loader.getHarness(ComboboxHarness);
// Popup isn't open yet, so getPopupWidget should fail.
await expectAsync(combobox.getPopupWidget(ListboxHarness)).toBeRejectedWithError(
/Cannot retrieve popup content because the combobox is closed/,
);
});

it('should support getting explicit popup loader for descendant matching', async () => {
const combobox = await loader.getHarness(ComboboxHarness);
await combobox.open();
fixture.detectChanges();

const popupLoader = await combobox.getPopupLoader();
// We are testing that the loader works for finding actual children (Options).
const option = await popupLoader.getHarness(ListboxOptionHarness);
expect(option).toBeDefined();
});

it('should support focusing and blurring', async () => {
const combobox = await loader.getHarness(ComboboxHarness);
await combobox.focus();
expect(await combobox.isFocused()).toBeTrue();

await combobox.blur();
expect(await combobox.isFocused()).toBeFalse();
});
});

describe('Overlay and Popover integrations', () => {
it('should find and resolve harnesses nested inside standard CdkOverlay', async () => {
setupTest(ComboboxOverlayTestApp);
const combobox = await loader.getHarness(ComboboxHarness);

await combobox.open();
fixture.detectChanges();

// Should find listbox inside the dynamically attached cdk overlay root container
const listbox = await combobox.getPopupWidget(ListboxHarness);
expect(listbox).toBeDefined();
expect((await listbox.getOptions()).length).toBe(2);
});

it('should resolve nested harnesses when using Native Popover API', async () => {
setupTest(ComboboxNativePopoverTestApp);
const combobox = await loader.getHarness(ComboboxHarness);

await combobox.open();
fixture.detectChanges();

const listbox = await combobox.getPopupWidget(ListboxHarness);
expect(listbox).toBeDefined();
expect((await listbox.getOptions()).length).toBe(2);
});
});
});

@Component({
template: `
<div>
<input
ngCombobox
#combobox="ngCombobox"
placeholder="Search states"
[disabled]="disabled()"
/>

<ng-template ngComboboxPopup [combobox]="combobox">
<div ngComboboxWidget #listbox="ngListbox" ngListbox id="listbox-widget" focusMode="activedescendant" [activeDescendant]="listbox.activeDescendant()">
<div ngOption value="CA" label="California">California</div>
<div ngOption value="WA" label="Washington">Washington</div>
<div ngOption value="OR" label="Oregon">Oregon</div>
</div>
</ng-template>
</div>
`,
imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option],
})
class ComboboxTestApp {
disabled = signal(false);
}

@Component({
template: `
<div #origin>
<input ngCombobox #combobox="ngCombobox" [(expanded)]="popupExpanded" />
</div>

<ng-template cdkConnectedOverlay [cdkConnectedOverlayOrigin]="origin" [cdkConnectedOverlayOpen]="popupExpanded()">
<ng-template ngComboboxPopup [combobox]="combobox">
<div ngComboboxWidget ngListbox id="overlay-listbox">
<div ngOption value="A">A</div>
<div ngOption value="B">B</div>
</div>
</ng-template>
</ng-template>
`,
imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
class ComboboxOverlayTestApp {
popupExpanded = signal(false);
}

@Component({
template: `
<div #origin>
<input ngCombobox #combobox="ngCombobox" [(expanded)]="popupExpanded" />
</div>

<ng-template [cdkConnectedOverlay]="{origin, usePopover: 'inline'}" [cdkConnectedOverlayOpen]="popupExpanded()">
<ng-template ngComboboxPopup [combobox]="combobox">
<div ngComboboxWidget ngListbox id="popover-listbox">
<div ngOption value="A">A</div>
<div ngOption value="B">B</div>
</div>
</ng-template>
</ng-template>
`,
imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
class ComboboxNativePopoverTestApp {
popupExpanded = signal(false);
}
Loading
Loading