-
Notifications
You must be signed in to change notification settings - Fork 6.8k
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(datepicker): allow MatCalendarHeader to be re-used inside a custom header #10856
fix(datepicker): allow MatCalendarHeader to be re-used inside a custom header #10856
Conversation
moduleId: module.id, | ||
selector: 'custom-header2', | ||
template: ` | ||
<div>Custom element</div> |
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.
@mmalerba Prepending elements may break the design (missing space at the bottom of the calendar body). Can this be solved with CSS?
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.
Yeah that's a good point. I think for now if people are overriding the header they can just add CSS to make the popup taller as well. It would be good to see if we can change the Material CSS to make it work automatically though
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.
Ok!
moduleId: module.id, | ||
selector: 'custom-header2', | ||
template: ` | ||
<div>Custom element</div> |
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.
Yeah that's a good point. I think for now if people are overriding the header they can just add CSS to make the popup taller as well. It would be good to see if we can change the Material CSS to make it work automatically though
|
||
<h2>Datepicker with custom header using standard header</h2> | ||
<p> | ||
<mat-form-field> |
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.
Can you create a unit test version of this that just tests that the component can be constructed without errors and the default header is in the DOM?
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 guess src/lib/datepicker/datepicker.spec.ts
would be a good place for that. I could move my test component CustomHeader2
there and create a test case where it is used as an input for the header.
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.
Yeah that sounds good, you can leave it in the demo app too if you want, but the unit test is most important
class DatepickerWithCustomHeader { | ||
@ViewChild('ch') datepicker: MatDatepicker<Date>; | ||
@ViewChild(MatDatepickerInput) datepickerInput: MatDatepickerInput<Date>; | ||
} |
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 tried to add the custom header component as an input:
@Component({
template: `
<input [matDatepicker]="ch">
<mat-datepicker #ch [calendarHeaderComponent]="CustomHeaderForDatepicker"></mat-datepicker>
`,
})
class DatepickerWithCustomHeader {
@ViewChild('ch') datepicker: MatDatepicker<Date>;
@ViewChild(MatDatepickerInput) datepickerInput: MatDatepickerInput<Date>;
}
@Component({
template: `
<div>Custom element</div>
<mat-calendar-header></mat-calendar-header>
`
})
export class CustomHeaderForDatepicker {
constructor() {
}
}
But I get:
: Cannot determine the module for class CustomHeaderForDatepicker in /src/lib/datepicker/datepicker.spec.ts! Add CustomHeaderForDatepicker to the NgModule to fix it.
I remember that I had to add the custom header component to entryComponents
for the demo page. How would that work for a test?
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.
Is this a possible solution https://stackoverflow.com/questions/41483841/providing-entrycomponents-for-a-testbed?
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.
Is this correct?
@Component({
template: `
<div>Custom element</div>
<mat-calendar-header></mat-calendar-header>
`
})
export class CustomHeaderForDatepicker {
constructor() {
}
}
@NgModule({
imports: [],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [CustomHeaderForDatepicker],
entryComponents: [CustomHeaderForDatepicker]
})
export class CustomHeaderModule {}
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.
You can modify the createComponent
function to take an optional list of entryComponents
. It already had optional lists for imports
and providers
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.
entryComponents
is not a member of the expected configuration object
static configureTestingModule(moduleDef: TestModuleMetadata): typeof TestBed;
export declare type TestModuleMetadata = {
providers?: any[];
declarations?: any[];
imports?: any[];
schemas?: Array<SchemaMetadata | any[]>;
aotSummaries?: () => any[];
};
Is this the way to do it angular/angular#10760 (comment) ?
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.
So the proposed solution would be
// Creates a test component fixture.
function createComponent(
component: any,
imports: any[] = [],
providers: any[] = [],
entryComponents: any[] = []):
ComponentFixture<any> {
TestBed.configureTestingModule({
imports: [
FormsModule,
MatDatepickerModule,
MatFormFieldModule,
MatInputModule,
NoopAnimationsModule,
ReactiveFormsModule,
...imports
],
providers,
declarations: [component],
});
TestBed.overrideModule(BrowserDynamicTestingModule, {
set: {
entryComponents: [entryComponents]
}
}).compileComponents();
return TestBed.createComponent(component);
}
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 tried this with the modified createComponent
function:
describe('datepicker with custom header', () => {
let fixture: ComponentFixture<DatepickerWithCustomHeader>;
let testComponent: DatepickerWithCustomHeader;
beforeEach(fakeAsync(() => {
fixture = createComponent(
DatepickerWithCustomHeader,
[MatNativeDateModule],
[],
[CustomHeaderForDatepicker]
);
fixture.detectChanges();
testComponent = fixture.componentInstance;
}));
it('should instantiate a datepicker with a custom header', fakeAsync(() => {
expect(testComponent).toBeTruthy();
}));
});
...
@Component({
template: `
<input [matDatepicker]="ch">
<mat-datepicker #ch [calendarHeaderComponent]="CustomHeaderForDatepicker"></mat-datepicker>
`,
})
class DatepickerWithCustomHeader {
@ViewChild('ch') datepicker: MatDatepicker<Date>;
@ViewChild(MatDatepickerInput) datepickerInput: MatDatepickerInput<Date>;
}
@Component({
template: `
<div>Custom element</div>
<mat-calendar-header></mat-calendar-header>
`
})
export class CustomHeaderForDatepicker {
constructor() {
}
}
However, I still get
: Cannot determine the module for class CustomHeaderForDatepicker in material2/src/lib/datepicker/datepicker.spec.ts! Add CustomHeaderForDatepicker to the NgModule to fix it.
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 think maybe the fact that you're exporting the class is the problem. Did you try just
class CustomHeaderForDatepicker { ... }
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.
done in 6aca934
However, there are still some issues:
-
tslint complains about
import {BrowserDynamicTestingModule} from '@angular/platform-browser-dynamic/testing';
-> missing inrollup-globals.ts
:ERROR: /home/travis/build/angular/material2/src/lib/datepicker/datepicker.spec.ts[33, 1]: Module "@angular/platform-browser-dynamic/testing" is missing from file /home/travis/build/angular/material2/tools/package-tools/rollup-globals.ts.
-
the test is executed now but fails. It complains about a missing import:
- should instantiate a datepicker with a custom header
MatDatepicker datepicker with custom header
Error: Component CustomHeaderForDatepicker is not part of any NgModule or the module has not been imported into your module. (line 22757)
But if I add
CustomHeaderForDatepicker
as an import tocreateComponent
it fails too - should instantiate a datepicker with a custom header
I would be glad for any advice ;-)
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.
Ah, I forgot to add the custom header to declarations
(https://stackoverflow.com/questions/44827999/component-is-not-part-of-any-ngmodule-or-the-module-has-not-been-imported-into-y?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa).
Done in 5ee44e7 Sorry!
lint problem should be fixed in e189b23
@@ -49,7 +56,13 @@ describe('MatDatepicker', () => { | |||
...imports | |||
], | |||
providers, | |||
declarations: [component], | |||
declarations: declarations, |
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 don't think we need to take declarations
in as a separate thing, you can just do:
declarations: [component, ...entryComponents]
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.
done in 47acc75
})); | ||
|
||
it('should instantiate a datepicker with a custom header', fakeAsync(() => { | ||
expect(testComponent).toBeTruthy(); |
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.
One more thing to check:
expect(fixture.debugElement.nativeElement.querySelector('mat-calendar-header')).toBeTruthy();
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.
Hm, it does not find it.
Do I need to do testComponent.datepicker.open();
before?
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 imagined a thing like this:
it('should find the standard header selector', fakeAsync(() => {
testComponent.datepicker.open();
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.querySelector('mat-calendar-header')).toBeTruthy();
}));
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.
done in 45eb7d7
But I had to use expect(document.querySelector('mat-calendar-header')).toBeTruthy();
I saw a lot of examples like this in the spec file.
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.
Did you try fixture.nativeElement.querySelector(...)
(with no debugElement
)? I see this elsewhere in the file too. Using document.querySelector
doesn't feel quite right...
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 know what you mean, you want to get the content from the parent element.
Unfortunately fixture.nativeElement.querySelector('mat-calendar-header')
yields null
.
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.
When I do console.log(fixture.nativeElement);
I get
<div id="root1" ng-version="6.0.0-rc.5">
<input ng-reflect-mat-datepicker="[object Object]" aria-haspopup="true" aria-owns="mat-datepicker-1">
<mat-datepicker></mat-datepicker>
</div>
This corresponds with the selectors in the template of DatepickerWithCustomHeader
. The selectors should be replaced by their associates templates so we could query for the actual DOM-elements.
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.
Oh I think I understand what's happening. The overlay must be adding itself to document.body
which is outside the fixture. Ok document.querySelector
is fine then
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.
@mmalerba In case there are more things left to do for this PR, please let me know. I can take care of it tomorrow or Friday. Then I will be away for two weeks.
`, | ||
}) | ||
class DatepickerWithCustomHeader { | ||
@ViewChild('ch') datepicker: MatDatepicker<Date>; |
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.
Don't need these ViewChild
since they aren't used for anything in the test
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 left the datepicker
in 47acc75 for the moment
` | ||
}) | ||
class CustomHeaderForDatepicker { | ||
constructor() { |
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.
Can omit the constructor
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.
done in 47acc75
expect(testComponent).toBeTruthy(); | ||
})); | ||
|
||
it('should find the standard header selector', fakeAsync(() => { |
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.
nit: 'should find the standard header element'
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.
done in ea5e99b
@mmalerba There seem to be some issues with the integration tests (mode saucelabs required), I can see some failing for other PRs too. I do not think the failing tests are specifically related to this PR. |
yeah CI has been a little flaky lately, probably unrelated |
component: any, | ||
imports: any[] = [], | ||
providers: any[] = [], | ||
entryComponents: any[] = []): ComponentFixture<any> { |
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.
nit: you could use better types instead of any
:
import {Component, FactoryProvider, Type, ValueProvider, ViewChild} from '@angular/core';
...
function createComponent(
component: Type<any>,
imports: Type<any>[] = [],
providers: (FactoryProvider | ValueProvider)[] = [],
entryComponents: Type<any>[] = []): ComponentFixture<any> {
...
}
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.
done in 811ee17
@Component({ | ||
template: ` | ||
<div>Custom element</div> | ||
<mat-calendar-header></mat-calendar-header> |
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.
Missing indentation for template?
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.
done in 811ee17
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
This PR makes
MatCalendarHeader
available for the use from the outside ofMatCalendar
. Like this, custom calendar header components may make use of the selectormat-calendar-header
.