Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(material-experimental/mdc-snack-bar): add harness support for getting label and actions #23585

Merged
merged 2 commits into from
Sep 16, 2021
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
3 changes: 3 additions & 0 deletions src/material-experimental/mdc-snack-bar/testing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ ng_test_library(
srcs = glob(["**/*.spec.ts"]),
deps = [
":testing",
"//src/cdk/testing",
"//src/cdk/testing/testbed",
"//src/material-experimental/mdc-snack-bar",
"//src/material/snack-bar/testing:harness_tests_lib",
"@npm//@angular/platform-browser",
],
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,88 @@
import {MatSnackBarModule, MatSnackBar} from '@angular/material-experimental/mdc-snack-bar';
import {
MatSnackBar,
MatSnackBarConfig,
MatSnackBarModule
} from '@angular/material-experimental/mdc-snack-bar';
import {runHarnessTests} from '@angular/material/snack-bar/testing/shared.spec';
import {MatSnackBarHarness} from './snack-bar-harness';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {Component, TemplateRef, ViewChild} from '@angular/core';

describe('MDC-based MatSnackBarHarness', () => {
runHarnessTests(MatSnackBarModule, MatSnackBar, MatSnackBarHarness as any);
});

describe('MDC-based MatSnackBarHarness (MDC only behavior)', () => {
let fixture: ComponentFixture<SnackbarHarnessTest>;
let loader: HarnessLoader;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MatSnackBarModule, NoopAnimationsModule],
declarations: [SnackbarHarnessTest],
}).compileComponents();

fixture = TestBed.createComponent(SnackbarHarnessTest);
fixture.detectChanges();
loader = TestbedHarnessEnvironment.documentRootLoader(fixture);
});

it('should be able to get message of a snack-bar with custom content', async () => {
fixture.componentInstance.openCustom();
let snackBar = await loader.getHarness(MatSnackBarHarness);
expect(await snackBar.getMessage()).toBe('My custom snack-bar.');

fixture.componentInstance.openCustomWithAction();
snackBar = await loader.getHarness(MatSnackBarHarness);
expect(await snackBar.getMessage()).toBe('My custom snack-bar with action.');
});

it('should fail to get action description of a snack-bar with no action', async () => {
fixture.componentInstance.openCustom();
const snackBar = await loader.getHarness(MatSnackBarHarness);
await expectAsync(snackBar.getActionDescription()).toBeRejectedWithError(/without an action/);
});

it('should be able to get action description of a snack-bar with an action', async () => {
fixture.componentInstance.openCustomWithAction();
const snackBar = await loader.getHarness(MatSnackBarHarness);
expect(await snackBar.getActionDescription()).toBe('Ok');
});

it('should be able to check whether a snack-bar with custom content has an action', async () => {
fixture.componentInstance.openCustom();
let snackBar = await loader.getHarness(MatSnackBarHarness);
expect(await snackBar.hasAction()).toBe(false);

fixture.componentInstance.openCustomWithAction();
snackBar = await loader.getHarness(MatSnackBarHarness);
expect(await snackBar.hasAction()).toBe(true);
});
});

@Component({
template: `
<ng-template #custom>My custom snack-bar.</ng-template>
<ng-template #customWithAction>
<span matSnackBarLabel>My custom snack-bar with action.</span>
<div matSnackBarActions><button matSnackBarAction>Ok</button></div>
</ng-template>
`
})
class SnackbarHarnessTest {
@ViewChild('custom') customTmpl: TemplateRef<any>;
@ViewChild('customWithAction') customWithActionTmpl: TemplateRef<any>;

constructor(public snackBar: MatSnackBar) {}

openCustom(config?: MatSnackBarConfig) {
return this.snackBar.openFromTemplate(this.customTmpl, config);
}

openCustomWithAction(config?: MatSnackBarConfig) {
return this.snackBar.openFromTemplate(this.customWithActionTmpl, config);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ export class MatSnackBarHarness extends BaseMatSnackBarHarness {
// notified when it's done.
/** The selector for the host element of a `MatSnackBar` instance. */
static override hostSelector = '.mat-mdc-snack-bar-container:not([mat-exit])';
protected override _messageSelector = '.mat-mdc-simple-snack-bar .mat-mdc-snack-bar-label';
protected override _simpleSnackBarSelector = '.mat-mdc-simple-snack-bar';
protected override _actionButtonSelector = '.mat-mdc-simple-snack-bar .mat-mdc-snack-bar-action';
protected override _messageSelector = '.mdc-snackbar__label';
protected override _actionButtonSelector = '.mat-mdc-snack-bar-action';

/**
* Gets a `HarnessPredicate` that can be used to search for a `MatSnackBarHarness` that meets
Expand All @@ -35,4 +34,6 @@ export class MatSnackBarHarness extends BaseMatSnackBarHarness {
options: SnackBarHarnessFilters = {}): HarnessPredicate<BaseMatSnackBarHarness> {
return new HarnessPredicate<BaseMatSnackBarHarness>(MatSnackBarHarness, options);
}

protected override async _assertContentAnnotated() {}
}
3 changes: 3 additions & 0 deletions src/material/snack-bar/testing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ ng_test_library(
deps = [
":harness_tests_lib",
":testing",
"//src/cdk/testing",
"//src/cdk/testing/testbed",
"//src/material/snack-bar",
"@npm//@angular/platform-browser",
],
)

Expand Down
20 changes: 1 addition & 19 deletions src/material/snack-bar/testing/shared.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,24 +91,12 @@ export function runHarnessTests(
fixture.componentInstance.openSimple('Subscribed to newsletter.');
let snackBar = await loader.getHarness(snackBarHarness);
expect(await snackBar.getMessage()).toBe('Subscribed to newsletter.');

// For snack-bar's with custom template, the message cannot be
// retrieved. We expect an error to be thrown.
fixture.componentInstance.openCustom();
snackBar = await loader.getHarness(snackBarHarness);
await expectAsync(snackBar.getMessage()).toBeRejectedWithError(/custom content/);
});

it('should be able to get action description of simple snack-bar', async () => {
fixture.componentInstance.openSimple('Hello', 'Unsubscribe');
let snackBar = await loader.getHarness(snackBarHarness);
expect(await snackBar.getActionDescription()).toBe('Unsubscribe');

// For snack-bar's with custom template, the action description
// cannot be retrieved. We expect an error to be thrown.
fixture.componentInstance.openCustom();
snackBar = await loader.getHarness(snackBarHarness);
await expectAsync(snackBar.getActionDescription()).toBeRejectedWithError(/custom content/);
});

it('should be able to check whether simple snack-bar has action', async () => {
Expand All @@ -119,12 +107,6 @@ export function runHarnessTests(
fixture.componentInstance.openSimple('No action');
snackBar = await loader.getHarness(snackBarHarness);
expect(await snackBar.hasAction()).toBe(false);

// For snack-bar's with custom template, the action cannot
// be found. We expect an error to be thrown.
fixture.componentInstance.openCustom();
snackBar = await loader.getHarness(snackBarHarness);
await expectAsync(snackBar.hasAction()).toBeRejectedWithError(/custom content/);
});

it('should be able to dismiss simple snack-bar with action', async () => {
Expand All @@ -143,7 +125,7 @@ export function runHarnessTests(

fixture.componentInstance.openSimple('No action');
snackBar = await loader.getHarness(snackBarHarness);
await expectAsync(snackBar.dismissWithAction()).toBeRejectedWithError(/without action/);
await expectAsync(snackBar.dismissWithAction()).toBeRejectedWithError(/without an action/);
});

@Component({
Expand Down
54 changes: 53 additions & 1 deletion src/material/snack-bar/testing/snack-bar-harness.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,59 @@
import {MatSnackBarModule, MatSnackBar} from '@angular/material/snack-bar';
import {MatSnackBar, MatSnackBarConfig, MatSnackBarModule} from '@angular/material/snack-bar';
import {runHarnessTests} from '@angular/material/snack-bar/testing/shared.spec';
import {MatSnackBarHarness} from './snack-bar-harness';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {Component, TemplateRef, ViewChild} from '@angular/core';

describe('Non-MDC-based MatSnackBarHarness', () => {
runHarnessTests(MatSnackBarModule, MatSnackBar, MatSnackBarHarness);
});

describe('Non-MDC-based MatSnackBarHarness (non-MDC only behavior)', () => {
let fixture: ComponentFixture<SnackbarHarnessTest>;
let loader: HarnessLoader;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MatSnackBarModule, NoopAnimationsModule],
declarations: [SnackbarHarnessTest],
}).compileComponents();

fixture = TestBed.createComponent(SnackbarHarnessTest);
fixture.detectChanges();
loader = TestbedHarnessEnvironment.documentRootLoader(fixture);
});

it('should fail to get message of a snack-bar with custom content', async () => {
fixture.componentInstance.openCustom();
const snackBar = await loader.getHarness(MatSnackBarHarness);
await expectAsync(snackBar.getMessage()).toBeRejectedWithError(/custom content/);
});

it('should fail to get action description of a snack-bar with custom content', async () => {
fixture.componentInstance.openCustom();
const snackBar = await loader.getHarness(MatSnackBarHarness);
await expectAsync(snackBar.getActionDescription()).toBeRejectedWithError(/custom content/);
});

it('should fail to check whether a snack-bar with custom content has an action', async () => {
fixture.componentInstance.openCustom();
const snackBar = await loader.getHarness(MatSnackBarHarness);
await expectAsync(snackBar.hasAction()).toBeRejectedWithError(/custom content/);
});
});

@Component({
template: `<ng-template>My custom snack-bar.</ng-template>`
})
class SnackbarHarnessTest {
@ViewChild(TemplateRef) customTmpl: TemplateRef<any>;

constructor(public snackBar: MatSnackBar) {}

openCustom(config?: MatSnackBarConfig) {
return this.snackBar.openFromTemplate(this.customTmpl, config);
}
}
39 changes: 19 additions & 20 deletions src/material/snack-bar/testing/snack-bar-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ export class MatSnackBarHarness extends ContentContainerComponentHarness<string>
/** The selector for the host element of a `MatSnackBar` instance. */
static hostSelector = '.mat-snack-bar-container';
protected _messageSelector = '.mat-simple-snackbar > span';
protected _simpleSnackBarSelector = '.mat-simple-snackbar';
protected _actionButtonSelector = '.mat-simple-snackbar-action > button';
private _simpleSnackBarLiveRegion = this.locatorFor('[aria-live]');
private _snackBarLiveRegion = this.locatorFor('[aria-live]');

/**
* Gets a `HarnessPredicate` that can be used to search for a `MatSnackBarHarness` that meets
Expand All @@ -46,25 +45,25 @@ export class MatSnackBarHarness extends ContentContainerComponentHarness<string>
* determined based on the ARIA politeness specified in the snack-bar config.
*/
async getAriaLive(): Promise<AriaLivePoliteness> {
return (await this._simpleSnackBarLiveRegion())
return (await this._snackBarLiveRegion())
.getAttribute('aria-live') as Promise<AriaLivePoliteness>;
}

/**
* Whether the snack-bar has an action. Method cannot be used for snack-bar's with custom content.
*/
async hasAction(): Promise<boolean> {
await this._assertSimpleSnackBar();
return (await this._getSimpleSnackBarActionButton()) !== null;
await this._assertContentAnnotated();
return (await this._getActionButton()) !== null;
}

/**
* Gets the description of the snack-bar. Method cannot be used for snack-bar's without action or
* with custom content.
*/
async getActionDescription(): Promise<string> {
await this._assertSimpleSnackBarWithAction();
return (await this._getSimpleSnackBarActionButton())!.text();
await this._assertHasAction();
return (await this._getActionButton())!.text();
}


Expand All @@ -73,15 +72,15 @@ export class MatSnackBarHarness extends ContentContainerComponentHarness<string>
* without action or with custom content.
*/
async dismissWithAction(): Promise<void> {
await this._assertSimpleSnackBarWithAction();
await (await this._getSimpleSnackBarActionButton())!.click();
await this._assertHasAction();
await (await this._getActionButton())!.click();
}

/**
* Gets the message of the snack-bar. Method cannot be used for snack-bar's with custom content.
*/
async getMessage(): Promise<string> {
await this._assertSimpleSnackBar();
await this._assertContentAnnotated();
return (await this.locatorFor(this._messageSelector)()).text();
}

Expand All @@ -102,33 +101,33 @@ export class MatSnackBarHarness extends ContentContainerComponentHarness<string>
}

/**
* Asserts that the current snack-bar does not use custom content. Promise rejects if
* custom content is used.
* Asserts that the current snack-bar has annotated content. Promise reject
* if content is not annotated.
*/
private async _assertSimpleSnackBar(): Promise<void> {
protected async _assertContentAnnotated(): Promise<void> {
if (!await this._isSimpleSnackBar()) {
throw Error('Method cannot be used for snack-bar with custom content.');
}
}

/**
* Asserts that the current snack-bar does not use custom content and has
* an action defined. Otherwise the promise will reject.
* Asserts that the current snack-bar has an action defined. Otherwise the
* promise will reject.
*/
private async _assertSimpleSnackBarWithAction(): Promise<void> {
await this._assertSimpleSnackBar();
protected async _assertHasAction(): Promise<void> {
await this._assertContentAnnotated();
if (!await this.hasAction()) {
throw Error('Method cannot be used for standard snack-bar without action.');
throw Error('Method cannot be used for a snack-bar without an action.');
}
}

/** Whether the snack-bar is using the default content template. */
private async _isSimpleSnackBar(): Promise<boolean> {
return await this.locatorForOptional(this._simpleSnackBarSelector)() !== null;
return await this.locatorForOptional('.mat-simple-snackbar')() !== null;
}

/** Gets the simple snack bar action button. */
private async _getSimpleSnackBarActionButton() {
private async _getActionButton() {
return this.locatorForOptional(this._actionButtonSelector)();
}
}
4 changes: 2 additions & 2 deletions tools/public_api_guard/material/snack-bar-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { HarnessPredicate } from '@angular/cdk/testing';
export class MatSnackBarHarness extends ContentContainerComponentHarness<string> {
// (undocumented)
protected _actionButtonSelector: string;
protected _assertContentAnnotated(): Promise<void>;
protected _assertHasAction(): Promise<void>;
dismissWithAction(): Promise<void>;
getActionDescription(): Promise<string>;
getAriaLive(): Promise<AriaLivePoliteness>;
Expand All @@ -24,8 +26,6 @@ export class MatSnackBarHarness extends ContentContainerComponentHarness<string>
isDismissed(): Promise<boolean>;
// (undocumented)
protected _messageSelector: string;
// (undocumented)
protected _simpleSnackBarSelector: string;
static with(options?: SnackBarHarnessFilters): HarnessPredicate<MatSnackBarHarness>;
}

Expand Down