diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 335f5573dbd..e3356681725 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -39,4 +39,4 @@ rules: mocha/no-nested-tests: error mocha/no-pending-tests: error mocha/no-skipped-tests: error - mocha/valid-suite-description: [error, ^MDC.+] + mocha/valid-suite-description: [error, ^MDC.+|^webpack] diff --git a/closure_externs.js b/closure_externs.js index 2969dc446f8..cd555b6e9e7 100644 --- a/closure_externs.js +++ b/closure_externs.js @@ -63,7 +63,7 @@ * @see http://npmjs.com/focus-trap */ -goog.provide('mdc.thirdparty.focusTrap') +goog.provide('mdc.thirdparty.focusTrap'); /** * @typedef {{ @@ -106,3 +106,62 @@ class FocusTrapInstance { * @param {FocusTrapCreateOptions=} createOptions */ mdc.thirdparty.focusTrap.default; + +/** @record */ +class PathLib { + /** + * @param {string} path + * @param {string=} extension + * @return {string} + */ + basename(path, extension) {} + + /** + * @param {string} from + * @param {string} to + * @return {string} + */ + relative(from, to) {} + + /** + * @param {...string} paths + * @return {string} + */ + resolve(...paths) {} + + /** + * Joins all given path segments together using the platform specific separator as a delimiter, then normalizes the + * resulting this.pathLib_. + * + * Zero-length path segments are ignored. If the joined path string is a zero-length string then '.' will be returned, + * representing the current working directory. + * + * @param {...string} paths + * @return {string} + */ + join(...paths) {} +} + +/** @record */ +class GlobLib { + /** + * @param {string} pattern + * @param {!Object=} options + * @return {!Array} + */ + sync(pattern, options = undefined) {} +} + +/** @record */ +class FsExtraLib { + /** + * @param {string} path + * @return {boolean} + */ + existsSync(path) {} + + /** + * @param {string} path + */ + removeSync(path) {} +} diff --git a/package.json b/package.json index 80485aa19be..b794f4300f8 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,13 @@ "lint": "npm-run-all --parallel lint:*", "postinstall": "lerna bootstrap", "pretest": "npm run lint && node scripts/check-imports.js", - "test": "npm run test:unit && npm run test:closure && npm run test:dependency && npm run build; npm run clean", + "test": "npm run test:buildconfig && npm run test:unit && npm run test:closure && npm run test:dependency && npm run build && npm run clean", "posttest": "istanbul report --root coverage text-summary && istanbul check-coverage --lines 95 --statements 95 --branches 95 --functions 95", "test:watch": "karma start --auto-watch", "test:unit": "karma start --single-run", "test:closure": "./scripts/closure-test.sh", - "test:dependency": "./scripts/dependency-test.sh" + "test:dependency": "./scripts/dependency-test.sh", + "test:buildconfig": "mocha ./test/build/index.js" }, "devDependencies": { "ascii-table": "0.0.9", @@ -59,6 +60,7 @@ "istanbul": "^0.4.4", "istanbul-instrumenter-loader": "^3.0.0", "json-loader": "^0.5.4", + "json-stringify-safe": "^5.0.1", "karma": "^2.0.0", "karma-chrome-launcher": "^2.0.0", "karma-coverage": "^1.1.0", diff --git a/test/build/goldens/build-config-dev-env.golden.json b/test/build/goldens/build-config-dev-env.golden.json new file mode 100644 index 00000000000..e6ad530b4a0 --- /dev/null +++ b/test/build/goldens/build-config-dev-env.golden.json @@ -0,0 +1,207 @@ +[ + { + "name": "js-all", + "entry": "/packages/material-components-web/index.js", + "output": { + "path": "/build", + "publicPath": "/assets/", + "filename": "material-components-web.js", + "libraryTarget": "umd", + "library": "mdc" + }, + "devServer": { + "disableHostCheck": true + }, + "devtool": "source-map", + "module": { + "rules": [ + { + "test": {}, + "exclude": {}, + "loader": "babel-loader", + "options": { + "cacheDirectory": true + } + } + ] + }, + "plugins": [ + { + "options": { + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/", + "raw": true, + "entryOnly": true + }, + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/" + } + ] + }, + { + "name": "demo-css", + "entry": { + "button": "/demos/button.scss", + "card": "/demos/card.scss", + "checkbox": "/demos/checkbox.scss", + "chips": "/demos/chips.scss", + "common": "/demos/common.scss", + "dialog": "/demos/dialog.scss", + "drawer/drawer": "/demos/drawer/drawer.scss", + "elevation": "/demos/elevation.scss", + "fab": "/demos/fab.scss", + "grid-list": "/demos/grid-list.scss", + "icon-toggle": "/demos/icon-toggle.scss", + "index": "/demos/index.scss", + "layout-grid": "/demos/layout-grid.scss", + "linear-progress": "/demos/linear-progress.scss", + "list": "/demos/list.scss", + "menu": "/demos/menu.scss", + "radio": "/demos/radio.scss", + "ripple": "/demos/ripple.scss", + "select": "/demos/select.scss", + "slider": "/demos/slider.scss", + "snackbar": "/demos/snackbar.scss", + "switch": "/demos/switch.scss", + "tabs": "/demos/tabs.scss", + "text-field": "/demos/text-field.scss", + "theme/theme-baseline": "/demos/theme/theme-baseline.scss", + "theme/theme-black": "/demos/theme/theme-black.scss", + "theme/theme-dark": "/demos/theme/theme-dark.scss", + "theme/theme-white": "/demos/theme/theme-white.scss", + "theme/theme-yellow": "/demos/theme/theme-yellow.scss", + "toolbar/toolbar": "/demos/toolbar/toolbar.scss", + "typography": "/demos/typography.scss" + }, + "output": { + "path": "/build", + "publicPath": "/assets/", + "filename": "[name].css.js" + }, + "devtool": false, + "module": { + "rules": [ + { + "test": {}, + "use": [ + { + "loader": "/node_modules/extract-text-webpack-plugin/dist/loader.js", + "options": { + "omit": 1, + "remove": true + } + }, + { + "loader": "style-loader" + }, + { + "loader": "css-loader", + "options": { + "sourceMap": false + } + }, + { + "loader": "postcss-loader", + "options": { + "sourceMap": false + } + }, + { + "loader": "sass-loader", + "options": { + "sourceMap": false, + "includePaths": [ + "/packages/material-components-web/node_modules", + "/packages/mdc-button/node_modules", + "/packages/mdc-card/node_modules", + "/packages/mdc-checkbox/node_modules", + "/packages/mdc-chips/node_modules", + "/packages/mdc-dialog/node_modules", + "/packages/mdc-drag-drop/node_modules", + "/packages/mdc-drawer/node_modules", + "/packages/mdc-elevation/node_modules", + "/packages/mdc-fab/node_modules", + "/packages/mdc-form-field/node_modules", + "/packages/mdc-grid-list/node_modules", + "/packages/mdc-icon-toggle/node_modules", + "/packages/mdc-line-ripple/node_modules", + "/packages/mdc-linear-progress/node_modules", + "/packages/mdc-list/node_modules", + "/packages/mdc-menu/node_modules", + "/packages/mdc-radio/node_modules", + "/packages/mdc-ripple/node_modules", + "/packages/mdc-select/node_modules", + "/packages/mdc-selection-control/node_modules", + "/packages/mdc-slider/node_modules", + "/packages/mdc-snackbar/node_modules", + "/packages/mdc-switch/node_modules", + "/packages/mdc-tabs/node_modules", + "/packages/mdc-textfield/node_modules", + "/packages/mdc-toolbar/node_modules" + ] + } + } + ] + } + ] + }, + "plugins": [ + { + "filename": "[name].css", + "id": 3, + "options": {} + }, + { + "options": { + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/", + "raw": true, + "entryOnly": true + }, + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/" + }, + {} + ] + }, + { + "name": "demo-js", + "entry": { + "common": [ + "/demos/common.js" + ], + "theme/index": [ + "/demos/theme/index.js" + ] + }, + "output": { + "path": "/build", + "publicPath": "/assets/", + "filename": "[name].js", + "libraryTarget": "umd", + "library": [ + "demo", + "[name]" + ] + }, + "devtool": "source-map", + "module": { + "rules": [ + { + "test": {}, + "exclude": {}, + "loader": "babel-loader", + "options": { + "cacheDirectory": true + } + } + ] + }, + "plugins": [ + { + "options": { + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/", + "raw": true, + "entryOnly": true + }, + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/" + } + ] + } +] \ No newline at end of file diff --git a/test/build/goldens/build-config-no-env.golden.json b/test/build/goldens/build-config-no-env.golden.json new file mode 100644 index 00000000000..325085adc38 --- /dev/null +++ b/test/build/goldens/build-config-no-env.golden.json @@ -0,0 +1,263 @@ +[ + { + "name": "js-all", + "entry": "/packages/material-components-web/index.js", + "output": { + "path": "/build", + "publicPath": "/assets/", + "filename": "material-components-web.js", + "libraryTarget": "umd", + "library": "mdc" + }, + "devServer": { + "disableHostCheck": true + }, + "devtool": "source-map", + "module": { + "rules": [ + { + "test": {}, + "exclude": {}, + "loader": "babel-loader", + "options": { + "cacheDirectory": true + } + } + ] + }, + "plugins": [ + { + "options": { + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/", + "raw": true, + "entryOnly": true + }, + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/" + } + ] + }, + { + "name": "js-components", + "entry": { + "animation": [ + "/packages/mdc-animation/index.js" + ], + "autoInit": [ + "/packages/mdc-auto-init/index.js" + ], + "base": [ + "/packages/mdc-base/index.js" + ], + "lineRipple": [ + "/packages/mdc-line-ripple/index.js" + ], + "checkbox": [ + "/packages/mdc-checkbox/index.js" + ], + "chips": [ + "/packages/mdc-chips/index.js" + ], + "dialog": [ + "/packages/mdc-dialog/index.js" + ], + "drawer": [ + "/packages/mdc-drawer/index.js" + ], + "formField": [ + "/packages/mdc-form-field/index.js" + ], + "gridList": [ + "/packages/mdc-grid-list/index.js" + ], + "iconToggle": [ + "/packages/mdc-icon-toggle/index.js" + ], + "linearProgress": [ + "/packages/mdc-linear-progress/index.js" + ], + "menu": [ + "/packages/mdc-menu/index.js" + ], + "radio": [ + "/packages/mdc-radio/index.js" + ], + "ripple": [ + "/packages/mdc-ripple/index.js" + ], + "select": [ + "/packages/mdc-select/index.js" + ], + "selectionControl": [ + "/packages/mdc-selection-control/index.js" + ], + "slider": [ + "/packages/mdc-slider/index.js" + ], + "snackbar": [ + "/packages/mdc-snackbar/index.js" + ], + "tabs": [ + "/packages/mdc-tabs/index.js" + ], + "textfield": [ + "/packages/mdc-textfield/index.js" + ], + "toolbar": [ + "/packages/mdc-toolbar/index.js" + ] + }, + "output": { + "path": "/build", + "publicPath": "/assets/", + "filename": "mdc.[name].js", + "libraryTarget": "umd", + "library": [ + "mdc", + "[name]" + ] + }, + "devtool": "source-map", + "module": { + "rules": [ + { + "test": {}, + "exclude": {}, + "loader": "babel-loader", + "options": { + "cacheDirectory": true + } + } + ] + }, + "plugins": [ + { + "options": { + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/", + "raw": true, + "entryOnly": true + }, + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/" + } + ] + }, + { + "name": "css", + "entry": { + "material-components-web": "/packages/material-components-web/material-components-web.scss", + "mdc.button": "/packages/mdc-button/mdc-button.scss", + "mdc.line-ripple": "/packages/mdc-line-ripple/mdc-line-ripple.scss", + "mdc.card": "/packages/mdc-card/mdc-card.scss", + "mdc.checkbox": "/packages/mdc-checkbox/mdc-checkbox.scss", + "mdc.chips": "/packages/mdc-chips/mdc-chips.scss", + "mdc.dialog": "/packages/mdc-dialog/mdc-dialog.scss", + "mdc.drawer": "/packages/mdc-drawer/mdc-drawer.scss", + "mdc.elevation": "/packages/mdc-elevation/mdc-elevation.scss", + "mdc.fab": "/packages/mdc-fab/mdc-fab.scss", + "mdc.form-field": "/packages/mdc-form-field/mdc-form-field.scss", + "mdc.grid-list": "/packages/mdc-grid-list/mdc-grid-list.scss", + "mdc.icon-toggle": "/packages/mdc-icon-toggle/mdc-icon-toggle.scss", + "mdc.layout-grid": "/packages/mdc-layout-grid/mdc-layout-grid.scss", + "mdc.linear-progress": "/packages/mdc-linear-progress/mdc-linear-progress.scss", + "mdc.list": "/packages/mdc-list/mdc-list.scss", + "mdc.menu": "/packages/mdc-menu/mdc-menu.scss", + "mdc.radio": "/packages/mdc-radio/mdc-radio.scss", + "mdc.ripple": "/packages/mdc-ripple/mdc-ripple.scss", + "mdc.select": "/packages/mdc-select/mdc-select.scss", + "mdc.slider": "/packages/mdc-slider/mdc-slider.scss", + "mdc.snackbar": "/packages/mdc-snackbar/mdc-snackbar.scss", + "mdc.switch": "/packages/mdc-switch/mdc-switch.scss", + "mdc.tabs": "/packages/mdc-tabs/mdc-tabs.scss", + "mdc.textfield": "/packages/mdc-textfield/mdc-text-field.scss", + "mdc.theme": "/packages/mdc-theme/mdc-theme.scss", + "mdc.toolbar": "/packages/mdc-toolbar/mdc-toolbar.scss", + "mdc.typography": "/packages/mdc-typography/mdc-typography.scss" + }, + "output": { + "path": "/build", + "publicPath": "/assets/", + "filename": "[name].css.js-entry" + }, + "devtool": false, + "module": { + "rules": [ + { + "test": {}, + "use": [ + { + "loader": "/node_modules/extract-text-webpack-plugin/dist/loader.js", + "options": { + "omit": 1, + "remove": true + } + }, + { + "loader": "style-loader" + }, + { + "loader": "css-loader", + "options": { + "sourceMap": false + } + }, + { + "loader": "postcss-loader", + "options": { + "sourceMap": false + } + }, + { + "loader": "sass-loader", + "options": { + "sourceMap": false, + "includePaths": [ + "/packages/material-components-web/node_modules", + "/packages/mdc-button/node_modules", + "/packages/mdc-card/node_modules", + "/packages/mdc-checkbox/node_modules", + "/packages/mdc-chips/node_modules", + "/packages/mdc-dialog/node_modules", + "/packages/mdc-drag-drop/node_modules", + "/packages/mdc-drawer/node_modules", + "/packages/mdc-elevation/node_modules", + "/packages/mdc-fab/node_modules", + "/packages/mdc-form-field/node_modules", + "/packages/mdc-grid-list/node_modules", + "/packages/mdc-icon-toggle/node_modules", + "/packages/mdc-line-ripple/node_modules", + "/packages/mdc-linear-progress/node_modules", + "/packages/mdc-list/node_modules", + "/packages/mdc-menu/node_modules", + "/packages/mdc-radio/node_modules", + "/packages/mdc-ripple/node_modules", + "/packages/mdc-select/node_modules", + "/packages/mdc-selection-control/node_modules", + "/packages/mdc-slider/node_modules", + "/packages/mdc-snackbar/node_modules", + "/packages/mdc-switch/node_modules", + "/packages/mdc-tabs/node_modules", + "/packages/mdc-textfield/node_modules", + "/packages/mdc-toolbar/node_modules" + ] + } + } + ] + } + ] + }, + "plugins": [ + { + "filename": "[name].css", + "id": 1, + "options": {} + }, + { + "options": { + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/", + "raw": true, + "entryOnly": true + }, + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/" + } + ] + } +] \ No newline at end of file diff --git a/test/build/goldens/build-config-prod-env.golden.json b/test/build/goldens/build-config-prod-env.golden.json new file mode 100644 index 00000000000..67f9700ecf2 --- /dev/null +++ b/test/build/goldens/build-config-prod-env.golden.json @@ -0,0 +1,263 @@ +[ + { + "name": "js-all", + "entry": "/packages/material-components-web/index.js", + "output": { + "path": "/build", + "publicPath": "/assets/", + "filename": "material-components-web.min.js", + "libraryTarget": "umd", + "library": "mdc" + }, + "devServer": { + "disableHostCheck": true + }, + "devtool": "source-map", + "module": { + "rules": [ + { + "test": {}, + "exclude": {}, + "loader": "babel-loader", + "options": { + "cacheDirectory": true + } + } + ] + }, + "plugins": [ + { + "options": { + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/", + "raw": true, + "entryOnly": true + }, + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/" + } + ] + }, + { + "name": "js-components", + "entry": { + "animation": [ + "/packages/mdc-animation/index.js" + ], + "autoInit": [ + "/packages/mdc-auto-init/index.js" + ], + "base": [ + "/packages/mdc-base/index.js" + ], + "lineRipple": [ + "/packages/mdc-line-ripple/index.js" + ], + "checkbox": [ + "/packages/mdc-checkbox/index.js" + ], + "chips": [ + "/packages/mdc-chips/index.js" + ], + "dialog": [ + "/packages/mdc-dialog/index.js" + ], + "drawer": [ + "/packages/mdc-drawer/index.js" + ], + "formField": [ + "/packages/mdc-form-field/index.js" + ], + "gridList": [ + "/packages/mdc-grid-list/index.js" + ], + "iconToggle": [ + "/packages/mdc-icon-toggle/index.js" + ], + "linearProgress": [ + "/packages/mdc-linear-progress/index.js" + ], + "menu": [ + "/packages/mdc-menu/index.js" + ], + "radio": [ + "/packages/mdc-radio/index.js" + ], + "ripple": [ + "/packages/mdc-ripple/index.js" + ], + "select": [ + "/packages/mdc-select/index.js" + ], + "selectionControl": [ + "/packages/mdc-selection-control/index.js" + ], + "slider": [ + "/packages/mdc-slider/index.js" + ], + "snackbar": [ + "/packages/mdc-snackbar/index.js" + ], + "tabs": [ + "/packages/mdc-tabs/index.js" + ], + "textfield": [ + "/packages/mdc-textfield/index.js" + ], + "toolbar": [ + "/packages/mdc-toolbar/index.js" + ] + }, + "output": { + "path": "/build", + "publicPath": "/assets/", + "filename": "mdc.[name].min.js", + "libraryTarget": "umd", + "library": [ + "mdc", + "[name]" + ] + }, + "devtool": "source-map", + "module": { + "rules": [ + { + "test": {}, + "exclude": {}, + "loader": "babel-loader", + "options": { + "cacheDirectory": true + } + } + ] + }, + "plugins": [ + { + "options": { + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/", + "raw": true, + "entryOnly": true + }, + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/" + } + ] + }, + { + "name": "css", + "entry": { + "material-components-web": "/packages/material-components-web/material-components-web.scss", + "mdc.button": "/packages/mdc-button/mdc-button.scss", + "mdc.line-ripple": "/packages/mdc-line-ripple/mdc-line-ripple.scss", + "mdc.card": "/packages/mdc-card/mdc-card.scss", + "mdc.checkbox": "/packages/mdc-checkbox/mdc-checkbox.scss", + "mdc.chips": "/packages/mdc-chips/mdc-chips.scss", + "mdc.dialog": "/packages/mdc-dialog/mdc-dialog.scss", + "mdc.drawer": "/packages/mdc-drawer/mdc-drawer.scss", + "mdc.elevation": "/packages/mdc-elevation/mdc-elevation.scss", + "mdc.fab": "/packages/mdc-fab/mdc-fab.scss", + "mdc.form-field": "/packages/mdc-form-field/mdc-form-field.scss", + "mdc.grid-list": "/packages/mdc-grid-list/mdc-grid-list.scss", + "mdc.icon-toggle": "/packages/mdc-icon-toggle/mdc-icon-toggle.scss", + "mdc.layout-grid": "/packages/mdc-layout-grid/mdc-layout-grid.scss", + "mdc.linear-progress": "/packages/mdc-linear-progress/mdc-linear-progress.scss", + "mdc.list": "/packages/mdc-list/mdc-list.scss", + "mdc.menu": "/packages/mdc-menu/mdc-menu.scss", + "mdc.radio": "/packages/mdc-radio/mdc-radio.scss", + "mdc.ripple": "/packages/mdc-ripple/mdc-ripple.scss", + "mdc.select": "/packages/mdc-select/mdc-select.scss", + "mdc.slider": "/packages/mdc-slider/mdc-slider.scss", + "mdc.snackbar": "/packages/mdc-snackbar/mdc-snackbar.scss", + "mdc.switch": "/packages/mdc-switch/mdc-switch.scss", + "mdc.tabs": "/packages/mdc-tabs/mdc-tabs.scss", + "mdc.textfield": "/packages/mdc-textfield/mdc-text-field.scss", + "mdc.theme": "/packages/mdc-theme/mdc-theme.scss", + "mdc.toolbar": "/packages/mdc-toolbar/mdc-toolbar.scss", + "mdc.typography": "/packages/mdc-typography/mdc-typography.scss" + }, + "output": { + "path": "/build", + "publicPath": "/assets/", + "filename": "[name].min.css.js-entry" + }, + "devtool": false, + "module": { + "rules": [ + { + "test": {}, + "use": [ + { + "loader": "/node_modules/extract-text-webpack-plugin/dist/loader.js", + "options": { + "omit": 1, + "remove": true + } + }, + { + "loader": "style-loader" + }, + { + "loader": "css-loader", + "options": { + "sourceMap": false + } + }, + { + "loader": "postcss-loader", + "options": { + "sourceMap": false + } + }, + { + "loader": "sass-loader", + "options": { + "sourceMap": false, + "includePaths": [ + "/packages/material-components-web/node_modules", + "/packages/mdc-button/node_modules", + "/packages/mdc-card/node_modules", + "/packages/mdc-checkbox/node_modules", + "/packages/mdc-chips/node_modules", + "/packages/mdc-dialog/node_modules", + "/packages/mdc-drag-drop/node_modules", + "/packages/mdc-drawer/node_modules", + "/packages/mdc-elevation/node_modules", + "/packages/mdc-fab/node_modules", + "/packages/mdc-form-field/node_modules", + "/packages/mdc-grid-list/node_modules", + "/packages/mdc-icon-toggle/node_modules", + "/packages/mdc-line-ripple/node_modules", + "/packages/mdc-linear-progress/node_modules", + "/packages/mdc-list/node_modules", + "/packages/mdc-menu/node_modules", + "/packages/mdc-radio/node_modules", + "/packages/mdc-ripple/node_modules", + "/packages/mdc-select/node_modules", + "/packages/mdc-selection-control/node_modules", + "/packages/mdc-slider/node_modules", + "/packages/mdc-snackbar/node_modules", + "/packages/mdc-switch/node_modules", + "/packages/mdc-tabs/node_modules", + "/packages/mdc-textfield/node_modules", + "/packages/mdc-toolbar/node_modules" + ] + } + } + ] + } + ] + }, + "plugins": [ + { + "filename": "[name].min.css", + "id": 2, + "options": {} + }, + { + "options": { + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/", + "raw": true, + "entryOnly": true + }, + "banner": "/*!\n Material Components for the web\n Copyright (c) 2018 Google Inc.\n License: Apache-2.0\n*/" + } + ] + } +] \ No newline at end of file diff --git a/test/build/index.js b/test/build/index.js new file mode 100644 index 00000000000..a8a80cecb10 --- /dev/null +++ b/test/build/index.js @@ -0,0 +1,20 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const glob = require('glob'); +const path = require('path'); + +glob.sync(path.join(__dirname, '**/*.test.js')).forEach((testFilePath) => require(testFilePath)); diff --git a/test/build/mock-env.js b/test/build/mock-env.js new file mode 100644 index 00000000000..1d1d757a704 --- /dev/null +++ b/test/build/mock-env.js @@ -0,0 +1,36 @@ +/** + * Copyright 2018 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Temporarily overwrites environment variables and restores their original values. + */ +module.exports = class { + constructor() { + this.saved_ = new Map(); + } + + mock(key, value) { + this.saved_[key] = process.env[key]; + process.env[key] = value; + } + + restoreAll() { + this.saved_.forEach((value, key) => { + process.env[key] = value; + }); + this.saved_.clear(); + } +}; diff --git a/test/build/webpack-config-loader.js b/test/build/webpack-config-loader.js new file mode 100644 index 00000000000..a3ffe0c2df9 --- /dev/null +++ b/test/build/webpack-config-loader.js @@ -0,0 +1,97 @@ +/** + * Copyright 2018 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Executes a webpack config file and returns its module exports as a string. + */ + +const fsx = require('fs-extra'); +const jsonStringifySafe = require('json-stringify-safe'); +const path = require('path'); + +const MockEnv = require('./mock-env'); + +const PROJECT_ROOT_ABSOLUTE_PATH = path.resolve(__dirname, '../../'); + +module.exports = class { + /** + * Simulates the `npm run build`, `npm run build:min`, and `npm run dev` commands, returning both the + * actual generated config object exported by the webpack config file, as well as the expected (golden) config object. + * @param {string} configPath Path to a webpack.config.js file + * @param {string} goldenPath Path to a JSON file containing the expected module exports + * @param {string} npmCmd Command passed to `npm run` - e.g., `dev`, `build:min`, `test:buildconfig` + * @param {string=} mdcEnv One of ["", "development", "production"] + * @param {boolean=} bootstrapGolden If `true`, the actual config exports are written to the golden file + * @return {{generatedWebpackConfig: string, expectedWebpackConfig: string}} Stringified JSON representations of both + * the actual and expected webpack config exports. + */ + setupTest({ + configPath, + goldenPath, + npmCmd, + mdcEnv = '', + bootstrapGolden = false, + }) { + const env = new MockEnv(); + + env.mock('npm_lifecycle_event', npmCmd); + env.mock('MDC_ENV', mdcEnv); + + const fsTextOpts = {encoding: 'utf8'}; + const generatedWebpackConfig = redactProjectRootPath(serialize(requireFresh(configPath))); + + if (bootstrapGolden) { + fsx.writeFileSync(goldenPath, generatedWebpackConfig, fsTextOpts); + } + + const expectedWebpackConfig = redactProjectRootPath(fsx.readFileSync(goldenPath, fsTextOpts)); + + env.restoreAll(); + + return { + generatedWebpackConfig, + expectedWebpackConfig, + }; + } +}; + +/** + * Same as `require`, but bypasses the module cache, forcing Node to reevaluate the requested module file. + * @param {string} module + * @return {*} + */ +function requireFresh(module) { + delete require.cache[require.resolve(module)]; + return require(module); +} + +/** + * Stringifies the given JS object to a serializable JSON format. Cyclical references are omitted. + * @param {!Object|!Array} obj + * @return {string} + */ +function serialize(obj) { + return jsonStringifySafe(obj, null, 2); +} + +/** + * Removes all occurrences of the MDC Web repo's root directory path from the given string. + * @param {string} str + * @return {string} + */ +function redactProjectRootPath(str) { + return str.split(PROJECT_ROOT_ABSOLUTE_PATH).join(''); +} diff --git a/test/build/webpack-module-exports.test.js b/test/build/webpack-module-exports.test.js new file mode 100644 index 00000000000..553a215f5a3 --- /dev/null +++ b/test/build/webpack-module-exports.test.js @@ -0,0 +1,64 @@ +/** + * Copyright 2018 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Ensures that our webpack config file's exports don't change unexpectedly after refactoring. + * TODO(acdvorak): Delete this test file once we're done refactoring webpack.config.js. + */ + +const assert = require('chai').assert; +const path = require('path'); + +const WebpackConfigLoader = require('./webpack-config-loader'); +const webpackConfigLoader = new WebpackConfigLoader(); + +describe('webpack.config.js', function() { + describe('MDC_ENV=""', function() { + it('module exports should match build-config-no-env.golden.json', function() { + const {generatedWebpackConfig, expectedWebpackConfig} = webpackConfigLoader.setupTest({ + configPath: path.join(__dirname, '../../webpack.config.js'), + goldenPath: path.join(__dirname, './goldens/build-config-no-env.golden.json'), + mdcEnv: '', + bootstrapGolden: true, + }); + assert.equal(generatedWebpackConfig, expectedWebpackConfig); + }); + }); + + describe('MDC_ENV="production"', function() { + it('module exports should match build-config-prod-env.golden.json', function() { + const {generatedWebpackConfig, expectedWebpackConfig} = webpackConfigLoader.setupTest({ + configPath: path.join(__dirname, '../../webpack.config.js'), + goldenPath: path.join(__dirname, './goldens/build-config-prod-env.golden.json'), + mdcEnv: 'production', + bootstrapGolden: true, + }); + assert.equal(generatedWebpackConfig, expectedWebpackConfig); + }); + }); + + describe('MDC_ENV="development"', function() { + it('module exports should match build-config-dev-env.golden.json', function() { + const {generatedWebpackConfig, expectedWebpackConfig} = webpackConfigLoader.setupTest({ + configPath: path.join(__dirname, '../../webpack.config.js'), + goldenPath: path.join(__dirname, './goldens/build-config-dev-env.golden.json'), + mdcEnv: 'development', + bootstrapGolden: true, + }); + assert.equal(generatedWebpackConfig, expectedWebpackConfig); + }); + }); +});