-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(material-experimental): add test harness for button #16556
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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
11 changes: 11 additions & 0 deletions
11
src/material-experimental/mdc-button/harness/button-harness-filters.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/** | ||
* @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.io/license | ||
*/ | ||
|
||
export type ButtonHarnessFilters = { | ||
text?: string | RegExp | ||
}; |
159 changes: 159 additions & 0 deletions
159
src/material-experimental/mdc-button/harness/button-harness.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import {HarnessLoader} from '@angular/cdk-experimental/testing'; | ||
import {TestbedHarnessEnvironment} from '@angular/cdk-experimental/testing/testbed'; | ||
import {Platform, PlatformModule} from '@angular/cdk/platform'; | ||
import {Component} from '@angular/core'; | ||
import {ComponentFixture, inject, TestBed} from '@angular/core/testing'; | ||
import {MatButtonModule} from '@angular/material/button'; | ||
import {MatButtonModule as MatMdcButtonModule} from '../index'; | ||
import {MatButtonHarness} from './button-harness'; | ||
import {MatButtonHarness as MatMdcButtonHarness} from './mdc-button-harness'; | ||
|
||
let fixture: ComponentFixture<ButtonHarnessTest>; | ||
let loader: HarnessLoader; | ||
let buttonHarness: typeof MatButtonHarness; | ||
let platform: Platform; | ||
|
||
describe('MatButtonHarness', () => { | ||
describe('non-MDC-based', () => { | ||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
imports: [MatButtonModule, PlatformModule], | ||
declarations: [ButtonHarnessTest], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(ButtonHarnessTest); | ||
fixture.detectChanges(); | ||
loader = TestbedHarnessEnvironment.loader(fixture); | ||
buttonHarness = MatButtonHarness; | ||
}); | ||
|
||
runTests(); | ||
}); | ||
|
||
describe('MDC-based', () => { | ||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
imports: [MatMdcButtonModule], | ||
declarations: [ButtonHarnessTest], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(ButtonHarnessTest); | ||
fixture.detectChanges(); | ||
loader = TestbedHarnessEnvironment.loader(fixture); | ||
// Public APIs are the same as MatButtonHarness, but cast is necessary because of different | ||
// private fields. | ||
buttonHarness = MatMdcButtonHarness as any; | ||
}); | ||
|
||
runTests(); | ||
}); | ||
}); | ||
|
||
/** Shared tests to run on both the original and MDC-based buttons. */ | ||
function runTests() { | ||
beforeEach(inject([Platform], (p: Platform) => { | ||
platform = p; | ||
})); | ||
|
||
it('should load all button harnesses', async () => { | ||
const buttons = await loader.getAllHarnesses(buttonHarness); | ||
expect(buttons.length).toBe(14); | ||
}); | ||
|
||
it('should load button with exact text', async () => { | ||
const buttons = await loader.getAllHarnesses(buttonHarness.with({text: 'Basic button'})); | ||
expect(buttons.length).toBe(1); | ||
expect(await buttons[0].getText()).toBe('Basic button'); | ||
}); | ||
|
||
it('should load button with regex label match', async () => { | ||
const buttons = await loader.getAllHarnesses(buttonHarness.with({text: /basic/i})); | ||
expect(buttons.length).toBe(2); | ||
expect(await buttons[0].getText()).toBe('Basic button'); | ||
expect(await buttons[1].getText()).toBe('Basic anchor'); | ||
}); | ||
|
||
it('should get disabled state', async () => { | ||
// Grab each combination of [enabled, disabled] ⨯ [button, anchor] | ||
const [disabledFlatButton, enabledFlatAnchor] = | ||
await loader.getAllHarnesses(buttonHarness.with({text: /flat/i})); | ||
const [enabledRaisedButton, disabledRaisedAnchor] = | ||
await loader.getAllHarnesses(buttonHarness.with({text: /raised/i})); | ||
|
||
expect(await enabledFlatAnchor.isDisabled()).toBe(false); | ||
expect(await disabledFlatButton.isDisabled()).toBe(true); | ||
expect(await enabledRaisedButton.isDisabled()).toBe(false); | ||
expect(await disabledRaisedAnchor.isDisabled()).toBe(true); | ||
}); | ||
|
||
it('should get button text', async () => { | ||
const [firstButton, secondButton] = await loader.getAllHarnesses(buttonHarness); | ||
expect(await firstButton.getText()).toBe('Basic button'); | ||
expect(await secondButton.getText()).toBe('Flat button'); | ||
}); | ||
|
||
it('should focus and blur a button', async () => { | ||
const button = await loader.getHarness(buttonHarness.with({text: 'Basic button'})); | ||
expect(getActiveElementId()).not.toBe('basic'); | ||
await button.foucs(); | ||
expect(getActiveElementId()).toBe('basic'); | ||
await button.blur(); | ||
expect(getActiveElementId()).not.toBe('basic'); | ||
}); | ||
|
||
it('should click a button', async () => { | ||
const button = await loader.getHarness(buttonHarness.with({text: 'Basic button'})); | ||
await button.click(); | ||
|
||
expect(fixture.componentInstance.clicked).toBe(true); | ||
}); | ||
|
||
it('should not click a disabled button', async () => { | ||
// Older versions of Edge have a bug where `disabled` buttons are still clickable if | ||
// they contain child elements. We skip this check on Edge. | ||
// See https://stackoverflow.com/questions/32377026/disabled-button-is-clickable-on-edge-browser | ||
if (platform.EDGE) { | ||
return; | ||
} | ||
|
||
const button = await loader.getHarness(buttonHarness.with({text: 'Flat button'})); | ||
await button.click(); | ||
|
||
expect(fixture.componentInstance.clicked).toBe(false); | ||
}); | ||
} | ||
|
||
function getActiveElementId() { | ||
return document.activeElement ? document.activeElement.id : ''; | ||
} | ||
|
||
@Component({ | ||
// Include one of each type of button selector to ensure that they're all captured by | ||
// the harness's selector. | ||
template: ` | ||
<button id="basic" type="button" mat-button (click)="clicked = true"> | ||
Basic button | ||
</button> | ||
<button id="flat" type="button" mat-flat-button disabled (click)="clicked = true"> | ||
Flat button | ||
</button> | ||
<button id="raised" type="button" mat-raised-button>Raised button</button> | ||
<button id="stroked" type="button" mat-stroked-button>Stroked button</button> | ||
<button id="icon" type="button" mat-icon-button>Icon button</button> | ||
<button id="fab" type="button" mat-fab>Fab button</button> | ||
<button id="mini-fab" type="button" mat-mini-fab>Mini Fab button</button> | ||
|
||
<a id="anchor-basic" mat-button>Basic anchor</a> | ||
<a id="anchor-flat" mat-flat-button>Flat anchor</a> | ||
<a id="anchor-raised" mat-raised-button disabled>Raised anchor</a> | ||
<a id="anchor-stroked" mat-stroked-button>Stroked anchor</a> | ||
<a id="anchor-icon" mat-icon-button>Icon anchor</a> | ||
<a id="anchor-fab" mat-fab>Fab anchor</a> | ||
<a id="anchor-mini-fab" mat-mini-fab>Mini Fab anchor</a> | ||
` | ||
}) | ||
class ButtonHarnessTest { | ||
disabled = true; | ||
clicked = false; | ||
} | ||
|
67 changes: 67 additions & 0 deletions
67
src/material-experimental/mdc-button/harness/button-harness.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/** | ||
* @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.io/license | ||
*/ | ||
|
||
import {ComponentHarness, HarnessPredicate} from '@angular/cdk-experimental/testing'; | ||
import {coerceBooleanProperty} from '@angular/cdk/coercion'; | ||
import {ButtonHarnessFilters} from './button-harness-filters'; | ||
|
||
|
||
/** | ||
* Harness for interacting with a standard mat-button in tests. | ||
* @dynamic | ||
*/ | ||
export class MatButtonHarness extends ComponentHarness { | ||
// TODO(jelbourn) use a single class, like `.mat-button-base` | ||
static hostSelector = [ | ||
'[mat-button]', | ||
'[mat-raised-button]', | ||
'[mat-flat-button]', | ||
'[mat-icon-button]', | ||
'[mat-stroked-button]', | ||
'[mat-fab]', | ||
'[mat-mini-fab]', | ||
].join(','); | ||
|
||
/** | ||
* Gets a `HarnessPredicate` that can be used to search for a button with specific attributes. | ||
* @param options Options for narrowing the search: | ||
* - `text` finds a button with specific text content. | ||
* @return a `HarnessPredicate` configured with the given options. | ||
*/ | ||
static with(options: ButtonHarnessFilters = {}): HarnessPredicate<MatButtonHarness> { | ||
return new HarnessPredicate(MatButtonHarness) | ||
.addOption('text', options.text, | ||
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text)); | ||
} | ||
|
||
/** Clicks the button. */ | ||
async click(): Promise<void> { | ||
return (await this.host()).click(); | ||
} | ||
|
||
/** Gets a boolean promise indicating if the button is disabled. */ | ||
async isDisabled(): Promise<boolean> { | ||
const disabled = (await this.host()).getAttribute('disabled'); | ||
return coerceBooleanProperty(await disabled); | ||
} | ||
|
||
/** Gets a promise for the button's label text. */ | ||
async getText(): Promise<string> { | ||
return (await this.host()).text(); | ||
} | ||
|
||
/** Focuses the button and returns a void promise that indicates when the action is complete. */ | ||
async foucs(): Promise<void> { | ||
return (await this.host()).focus(); | ||
} | ||
|
||
/** Blurs the button and returns a void promise that indicates when the action is complete. */ | ||
async blur(): Promise<void> { | ||
return (await this.host()).blur(); | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
src/material-experimental/mdc-button/harness/mdc-button-harness.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/** | ||
* @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.io/license | ||
*/ | ||
|
||
import {ComponentHarness, HarnessPredicate} from '@angular/cdk-experimental/testing'; | ||
import {coerceBooleanProperty} from '@angular/cdk/coercion'; | ||
import {ButtonHarnessFilters} from './button-harness-filters'; | ||
|
||
|
||
/** | ||
* Harness for interacting with a MDC-based mat-button in tests. | ||
* @dynamic | ||
*/ | ||
export class MatButtonHarness extends ComponentHarness { | ||
// TODO(jelbourn) use a single class, like `.mat-button-base` | ||
static hostSelector = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as the comment above importing these There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a |
||
'[mat-button]', | ||
'[mat-raised-button]', | ||
'[mat-flat-button]', | ||
'[mat-icon-button]', | ||
'[mat-stroked-button]', | ||
'[mat-fab]', | ||
'[mat-mini-fab]', | ||
].join(','); | ||
|
||
/** | ||
* Gets a `HarnessPredicate` that can be used to search for a button with specific attributes. | ||
* @param options Options for narrowing the search: | ||
* - `text` finds a button with specific text content. | ||
* @return a `HarnessPredicate` configured with the given options. | ||
*/ | ||
static with(options: ButtonHarnessFilters = {}): HarnessPredicate<MatButtonHarness> { | ||
return new HarnessPredicate(MatButtonHarness) | ||
.addOption('text', options.text, | ||
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text)); | ||
} | ||
|
||
/** Clicks the button. */ | ||
async click(): Promise<void> { | ||
return (await this.host()).click(); | ||
} | ||
|
||
/** Gets a boolean promise indicating if the button is disabled. */ | ||
async isDisabled(): Promise<boolean> { | ||
const disabled = (await this.host()).getAttribute('disabled'); | ||
return coerceBooleanProperty(await disabled); | ||
} | ||
|
||
/** Gets a promise for the button's label text. */ | ||
async getText(): Promise<string> { | ||
return (await this.host()).text(); | ||
} | ||
|
||
/** Focuses the button and returns a void promise that indicates when the action is complete. */ | ||
async foucs(): Promise<void> { | ||
return (await this.host()).focus(); | ||
} | ||
|
||
/** Blurs the button and returns a void promise that indicates when the action is complete. */ | ||
async blur(): Promise<void> { | ||
return (await this.host()).blur(); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an array of these defined as
BUTTON_HOST_ATTRIBUTES
. Could we export and re-use it here? Otherwise we'll have to remember to update this array if we add more button types.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really just want to change this to use e.g.
.mat-button-base
, which was in another PR when I wrote this. Added aTODO
for now