-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(cdk-experimental/menu): Implement grouping logic for menuitems #19628
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
Show all changes
15 commits
Select commit
Hold shift + click to select a range
671341f
feat(cdk-experimental/menu): Implement grouping logic for menuitems
andy9775 c230e82
refactor(cdk-experimental/menu): Mark unchnaged property as readonly
andy9775 bb7b960
refactor(cdk-experimental/menu): Mark MenuItem panel reference optional
andy9775 56f724e
fix(cdk-experimental/menu): Fix circular dependency
andy9775 39a3d88
Merge branch 'master' of github.com:angular/components into cdk-menu-…
andy9775 0438e6c
refactor(cdk-experimental/menu): Remove unncessary selectors from tes…
andy9775 463f5ba
refactor(cdk-experimental/menu): Move logic for clicke event emitting
andy9775 561040c
test(cdk-experimental/menu): Reduce unnecessary sanity tests for Menu…
andy9775 f1949de
test(cdk-experimental/menu): Remove usage of private members in menu …
andy9775 25baedd
refactor(cdk-experimental/menu): Remove unused properties on Menu and…
andy9775 120139c
build: CdkMenuItem now uses an interface which fixes the circular dep…
andy9775 95f0747
bug(cdk-experimental): Unsubscribe MenuItem subscriptions
andy9775 a412360
style: Remove parens around single argument arrow function
andy9775 8c9edc8
test(cdk-experimental/menu): refactor tests to use smaller, more focu…
andy9775 9714c83
style(cdk-experimental/menu): Use T[] syntax instead of Array<T> for …
andy9775 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 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 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,88 @@ | ||
import {ComponentFixture, TestBed, async} from '@angular/core/testing'; | ||
import {Component} from '@angular/core'; | ||
import {CdkMenuBar} from './menu-bar'; | ||
import {CdkMenuModule} from './menu-module'; | ||
import {CdkMenuItem} from './menu-item'; | ||
import {By} from '@angular/platform-browser'; | ||
|
||
describe('MenuBar', () => { | ||
describe('as radio group', () => { | ||
let fixture: ComponentFixture<MenuBarRadioGroup>; | ||
let menuItems: CdkMenuItem[]; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [CdkMenuModule], | ||
declarations: [MenuBarRadioGroup], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(MenuBarRadioGroup); | ||
fixture.detectChanges(); | ||
|
||
menuItems = fixture.debugElement | ||
.queryAll(By.directive(CdkMenuItem)) | ||
.map(element => element.injector.get(CdkMenuItem)); | ||
})); | ||
|
||
it('should toggle menuitemradio items', () => { | ||
expect(menuItems[0].checked).toBeTrue(); | ||
expect(menuItems[1].checked).toBeFalse(); | ||
|
||
menuItems[1].trigger(); | ||
|
||
expect(menuItems[0].checked).toBeFalse(); | ||
expect(menuItems[1].checked).toBeTrue(); | ||
}); | ||
}); | ||
|
||
describe('radiogroup change events', () => { | ||
let fixture: ComponentFixture<MenuBarRadioGroup>; | ||
let menuItems: CdkMenuItem[]; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [CdkMenuModule], | ||
declarations: [MenuBarRadioGroup], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(MenuBarRadioGroup); | ||
|
||
fixture.detectChanges(); | ||
|
||
menuItems = fixture.debugElement | ||
.queryAll(By.directive(CdkMenuItem)) | ||
.map(element => element.injector.get(CdkMenuItem)); | ||
})); | ||
|
||
it('should emit on click', () => { | ||
const spy = jasmine.createSpy('cdkMenu change spy'); | ||
fixture.debugElement | ||
.query(By.directive(CdkMenuBar)) | ||
.injector.get(CdkMenuBar) | ||
.change.subscribe(spy); | ||
|
||
menuItems[0].trigger(); | ||
|
||
expect(spy).toHaveBeenCalledTimes(1); | ||
expect(spy).toHaveBeenCalledWith(menuItems[0]); | ||
}); | ||
}); | ||
}); | ||
|
||
@Component({ | ||
template: ` | ||
<ul cdkMenuBar> | ||
<li role="none"> | ||
<button checked="true" role="menuitemradio" cdkMenuItem> | ||
first | ||
</button> | ||
</li> | ||
<li role="none"> | ||
<button role="menuitemradio" cdkMenuItem> | ||
second | ||
</button> | ||
</li> | ||
</ul> | ||
`, | ||
}) | ||
class MenuBarRadioGroup {} |
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 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,245 @@ | ||
import {Component} from '@angular/core'; | ||
import {ComponentFixture, TestBed, async} from '@angular/core/testing'; | ||
import {CdkMenuModule} from './menu-module'; | ||
import {CdkMenuGroup} from './menu-group'; | ||
import {CdkMenuItem} from './menu-item'; | ||
import {CdkMenu} from './menu'; | ||
import {By} from '@angular/platform-browser'; | ||
|
||
describe('MenuGroup', () => { | ||
describe('with MenuItems as checkbox', () => { | ||
let fixture: ComponentFixture<CheckboxMenu>; | ||
let menuItems: CdkMenuItem[]; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [CdkMenuModule], | ||
declarations: [CheckboxMenu], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(CheckboxMenu); | ||
fixture.detectChanges(); | ||
|
||
menuItems = fixture.debugElement | ||
.queryAll(By.directive(CdkMenuItem)) | ||
.map(e => e.injector.get(CdkMenuItem)); | ||
})); | ||
|
||
it('should not change state of sibling checked menuitemcheckbox', () => { | ||
menuItems[1].trigger(); | ||
|
||
expect(menuItems[0].checked).toBeTrue(); | ||
}); | ||
}); | ||
|
||
describe('with MenuItems as radio button', () => { | ||
let fixture: ComponentFixture<MenuWithMultipleRadioGroups>; | ||
let menuItems: CdkMenuItem[]; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [CdkMenuModule], | ||
declarations: [MenuWithMultipleRadioGroups], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(MenuWithMultipleRadioGroups); | ||
fixture.detectChanges(); | ||
|
||
menuItems = fixture.debugElement | ||
.queryAll(By.directive(CdkMenuItem)) | ||
.map(e => e.injector.get(CdkMenuItem)); | ||
})); | ||
|
||
it('should change state of sibling menuitemradio in same group', () => { | ||
menuItems[1].trigger(); | ||
|
||
expect(menuItems[1].checked).toBeTrue(); | ||
expect(menuItems[0].checked).toBeFalse(); | ||
}); | ||
|
||
it('should not change state of menuitemradio in sibling group', () => { | ||
menuItems[3].trigger(); | ||
|
||
expect(menuItems[3].checked).toBeTrue(); | ||
expect(menuItems[0].checked).toBeTrue(); | ||
}); | ||
|
||
it('should not change radiogroup state with disabled button', () => { | ||
menuItems[1].disabled = true; | ||
|
||
menuItems[1].trigger(); | ||
|
||
expect(menuItems[0].checked).toBeTrue(); | ||
expect(menuItems[1].checked).toBeFalse(); | ||
}); | ||
}); | ||
|
||
describe('change events', () => { | ||
let fixture: ComponentFixture<MenuWithMenuItemsAndRadioGroups>; | ||
let menu: CdkMenu; | ||
let menuItems: CdkMenuItem[]; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [CdkMenuModule], | ||
declarations: [MenuWithMenuItemsAndRadioGroups], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(MenuWithMenuItemsAndRadioGroups); | ||
fixture.detectChanges(); | ||
|
||
menu = fixture.debugElement.query(By.directive(CdkMenu)).injector.get(CdkMenu); | ||
|
||
menuItems = fixture.debugElement | ||
.queryAll(By.directive(CdkMenuItem)) | ||
.map(element => element.injector.get(CdkMenuItem)); | ||
})); | ||
|
||
it('should not emit from root menu with nested groups', () => { | ||
const spy = jasmine.createSpy('changeSpy for root menu'); | ||
menu.change.subscribe(spy); | ||
|
||
menuItems.forEach(menuItem => menuItem.trigger()); | ||
|
||
expect(spy).toHaveBeenCalledTimes(0); | ||
}); | ||
|
||
it('should emit from enclosing radio group only', () => { | ||
const spies: jasmine.Spy[] = []; | ||
|
||
fixture.debugElement.queryAll(By.directive(CdkMenuGroup)).forEach((group, index) => { | ||
const spy = jasmine.createSpy(`cdkMenuGroup ${index} change spy`); | ||
spies.push(spy); | ||
group.injector.get(CdkMenuGroup).change.subscribe(spy); | ||
}); | ||
|
||
menuItems[0].trigger(); | ||
|
||
expect(spies[1]).toHaveBeenCalledTimes(1); | ||
expect(spies[1]).toHaveBeenCalledWith(menuItems[0]); | ||
expect(spies[2]).toHaveBeenCalledTimes(0); | ||
expect(spies[3]).toHaveBeenCalledTimes(0); | ||
}); | ||
|
||
it('should not emit with click on disabled button', () => { | ||
const spy = jasmine.createSpy('cdkMenuGroup change spy'); | ||
|
||
fixture.debugElement | ||
.queryAll(By.directive(CdkMenuGroup))[1] | ||
.injector.get(CdkMenuGroup) | ||
.change.subscribe(spy); | ||
menuItems[0].disabled = true; | ||
|
||
menuItems[0].trigger(); | ||
|
||
expect(spy).toHaveBeenCalledTimes(0); | ||
}); | ||
|
||
it('should not emit on menuitem click', () => { | ||
const spies: jasmine.Spy[] = []; | ||
|
||
fixture.debugElement.queryAll(By.directive(CdkMenuGroup)).forEach((group, index) => { | ||
const spy = jasmine.createSpy(`cdkMenuGroup ${index} change spy`); | ||
spies.push(spy); | ||
group.injector.get(CdkMenuGroup).change.subscribe(spy); | ||
}); | ||
|
||
menuItems[2].trigger(); | ||
|
||
spies.forEach(spy => expect(spy).toHaveBeenCalledTimes(0)); | ||
}); | ||
}); | ||
}); | ||
|
||
@Component({ | ||
template: ` | ||
<ul cdkMenu> | ||
<li role="none"> | ||
<ul cdkMenuGroup> | ||
<li #first role="none"> | ||
<button checked="true" role="menuitemcheckbox" cdkMenuItem> | ||
one | ||
</button> | ||
</li> | ||
<li role="none"> | ||
<button role="menuitemcheckbox" cdkMenuItem> | ||
two | ||
</button> | ||
</li> | ||
</ul> | ||
</li> | ||
</ul> | ||
`, | ||
}) | ||
class CheckboxMenu {} | ||
|
||
@Component({ | ||
template: ` | ||
<ul cdkMenu> | ||
<li role="none"> | ||
<ul cdkMenuGroup> | ||
<li role="none"> | ||
<button checked="true" role="menuitemradio" cdkMenuItem> | ||
one | ||
</button> | ||
</li> | ||
<li role="none"> | ||
<button role="menuitemradio" cdkMenuItem> | ||
two | ||
</button> | ||
</li> | ||
</ul> | ||
</li> | ||
<li role="none"> | ||
<ul cdkMenuGroup> | ||
<li role="none"> | ||
<button role="menuitemradio" cdkMenuItem> | ||
three | ||
</button> | ||
</li> | ||
<li role="none"> | ||
<button role="menuitemradio" cdkMenuItem> | ||
four | ||
</button> | ||
</li> | ||
</ul> | ||
</li> | ||
</ul> | ||
`, | ||
}) | ||
class MenuWithMultipleRadioGroups {} | ||
|
||
@Component({ | ||
template: ` | ||
<ul cdkMenu> | ||
<li role="none"> | ||
<ul cdkMenuGroup> | ||
<li role="none"> | ||
<button role="menuitemradio" cdkMenuItem> | ||
one | ||
</button> | ||
</li> | ||
</ul> | ||
</li> | ||
<li role="none"> | ||
<ul cdkMenuGroup> | ||
<li role="none"> | ||
<button role="menuitemradio" cdkMenuItem> | ||
two | ||
</button> | ||
</li> | ||
</ul> | ||
</li> | ||
<li role="none"> | ||
<ul cdkMenuGroup> | ||
<li role="none"> | ||
<button cdkMenuItem> | ||
three | ||
</button> | ||
</li> | ||
</ul> | ||
</li> | ||
</ul> | ||
`, | ||
}) | ||
class MenuWithMenuItemsAndRadioGroups {} |
Oops, something went wrong.
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.
I just realized you test a lot of the same functionality in the tests for menu-group. Since menu-bar extends menu-group, I don't think you need to repeat the same tests here -- this suite can focus on tests specific to menu-bar's extra functionality.
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.
The menu and menu-bar tests are more of a sanity check to ensure that it's wired up correctly The meat of the logic is in the menu-group which contains most of the tests.
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.
In that case, just one or two sanity-check tests should be enough -- I don't think you need to go over checkboxes and radio buttons separately. (That said, I initially thought there were more tests in this file, for some reason. Now that I look again, there doesn't seem to be as much overlap. I'm still not used to reviewing on Github...)