diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c31d5bd66603..5a2b31a75e74 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -98,6 +98,7 @@ /src/material-experimental/mdc-radio/** @mmalerba /src/material-experimental/mdc-slide-toggle/** @crisbeto /src/material-experimental/mdc-tabs/** @crisbeto +/src/material-experimental/mdc-sidenav/** @crisbeto /src/material-experimental/mdc-theming/** @mmalerba /src/material-experimental/mdc-typography/** @mmalerba /src/material-experimental/popover-edit/** @kseamon @andrewseguin diff --git a/src/material-experimental/mdc-sidenav/BUILD.bazel b/src/material-experimental/mdc-sidenav/BUILD.bazel new file mode 100644 index 000000000000..1980444c5d21 --- /dev/null +++ b/src/material-experimental/mdc-sidenav/BUILD.bazel @@ -0,0 +1,97 @@ +package(default_visibility = ["//visibility:public"]) + +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary", "sass_library") +load("//tools:defaults.bzl", "ng_e2e_test_library", "ng_module", "ng_test_library", "ng_web_test_suite", "ts_library") +load("//src/e2e-app:test_suite.bzl", "e2e_test_suite") + +ng_module( + name = "mdc-sidenav", + srcs = glob( + ["**/*.ts"], + exclude = [ + "**/*.spec.ts", + "harness/**", + ], + ), + assets = [ + # TODO: include scss assets + ] + glob(["**/*.html"]), + module_name = "@angular/material-experimental/mdc-sidenav", + deps = [ + "//src/material/core", + ], +) + +ts_library( + name = "harness", + srcs = glob( + ["harness/**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + deps = [ + "//src/cdk-experimental/testing", + ], +) + +sass_library( + name = "mdc_sidenav_scss_lib", + srcs = glob(["**/_*.scss"]), + deps = [ + "//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib", + "//src/material-experimental/mdc-helpers:mdc_scss_deps_lib", + "//src/material/core:core_scss_lib", + ], +) + +sass_binary( + name = "sidenav_scss", + src = "sidenav.scss", + include_paths = [ + "external/npm/node_modules", + ], + deps = [ + "//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib", + "//src/material-experimental/mdc-helpers:mdc_scss_deps_lib", + "//src/material/core:all_themes", + ], +) + +ng_test_library( + name = "sidenav_tests_lib", + srcs = [ + "harness/sidenav-harness.spec.ts", + ], + deps = [ + ":harness", + ":mdc-sidenav", + "//src/cdk-experimental/testing", + "//src/cdk-experimental/testing/testbed", + "//src/cdk/testing", + "//src/material/sidenav", + "@npm//@angular/platform-browser", + ], +) + +ng_web_test_suite( + name = "unit_tests", + deps = [ + ":sidenav_tests_lib", + "//src/material-experimental:mdc_require_config.js", + ], +) + +ng_e2e_test_library( + name = "e2e_test_sources", + srcs = glob(["**/*.e2e.spec.ts"]), + deps = [ + "//src/cdk/private/testing/e2e", + ], +) + +e2e_test_suite( + name = "e2e_tests", + deps = [ + ":e2e_test_sources", + "//src/cdk/private/testing/e2e", + ], +) diff --git a/src/material-experimental/mdc-sidenav/README.md b/src/material-experimental/mdc-sidenav/README.md new file mode 100644 index 000000000000..cf873b4c072b --- /dev/null +++ b/src/material-experimental/mdc-sidenav/README.md @@ -0,0 +1 @@ + diff --git a/src/material-experimental/mdc-sidenav/_mdc-sidenav.scss b/src/material-experimental/mdc-sidenav/_mdc-sidenav.scss new file mode 100644 index 000000000000..7d21c8fef14f --- /dev/null +++ b/src/material-experimental/mdc-sidenav/_mdc-sidenav.scss @@ -0,0 +1,13 @@ +@import '../mdc-helpers/mdc-helpers'; + +@mixin mat-sidenav-theme-mdc($theme) { + @include mat-using-mdc-theme($theme) { + // TODO: implement MDC-based sidenav. + } +} + +@mixin mat-sidenav-typography-mdc($config) { + @include mat-using-mdc-typography($config) { + // TODO: implement MDC-based sidenav. + } +} diff --git a/src/material-experimental/mdc-sidenav/harness/mdc-sidenav-harness.ts b/src/material-experimental/mdc-sidenav/harness/mdc-sidenav-harness.ts new file mode 100644 index 000000000000..7c92bce1cc4f --- /dev/null +++ b/src/material-experimental/mdc-sidenav/harness/mdc-sidenav-harness.ts @@ -0,0 +1,18 @@ +/** + * @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} from '@angular/cdk-experimental/testing'; + + +/** + * Harness for interacting with a MDC-based mat-sidenav in tests. + * @dynamic + */ +export class MatSidenavHarness extends ComponentHarness { + // TODO: implement once MDC sidenav is done. +} diff --git a/src/material-experimental/mdc-sidenav/harness/sidenav-harness-filters.ts b/src/material-experimental/mdc-sidenav/harness/sidenav-harness-filters.ts new file mode 100644 index 000000000000..013ea019dcf6 --- /dev/null +++ b/src/material-experimental/mdc-sidenav/harness/sidenav-harness-filters.ts @@ -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 SidenavHarnessFilters = { + id?: string; +}; diff --git a/src/material-experimental/mdc-sidenav/harness/sidenav-harness.spec.ts b/src/material-experimental/mdc-sidenav/harness/sidenav-harness.spec.ts new file mode 100644 index 000000000000..c96fb0604d95 --- /dev/null +++ b/src/material-experimental/mdc-sidenav/harness/sidenav-harness.spec.ts @@ -0,0 +1,116 @@ +import {HarnessLoader} from '@angular/cdk-experimental/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk-experimental/testing/testbed'; +import {Component} from '@angular/core'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {MatSidenavModule} from '@angular/material/sidenav'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; +import {MatSidenavModule as MatMdcSidenavModule} from '../index'; +import {MatSidenavHarness} from './sidenav-harness'; +import {MatSidenavHarness as MatMdcSidenavHarness} from './mdc-sidenav-harness'; + +let fixture: ComponentFixture; +let loader: HarnessLoader; +let harness: typeof MatSidenavHarness; + +describe('MatSidenavHarness', () => { + describe('non-MDC-based', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MatSidenavModule, NoopAnimationsModule], + declarations: [SidenavHarnessTest], + }).compileComponents(); + + fixture = TestBed.createComponent(SidenavHarnessTest); + fixture.detectChanges(); + loader = TestbedHarnessEnvironment.loader(fixture); + harness = MatSidenavHarness; + }); + + runTests(); + }); + + describe('MDC-based', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MatMdcSidenavModule, NoopAnimationsModule], + declarations: [SidenavHarnessTest], + }).compileComponents(); + + fixture = TestBed.createComponent(SidenavHarnessTest); + fixture.detectChanges(); + loader = TestbedHarnessEnvironment.loader(fixture); + // Public APIs are the same as MatSidenavHarness, but cast + // is necessary because of different private fields. + harness = MatMdcSidenavHarness as any; + }); + + // TODO: enable after MDC sidenav is implemented + // runTests(); + }); +}); + +/** Shared tests to run on both the original and MDC-based sidenav. */ +function runTests() { + it('should load all sidenav harnesses', async () => { + const sidenavs = await loader.getAllHarnesses(harness); + expect(sidenavs.length).toBe(3); + }); + + it('should be able to get whether the sidenav is open', async () => { + const sidenavs = await loader.getAllHarnesses(harness); + + expect(await sidenavs[0].isOpen()).toBe(false); + expect(await sidenavs[1].isOpen()).toBe(false); + expect(await sidenavs[2].isOpen()).toBe(true); + + fixture.componentInstance.threeOpened = false; + fixture.detectChanges(); + + expect(await sidenavs[0].isOpen()).toBe(false); + expect(await sidenavs[1].isOpen()).toBe(false); + expect(await sidenavs[2].isOpen()).toBe(false); + }); + + it('should be able to get the position of a sidenav', async () => { + const sidenavs = await loader.getAllHarnesses(harness); + + expect(await sidenavs[0].getPosition()).toBe('start'); + expect(await sidenavs[1].getPosition()).toBe('end'); + expect(await sidenavs[2].getPosition()).toBe('start'); + }); + + it('should be able to get the mode of a sidenav', async () => { + const sidenavs = await loader.getAllHarnesses(harness); + + expect(await sidenavs[0].getMode()).toBe('over'); + expect(await sidenavs[1].getMode()).toBe('side'); + expect(await sidenavs[2].getMode()).toBe('push'); + }); + + it('should be able to get whether a sidenav is fixed in the viewport', async () => { + const sidenavs = await loader.getAllHarnesses(harness); + + expect(await sidenavs[0].isFixedInViewport()).toBe(false); + expect(await sidenavs[1].isFixedInViewport()).toBe(false); + expect(await sidenavs[2].isFixedInViewport()).toBe(true); + }); +} + +@Component({ + template: ` + + One + Two + Content + + + + Three + Content + + ` +}) +class SidenavHarnessTest { + threeOpened = true; +} + diff --git a/src/material-experimental/mdc-sidenav/harness/sidenav-harness.ts b/src/material-experimental/mdc-sidenav/harness/sidenav-harness.ts new file mode 100644 index 000000000000..d2c62d423898 --- /dev/null +++ b/src/material-experimental/mdc-sidenav/harness/sidenav-harness.ts @@ -0,0 +1,63 @@ +/** + * @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 {SidenavHarnessFilters} from './sidenav-harness-filters'; + +/** + * Harness for interacting with a standard mat-sidenav in tests. + * @dynamic + */ +export class MatSidenavHarness extends ComponentHarness { + static hostSelector = '.mat-sidenav'; + + /** + * Gets a `HarnessPredicate` that can be used to search for a sidenav with + * specific attributes. + * @param options Options for narrowing the search. + * @return `HarnessPredicate` configured with the given options. + */ + static with(options: SidenavHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatSidenavHarness) + .addOption('id', options.id, async (harness, id) => { + const host = await harness.host(); + return (await host.getAttribute('id')) === id; + }); + } + + /** Gets whether the sidenav is open. */ + async isOpen(): Promise { + return (await this.host()).hasClass('mat-drawer-opened'); + } + + /** Gets the position of the sidenav inside its container. */ + async getPosition(): Promise<'start'|'end'> { + const host = await this.host(); + return (await host.hasClass('mat-drawer-end')) ? 'end' : 'start'; + } + + /** Gets the mode that the sidenav is in. */ + async getMode(): Promise<'over'|'push'|'side'> { + const host = await this.host(); + + if (await host.hasClass('mat-drawer-push')) { + return 'push'; + } + + if (await host.hasClass('mat-drawer-side')) { + return 'side'; + } + + return 'over'; + } + + /** Gets whether the sidenav is fixed in the viewport. */ + async isFixedInViewport(): Promise { + return (await this.host()).hasClass('mat-sidenav-fixed'); + } +} diff --git a/src/material-experimental/mdc-sidenav/index.ts b/src/material-experimental/mdc-sidenav/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material-experimental/mdc-sidenav/index.ts @@ -0,0 +1,9 @@ +/** + * @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 * from './public-api'; diff --git a/src/material-experimental/mdc-sidenav/module.ts b/src/material-experimental/mdc-sidenav/module.ts new file mode 100644 index 000000000000..7df9f38cca66 --- /dev/null +++ b/src/material-experimental/mdc-sidenav/module.ts @@ -0,0 +1,18 @@ +/** + * @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 {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {MatCommonModule} from '@angular/material/core'; + +@NgModule({ + imports: [MatCommonModule, CommonModule], + exports: [MatCommonModule], +}) +export class MatSidenavModule { +} diff --git a/src/material-experimental/mdc-sidenav/public-api.ts b/src/material-experimental/mdc-sidenav/public-api.ts new file mode 100644 index 000000000000..508adc834fb3 --- /dev/null +++ b/src/material-experimental/mdc-sidenav/public-api.ts @@ -0,0 +1,9 @@ +/** + * @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 * from './module'; diff --git a/src/material-experimental/mdc-sidenav/sidenav.e2e.spec.ts b/src/material-experimental/mdc-sidenav/sidenav.e2e.spec.ts new file mode 100644 index 000000000000..507f760e60cc --- /dev/null +++ b/src/material-experimental/mdc-sidenav/sidenav.e2e.spec.ts @@ -0,0 +1 @@ +// TODO: copy tests from existing mat-sidenav, update as necessary to fix. diff --git a/src/material-experimental/mdc-sidenav/sidenav.scss b/src/material-experimental/mdc-sidenav/sidenav.scss new file mode 100644 index 000000000000..3caa13e6b9fe --- /dev/null +++ b/src/material-experimental/mdc-sidenav/sidenav.scss @@ -0,0 +1 @@ +// TODO: implement MDC-based sidenav diff --git a/src/material-experimental/mdc-sidenav/tsconfig-build.json b/src/material-experimental/mdc-sidenav/tsconfig-build.json new file mode 100644 index 000000000000..772120ec4480 --- /dev/null +++ b/src/material-experimental/mdc-sidenav/tsconfig-build.json @@ -0,0 +1,15 @@ +{ + "extends": "../tsconfig-build", + "files": [ + "public-api.ts", + "../typings.d.ts" + ], + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "strictMetadataEmit": true, + "flatModuleOutFile": "index.js", + "flatModuleId": "@angular/material-experimental/mdc-sidenav", + "skipTemplateCodegen": true, + "fullTemplateTypeCheck": true + } +} diff --git a/src/material/sidenav/drawer.ts b/src/material/sidenav/drawer.ts index a3d28c8d6206..7ecc363ecb3e 100644 --- a/src/material/sidenav/drawer.ts +++ b/src/material/sidenav/drawer.ts @@ -123,6 +123,7 @@ export class MatDrawerContent extends CdkScrollable implements AfterContentInit '[class.mat-drawer-over]': 'mode === "over"', '[class.mat-drawer-push]': 'mode === "push"', '[class.mat-drawer-side]': 'mode === "side"', + '[class.mat-drawer-opened]': 'opened', 'tabIndex': '-1', }, changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/material/sidenav/sidenav.ts b/src/material/sidenav/sidenav.ts index b6738c878c41..da78201c667f 100644 --- a/src/material/sidenav/sidenav.ts +++ b/src/material/sidenav/sidenav.ts @@ -65,6 +65,7 @@ export class MatSidenavContent extends MatDrawerContent { '[class.mat-drawer-over]': 'mode === "over"', '[class.mat-drawer-push]': 'mode === "push"', '[class.mat-drawer-side]': 'mode === "side"', + '[class.mat-drawer-opened]': 'opened', '[class.mat-sidenav-fixed]': 'fixedInViewport', '[style.top.px]': 'fixedInViewport ? fixedTopGap : null', '[style.bottom.px]': 'fixedInViewport ? fixedBottomGap : null',