├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .release.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build
├── build.js
├── utils
│ ├── index.js
│ ├── log.js
│ └── write.js
├── webpack.config.base.js
├── webpack.config.browser.js
├── webpack.config.dev.js
└── webpack.config.dll.js
├── circle.yml
├── package.json
├── src
├── Button.vue
├── Card
│ ├── Card.vue
│ ├── CardActions.js
│ ├── CardHorizontalBlock.js
│ ├── CardMedia.js
│ ├── CardPrimary.js
│ ├── CardSubtitle.js
│ ├── CardSupportingText.js
│ └── CardTitle.js
├── Checkbox.vue
├── Dialog
│ ├── Dialog.vue
│ └── DialogHeaderTitle.js
├── Drawer
│ ├── Drawer.vue
│ ├── DrawerHeader.vue
│ ├── DrawerNav.js
│ └── DrawerNavItem.vue
├── Fab.vue
├── List
│ ├── List.vue
│ ├── ListDivider.vue
│ └── ListItem.vue
├── Toolbar
│ ├── Toolbar.vue
│ ├── ToolbarRow.js
│ └── ToolbarSection.js
├── index.js
├── ripple.js
└── utils
│ └── index.js
├── test
├── .eslintrc
├── helpers
│ ├── Test.vue
│ ├── index.js
│ ├── style.css
│ ├── utils.js
│ └── wait-for-update.js
├── index.js
├── karma.conf.js
├── specs
│ ├── Button.spec.js
│ ├── Card.spec.js
│ ├── Checkbox.spec.js
│ ├── Dialog.spec.js
│ ├── Drawer.spec.js
│ ├── Fab.spec.js
│ ├── List.spec.js
│ ├── Toolbar.spec.js
│ ├── VueMdc.spec.js
│ └── ripple.spec.js
└── visual.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/*.js
2 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | globals: {
4 | HTMLElement: true,
5 | },
6 | extends: 'posva',
7 | // add your custom rules here
8 | 'rules': {
9 | // allow async-await
10 | 'generator-star-spacing': 0,
11 | // allow debugger during development
12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
13 | 'comma-dangle': ['error', 'always-multiline'],
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log
4 | test/coverage
5 | dist
6 | yarn-error.log
7 | reports
8 |
--------------------------------------------------------------------------------
/.release.json:
--------------------------------------------------------------------------------
1 | {
2 | "src": {
3 | "tagName": "v%s",
4 | "commitMessage": "🔖 %s"
5 | },
6 | "github": {
7 | "release": true,
8 | "releaseName": "Release %s",
9 | "tokenRef": "GITHUB_TOKEN"
10 | },
11 | "npm": {
12 | "publish": true
13 | },
14 | "changelogCommand": "git log --pretty=format:'- %s (%h)' [REV_RANGE]"
15 | }
16 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and will be fully **credited**.
4 |
5 | We accept contributions via Pull Requests on [Github](https://github.com/posva/vue-mdc).
6 |
7 |
8 | ## Pull Requests
9 |
10 | - **Keep the same style** - eslint will automatically be ran before committing
11 |
12 | - **Tip** to pass lint tests easier use the `npm run lint:fix` command
13 |
14 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
15 |
16 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
17 |
18 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
19 |
20 | - **Create feature branches** - Don't ask us to pull from your master branch.
21 |
22 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
23 |
24 | - **Send coherent history** - Make sure your commits message means something
25 |
26 |
27 | ## Running Tests
28 |
29 | Launch visual tests and watch the components at the same time
30 |
31 | ``` bash
32 | $ npm run dev
33 | ```
34 |
35 |
36 | **Happy coding**!
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Eduardo San Martin Morote
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **If you're looking for vue-mdl, check the [mdl branch](https://github.com/posva/vue-mdc/tree/mdl). To learn about what's happening, check [#139](https://github.com/posva/vue-mdc/issues/139)**
2 |
3 | ---
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ---
33 |
34 | # Vue Material Components web
35 |
36 | > Material Components Web for Vue.js
37 |
38 | This is the adaptation of [Material Components web](https://github.com/material-components/material-components-web) for Vue.js. Keeping them fast, easy to use and, SSR ready.
39 |
40 | The docs will be the demo, until then you can [check the test folder](test/specs) and the [Material Components web demo](http://material-components-web.appspot.com/)
41 |
42 | ## Installation
43 |
44 | ```bash
45 | $ npm install --save vue-mdc
46 | # or
47 | $ yarn add vue-mdc
48 | ```
49 |
50 | ## Usage
51 |
52 | ### Bundler (Webpack, Rollup)
53 |
54 | ```js
55 | import Vue from 'vue'
56 | import VueMdc from 'vue-mdc'
57 | // You need a specific loader for CSS files like https://github.com/webpack/css-loader
58 | import 'vue-mdc/dist/vue-mdc.css'
59 |
60 | Vue.use(VueMdc)
61 | ```
62 |
63 | ### Browser
64 |
65 | ```html
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | ```
75 |
76 | ### Docs
77 |
78 | WIP 😁
79 |
80 | ## Development
81 |
82 | ### Launch visual tests
83 |
84 | ```bash
85 | npm run dev
86 | ```
87 |
88 | ### Launch Karma with coverage
89 |
90 | ```bash
91 | npm run dev:coverage
92 | ```
93 |
94 | ### Build
95 |
96 | Bundle the js and css of to the `dist` folder:
97 |
98 | ```bash
99 | npm run build
100 | ```
101 |
102 |
103 | ## Publishing
104 |
105 | The `prepublish` hook will ensure dist files are created before publishing. This
106 | way you don't need to commit them in your repository.
107 |
108 | ```bash
109 | # Bump the version first
110 | # It'll also commit it and create a tag
111 | npm version
112 | # Push the bumped package and tags
113 | git push --follow-tags
114 | # Ship it 🚀
115 | npm publish
116 | ```
117 |
118 | ## License
119 |
120 | [MIT](http://opensource.org/licenses/MIT)
121 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | const mkdirp = require('mkdirp')
2 | const rollup = require('rollup').rollup
3 | const vue = require('rollup-plugin-vue')
4 | const buble = require('rollup-plugin-buble')
5 | const replace = require('rollup-plugin-replace')
6 | const cjs = require('rollup-plugin-commonjs')
7 | const node = require('rollup-plugin-node-resolve')
8 | const scss = require('rollup-plugin-scss')
9 | const uglify = require('uglify-js')
10 |
11 | // Make sure dist dir exists
12 | mkdirp('dist')
13 |
14 | const {
15 | logError,
16 | write,
17 | banner,
18 | name,
19 | moduleName,
20 | version,
21 | } = require('./utils')
22 |
23 | function rollupBundle ({ env, entry }) {
24 | return rollup({
25 | input: entry || 'src/index.js',
26 | plugins: [
27 | scss({ output: false }),
28 | node({
29 | extensions: ['.js', '.jsx', '.vue'],
30 | }),
31 | cjs(),
32 | vue({ compileTemplate: true, css: false }),
33 | replace(Object.assign({
34 | __VERSION__: version,
35 | }, env)),
36 | buble({
37 | objectAssign: 'Object.assign',
38 | }),
39 | ],
40 | })
41 | }
42 |
43 | const bundleOptions = {
44 | banner,
45 | exports: 'named',
46 | format: 'umd',
47 | name: moduleName,
48 | }
49 |
50 | function createBundle ({ name, env, entry, format }) {
51 | return rollupBundle({
52 | env,
53 | entry,
54 | }).then(function (bundle) {
55 | const options = Object.assign({}, bundleOptions)
56 | if (format) options.format = format
57 | return bundle.generate(options).then(({ code }) => {
58 | if (/min$/.test(name)) {
59 | const minified = uglify.minify(code, {
60 | output: {
61 | preamble: banner,
62 | ascii_only: true, // eslint-disable-line camelcase
63 | },
64 | }).code
65 | return write(`dist/${name}.js`, minified)
66 | } else {
67 | return write(`dist/${name}.js`, code)
68 | }
69 | })
70 | }).catch(logError)
71 | }
72 |
73 | // Browser bundle (can be used with script)
74 | createBundle({
75 | name: `${name}`,
76 | env: {
77 | 'process.env.NODE_ENV': '"development"',
78 | },
79 | })
80 |
81 | // Commonjs bundle (preserves process.env.NODE_ENV) so
82 | // the user can replace it in dev and prod mode
83 | createBundle({
84 | name: `${name}.esm`,
85 | env: {},
86 | format: 'es',
87 | })
88 |
89 | createBundle({
90 | name: `${name}.common`,
91 | env: {},
92 | format: 'cjs',
93 | })
94 |
95 | // Minified version for browser
96 | createBundle({
97 | name: `${name}.min`,
98 | env: {
99 | 'process.env.NODE_ENV': '"production"',
100 | },
101 | })
102 |
--------------------------------------------------------------------------------
/build/utils/index.js:
--------------------------------------------------------------------------------
1 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
2 | const { join } = require('path')
3 |
4 | const {
5 | red,
6 | logError,
7 | } = require('./log')
8 |
9 | const uppercamelcase = require('uppercamelcase')
10 |
11 | exports.write = require('./write')
12 |
13 | const {
14 | author,
15 | name,
16 | version,
17 | dllPlugin,
18 | } = require('../../package.json')
19 |
20 | const authorName = author.replace(/\s+<.*/, '')
21 | const minExt = process.env.NODE_ENV === 'production' ? '.min' : ''
22 |
23 | exports.author = authorName
24 | exports.version = version
25 | exports.dllName = dllPlugin.name
26 | exports.name = name
27 | exports.moduleName = uppercamelcase(name)
28 | exports.filename = name + minExt
29 | exports.banner = `/*!
30 | * ${name} v${version}
31 | * (c) ${new Date().getFullYear()} ${authorName}
32 | * Released under the MIT License.
33 | */
34 | `
35 |
36 | // log.js
37 | exports.red = red
38 | exports.logError = logError
39 |
40 | // It'd be better to add a sass property to the vue-loader options
41 | // but it simply don't work
42 | const sassOptions = {
43 | includePaths: [
44 | join(__dirname, '../../node_modules'),
45 | ],
46 | }
47 |
48 | // don't extract css in test mode
49 | const nullLoader = /(common|esm)/.test(process.env.NODE_ENV) ? 'null-loader!' : ''
50 | exports.vueLoaders =
51 | process.env.BABEL_ENV === 'test' ? {
52 | scss: `css-loader!sass-loader?${JSON.stringify(sassOptions)}`,
53 | } : {
54 | scss: ExtractTextPlugin.extract(
55 | `${nullLoader}css-loader!sass-loader?${JSON.stringify(sassOptions)}`
56 | ),
57 | }
58 |
--------------------------------------------------------------------------------
/build/utils/log.js:
--------------------------------------------------------------------------------
1 | function logError (e) {
2 | console.log(e)
3 | }
4 |
5 | function blue (str) {
6 | return `\x1b[1m\x1b[34m${str}\x1b[39m\x1b[22m`
7 | }
8 |
9 | function green (str) {
10 | return `\x1b[1m\x1b[32m${str}\x1b[39m\x1b[22m`
11 | }
12 |
13 | function red (str) {
14 | return `\x1b[1m\x1b[31m${str}\x1b[39m\x1b[22m`
15 | }
16 |
17 | function yellow (str) {
18 | return `\x1b[1m\x1b[33m${str}\x1b[39m\x1b[22m`
19 | }
20 |
21 | module.exports = {
22 | blue,
23 | green,
24 | red,
25 | yellow,
26 | logError,
27 | }
28 |
--------------------------------------------------------------------------------
/build/utils/write.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | const { blue } = require('./log.js')
4 |
5 | function write (dest, code) {
6 | return new Promise(function (resolve, reject) {
7 | fs.writeFile(dest, code, function (err) {
8 | if (err) return reject(err)
9 | console.log(blue(dest) + ' ' + getSize(code))
10 | resolve(code)
11 | })
12 | })
13 | }
14 |
15 | function getSize (code) {
16 | return (code.length / 1024).toFixed(2) + 'kb'
17 | }
18 |
19 | module.exports = write
20 |
--------------------------------------------------------------------------------
/build/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
3 | const { resolve } = require('path')
4 |
5 | const {
6 | banner,
7 | filename,
8 | version,
9 | vueLoaders,
10 | } = require('./utils')
11 |
12 | const plugins = [
13 | new webpack.DefinePlugin({
14 | '__VERSION__': JSON.stringify(version),
15 | 'process.env.NODE_ENV': '"test"',
16 | }),
17 | new webpack.BannerPlugin({ banner, raw: true, entryOnly: true }),
18 | new ExtractTextPlugin({
19 | filename: `${filename}.css`,
20 | // Don't extract css in test mode
21 | disable: /^(common|test|esm)$/.test(process.env.NODE_ENV),
22 | }),
23 | ]
24 |
25 | module.exports = {
26 | output: {
27 | pathinfo: true,
28 | path: resolve(__dirname, '../dist'),
29 | filename: `${filename}.common.js`,
30 | },
31 | entry: './src/index.js',
32 | resolve: {
33 | extensions: ['.js', '.vue', '.jsx', 'css'],
34 | alias: {
35 | 'src': resolve(__dirname, '../src'),
36 | },
37 | },
38 | module: {
39 | rules: [
40 | {
41 | test: /.jsx?$/,
42 | use: 'babel-loader',
43 | include: [
44 | resolve(__dirname, '../node_modules/@material'),
45 | resolve(__dirname, '../src'),
46 | resolve(__dirname, '../test'),
47 | ],
48 | },
49 | {
50 | test: /\.vue$/,
51 | loader: 'vue-loader',
52 | options: {
53 | loaders: vueLoaders,
54 | },
55 | },
56 | ],
57 | },
58 | plugins,
59 | }
60 |
--------------------------------------------------------------------------------
/build/webpack.config.browser.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge')
2 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
3 | const base = require('./webpack.config.base')
4 | const { resolve } = require('path')
5 | const {
6 | filename,
7 | moduleName,
8 | vueLoaders,
9 | } = require('./utils')
10 |
11 | module.exports = merge(base, {
12 | output: {
13 | pathinfo: false,
14 | filename: `${filename}.js`,
15 | library: moduleName,
16 | libraryTarget: 'umd',
17 | },
18 | module: {
19 | rules: [
20 | {
21 | test: /.scss$/,
22 | use: vueLoaders.scss,
23 | include: [
24 | resolve(__dirname, '../node_modules/@material'),
25 | resolve(__dirname, '../src'),
26 | ],
27 | },
28 | ],
29 | },
30 | plugins: [
31 | new BundleAnalyzerPlugin({
32 | analyzerMode: 'static',
33 | openAnalyzer: false,
34 | reportFilename: resolve(__dirname, `../reports/${process.env.NODE_ENV}.html`),
35 | }),
36 | ],
37 | })
38 |
--------------------------------------------------------------------------------
/build/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const merge = require('webpack-merge')
3 | const HtmlWebpackPlugin = require('html-webpack-plugin')
4 | const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
5 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
6 | const DashboardPlugin = require('webpack-dashboard/plugin')
7 | const base = require('./webpack.config.base')
8 | const { resolve, join } = require('path')
9 | const { existsSync } = require('fs')
10 | const {
11 | dllName,
12 | logError,
13 | red,
14 | vueLoaders,
15 | } = require('./utils')
16 |
17 | const rootDir = resolve(__dirname, '../test')
18 | const buildPath = resolve(rootDir, 'dist')
19 |
20 | if (!existsSync(join(buildPath, dllName) + '.dll.js')) {
21 | logError(red('The DLL manifest is missing. Please run `npm run build:dll` (Quit this with `q`)'))
22 | process.exit(0)
23 | }
24 |
25 | const dllManifest = require(
26 | join(buildPath, dllName) + '.json'
27 | )
28 |
29 | module.exports = merge(base, {
30 | entry: {
31 | tests: resolve(rootDir, 'visual.js'),
32 | },
33 | output: {
34 | path: buildPath,
35 | filename: '[name].js',
36 | chunkFilename: '[id].js',
37 | },
38 | module: {
39 | rules: [
40 | {
41 | test: /.scss$/,
42 | loader: vueLoaders.scss,
43 | include: [
44 | resolve(__dirname, '../node_modules/@material'),
45 | resolve(__dirname, '../src'),
46 | ],
47 | },
48 | ],
49 | },
50 | plugins: [
51 | new webpack.DllReferencePlugin({
52 | context: join(__dirname, '..'),
53 | manifest: dllManifest,
54 | }),
55 | new HtmlWebpackPlugin({
56 | chunkSortMode: 'dependency',
57 | }),
58 | new AddAssetHtmlPlugin({
59 | filepath: require.resolve(
60 | join(buildPath, dllName) + '.dll.js'
61 | ),
62 | }),
63 | new webpack.optimize.CommonsChunkPlugin({
64 | name: 'vendor',
65 | minChunks (module, count) {
66 | return (
67 | module.resource &&
68 | /\.js$/.test(module.resource) &&
69 | module.resource.indexOf(join(__dirname, '../node_modules/')) === 0
70 | )
71 | },
72 | }),
73 | new webpack.optimize.CommonsChunkPlugin({
74 | name: 'manifest',
75 | chunks: ['vendor'],
76 | }),
77 | new DashboardPlugin(),
78 | new BundleAnalyzerPlugin({
79 | analyzerMode: 'static',
80 | openAnalyzer: false,
81 | reportFilename: resolve(__dirname, `../reports/${process.env.NODE_ENV}.html`),
82 | }),
83 | ],
84 | devtool: '#eval-source-map',
85 | devServer: {
86 | inline: true,
87 | stats: {
88 | colors: true,
89 | chunks: false,
90 | cached: false,
91 | },
92 | contentBase: buildPath,
93 | },
94 | performance: {
95 | hints: false,
96 | },
97 | })
98 |
--------------------------------------------------------------------------------
/build/webpack.config.dll.js:
--------------------------------------------------------------------------------
1 | const { resolve, join } = require('path')
2 | const webpack = require('webpack')
3 | const pkg = require('../package.json')
4 |
5 | const rootDir = resolve(__dirname, '../test')
6 | const buildPath = resolve(rootDir, 'dist')
7 |
8 | const entry = {}
9 | entry[pkg.dllPlugin.name] = pkg.dllPlugin.include
10 |
11 | module.exports = {
12 | devtool: '#source-map',
13 | entry,
14 | output: {
15 | path: buildPath,
16 | filename: '[name].dll.js',
17 | library: '[name]',
18 | },
19 | plugins: [
20 | new webpack.DllPlugin({
21 | name: '[name]',
22 | path: join(buildPath, '[name].json'),
23 | }),
24 | ],
25 | performance: {
26 | hints: false,
27 | },
28 | }
29 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | general:
2 | branches:
3 | ignore:
4 | - gh-pages
5 | machine:
6 | node:
7 | version: 6
8 | environment:
9 | PATH: "${PATH}:${HOME}/.yarn/bin:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin"
10 | dependencies:
11 | pre:
12 | - |
13 | if [[ ! -e ~/.yarn/bin/yarn ]]; then
14 | curl -o- -L https://yarnpkg.com/install.sh | bash -s
15 | fi
16 | cache_directories:
17 | - ~/.yarn
18 | - ~/.yarn-cache
19 | override:
20 | - yarn install
21 | test:
22 | pre:
23 | - npm rebuild node-sass
24 | - yarn build:dll
25 | override:
26 | - yarn test
27 | post:
28 | - bash <(curl -s https://codecov.io/bash)
29 | - cat test/coverage/lcov.info | ./node_modules/.bin/codacy-coverage
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-mdc",
3 | "version": "0.0.10",
4 | "description": "Material Components Web for Vue.js",
5 | "author": "Eduardo San Martin Morote ",
6 | "main": "dist/vue-mdc.common.js",
7 | "module": "dist/vue-mdc.esm.js",
8 | "browser": "dist/vue-mdc.js",
9 | "unpkg": "dist/vue-mdc.js",
10 | "style": "dist/vue-mdc.css",
11 | "files": [
12 | "dist",
13 | "src"
14 | ],
15 | "scripts": {
16 | "prebuild": "rimraf dist",
17 | "build": "yon run build:browser && yon run build:browser:min && yon run build:esm",
18 | "build:esm": "cross-env NODE_ENV=esm node build/build.js",
19 | "build:browser:base": "webpack --config build/webpack.config.browser.js --progress --hide-modules",
20 | "build:browser": "cross-env NODE_ENV=browser yon run build:browser:base",
21 | "build:browser:min": "cross-env NODE_ENV=production yon build:browser:base -- -p",
22 | "build:dll": "webpack --progress --config build/webpack.config.dll.js",
23 | "lint": "eslint --ext js --ext jsx --ext vue src test/**/*.spec.js test/*.js build",
24 | "lint:fix": "yon run lint:js -- --fix",
25 | "lint:staged": "lint-staged",
26 | "pretest": "true || yon run lint",
27 | "test": "cross-env BABEL_ENV=test karma start test/karma.conf.js --single-run",
28 | "dev": "webpack-dashboard -- webpack-dev-server --config build/webpack.config.dev.js --open",
29 | "dev:coverage": "cross-env BABEL_ENV=test karma start test/karma.conf.js",
30 | "prepublish": "! in-publish || yon run build"
31 | },
32 | "lint-staged": {
33 | "*.{vue,jsx,js}": [
34 | "true || eslint --fix"
35 | ]
36 | },
37 | "pre-commit": "lint:staged",
38 | "devDependencies": {
39 | "add-asset-html-webpack-plugin": "^2.1.2",
40 | "babel-core": "^6.26.0",
41 | "babel-eslint": "^8.0.1",
42 | "babel-helper-vue-jsx-merge-props": "^2.0.2",
43 | "babel-loader": "^7.1.2",
44 | "babel-plugin-istanbul": "^4.1.5",
45 | "babel-plugin-syntax-jsx": "^6.18.0",
46 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
47 | "babel-plugin-transform-vue-jsx": "^3.5.0",
48 | "babel-preset-env": "^1.6.0",
49 | "camelcase": "^4.1.0",
50 | "chai": ">= 3.5.0 && < 4.0.0",
51 | "chai-dom": "^1.5.0",
52 | "codacy-coverage": "^2.0.3",
53 | "cross-env": "^5.0.5",
54 | "css-loader": "^0.28.7",
55 | "eslint": "^4.8.0",
56 | "eslint-config-posva": "^1.3.1",
57 | "extract-text-webpack-plugin": "^3.0.0",
58 | "function-bind": "^1.1.1",
59 | "html-webpack-plugin": "^2.30.1",
60 | "in-publish": "^2.0.0",
61 | "karma": "^1.7.1",
62 | "karma-chai-dom": "^1.1.0",
63 | "karma-chrome-launcher": "^2.2.0",
64 | "karma-coverage": "^1.1.1",
65 | "karma-mocha": "^1.3.0",
66 | "karma-sinon-chai": "^1.3.2",
67 | "karma-sourcemap-loader": "^0.3.7",
68 | "karma-spec-reporter": "^0.0.31",
69 | "karma-webpack": "^2.0.4",
70 | "lint-staged": "^6.0.0",
71 | "material-components-web": "^0.24.0",
72 | "mocha": "^3.5.3",
73 | "mocha-css": "^1.0.1",
74 | "node-sass": "^4.5.3",
75 | "null-loader": "^0.1.1",
76 | "pre-commit": "^1.2.2",
77 | "rimraf": "^2.6.2",
78 | "rollup": "^0.52.0",
79 | "rollup-plugin-buble": "^0.18.0",
80 | "rollup-plugin-commonjs": "^8.2.1",
81 | "rollup-plugin-node-resolve": "^3.0.0",
82 | "rollup-plugin-replace": "^2.0.0",
83 | "rollup-plugin-scss": "^0.4.0",
84 | "rollup-plugin-vue": "^3.0.0",
85 | "sass-loader": "^6.0.6",
86 | "sinon": "^4.0.0",
87 | "sinon-chai": "^2.14.0",
88 | "style-loader": "^0.19.0",
89 | "uglify-js": "^3.1.3",
90 | "uppercamelcase": "^3.0.0",
91 | "vue": "^2.4.4",
92 | "vue-loader": "^13.0.5",
93 | "vue-template-compiler": "^2.4.4",
94 | "webpack": "^3.6.0",
95 | "webpack-bundle-analyzer": "^2.9.0",
96 | "webpack-dashboard": "^1.0.0-5",
97 | "webpack-dev-server": "^2.9.1",
98 | "webpack-merge": "^4.1.0",
99 | "yarn-or-npm": "^2.0.4"
100 | },
101 | "dllPlugin": {
102 | "name": "visualTestingDeps",
103 | "include": [
104 | "mocha/mocha.js",
105 | "style-loader!css-loader!mocha-css",
106 | "html-entities",
107 | "vue/dist/vue.js",
108 | "chai",
109 | "chai-dom",
110 | "sinon",
111 | "sinon-chai",
112 | "diff",
113 | "core-js/library",
114 | "url",
115 | "sockjs-client",
116 | "vue-style-loader/lib/addStylesClient.js",
117 | "events",
118 | "ansi-html",
119 | "text-encoding"
120 | ]
121 | },
122 | "babel": {
123 | "presets": [
124 | [
125 | "env",
126 | {
127 | "targets": {
128 | "browsers": [
129 | "last 2 versions",
130 | "ie >= 11"
131 | ]
132 | }
133 | }
134 | ]
135 | ],
136 | "plugins": [
137 | "transform-vue-jsx",
138 | "transform-object-rest-spread"
139 | ],
140 | "env": {
141 | "test": {
142 | "plugins": [
143 | "istanbul"
144 | ]
145 | }
146 | }
147 | },
148 | "repository": {
149 | "type": "git",
150 | "url": "git+https://github.com/posva/vue-mdc.git"
151 | },
152 | "bugs": {
153 | "url": "https://github.com/posva/vue-mdc/issues"
154 | },
155 | "homepage": "https://github.com/posva/vue-mdc#readme",
156 | "license": {
157 | "type": "MIT",
158 | "url": "http://www.opensource.org/licenses/mit-license.php"
159 | },
160 | "dependencies": {
161 | "decamelize": "^1.2.0"
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/Button.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
30 |
--------------------------------------------------------------------------------
/src/Card/Card.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
10 |
11 |
12 |
15 |
16 | {{ title }}
17 | {{ subtitle }}
18 |
19 |
20 |
21 |
22 | {{ supportingText }}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
74 |
75 |
77 |
78 |
84 |
--------------------------------------------------------------------------------
/src/Card/CardActions.js:
--------------------------------------------------------------------------------
1 | import { propsToClasses } from '../utils'
2 |
3 | export default {
4 | functional: true,
5 |
6 | props: {
7 | vertical: Boolean,
8 | },
9 |
10 | render (h, { data, children, props }) {
11 | const staticClass = propsToClasses('mdc-card__actions', props)
12 | data.staticClass = data.staticClass
13 | ? `${data.staticClass} ${staticClass}`
14 | : staticClass
15 | children.forEach(child => {
16 | if (child.tag === 'button') {
17 | child.data.staticClass += ' mdc-card__action'
18 | }
19 | })
20 | return h('section', {
21 | ...data,
22 | }, children)
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/src/Card/CardHorizontalBlock.js:
--------------------------------------------------------------------------------
1 | export default {
2 | functional: true,
3 |
4 | render (h, { data, children }) {
5 | const staticClass = 'mdc-card__horizontal-block'
6 | data.staticClass = data.staticClass
7 | ? `${data.staticClass} ${staticClass}`
8 | : staticClass
9 | return h('div', {
10 | ...data,
11 | }, children)
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/src/Card/CardMedia.js:
--------------------------------------------------------------------------------
1 | export default {
2 | functional: true,
3 |
4 | props: {
5 | media: String,
6 | },
7 |
8 | render (h, { data, children, props }) {
9 | const staticClass = 'mdc-card__media'
10 | data.staticClass = data.staticClass
11 | ? `${data.staticClass} ${staticClass}`
12 | : staticClass
13 | data.style = data.style || {}
14 | data.style['background-image'] = data.style['background-image'] || `url(${props.media})`
15 |
16 | return h('section', {
17 | ...data,
18 | }, children)
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/src/Card/CardPrimary.js:
--------------------------------------------------------------------------------
1 | export default {
2 | functional: true,
3 |
4 | render (h, { data, children }) {
5 | const staticClass = 'mdc-card__primary'
6 | data.staticClass = data.staticClass
7 | ? `${data.staticClass} ${staticClass}`
8 | : staticClass
9 | return h('section', {
10 | ...data,
11 | }, children)
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/src/Card/CardSubtitle.js:
--------------------------------------------------------------------------------
1 | export default {
2 | functional: true,
3 |
4 | render (h, { data, children }) {
5 | const staticClass = 'mdc-card__subtitle'
6 | data.staticClass = data.staticClass
7 | ? `${data.staticClass} ${staticClass}`
8 | : staticClass
9 | return h('h2', {
10 | ...data,
11 | }, children)
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/src/Card/CardSupportingText.js:
--------------------------------------------------------------------------------
1 | export default {
2 | functional: true,
3 |
4 | render (h, { data, children }) {
5 | const staticClass = 'mdc-card__supporting-text'
6 | data.staticClass = data.staticClass
7 | ? `${data.staticClass} ${staticClass}`
8 | : staticClass
9 | return h('section', {
10 | ...data,
11 | }, children)
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/src/Card/CardTitle.js:
--------------------------------------------------------------------------------
1 | import { propsToClasses } from '../utils'
2 |
3 | export default {
4 | functional: true,
5 |
6 | props: {
7 | large: Boolean,
8 | },
9 |
10 | render (h, { data, children, props, parent }) {
11 | const staticClass = propsToClasses('mdc-card__title', props)
12 | data.staticClass = data.staticClass
13 | ? `${data.staticClass} ${staticClass}`
14 | : staticClass
15 | return h('h1', {
16 | ...data,
17 | }, children)
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/src/Checkbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
55 |
56 |
58 |
--------------------------------------------------------------------------------
/src/Dialog/Dialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
34 |
35 |
36 |
108 |
109 |
111 |
--------------------------------------------------------------------------------
/src/Dialog/DialogHeaderTitle.js:
--------------------------------------------------------------------------------
1 | export default {
2 | functional: true,
3 |
4 | render (h, { data, children }) {
5 | const staticClass = 'mdc-dialog__header__title'
6 | data.staticClass = data.staticClass
7 | ? `${data.staticClass} ${staticClass}`
8 | : staticClass
9 | return h('h2', {
10 | ...data,
11 | }, children)
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/src/Drawer/Drawer.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
17 |
18 |
19 |
113 |
114 |
116 |
117 |
122 |
--------------------------------------------------------------------------------
/src/Drawer/DrawerHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
27 |
--------------------------------------------------------------------------------
/src/Drawer/DrawerNav.js:
--------------------------------------------------------------------------------
1 | import List from '../List/List'
2 |
3 | export default {
4 | functional: true,
5 |
6 | props: {
7 | tag: {
8 | type: String,
9 | default: 'nav',
10 | },
11 | },
12 |
13 | render (h, { children, data, props }) {
14 | return h(List, {
15 | ...data,
16 | props,
17 | }, children)
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/src/Drawer/DrawerNavItem.vue:
--------------------------------------------------------------------------------
1 |
35 |
--------------------------------------------------------------------------------
/src/Fab.vue:
--------------------------------------------------------------------------------
1 |
40 |
41 |
43 |
--------------------------------------------------------------------------------
/src/List/List.vue:
--------------------------------------------------------------------------------
1 |
29 |
30 |
32 |
--------------------------------------------------------------------------------
/src/List/ListDivider.vue:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/src/List/ListItem.vue:
--------------------------------------------------------------------------------
1 |
23 |
24 |
26 |
--------------------------------------------------------------------------------
/src/Toolbar/Toolbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
31 |
32 |
34 |
35 |
43 |
--------------------------------------------------------------------------------
/src/Toolbar/ToolbarRow.js:
--------------------------------------------------------------------------------
1 | export default {
2 | functional: true,
3 |
4 | render (h, { children, data }) {
5 | const staticClass = 'mdc-toolbar__row'
6 | data.staticClass = data.staticClass
7 | ? `${data.staticClass} ${staticClass}`
8 | : staticClass
9 | return h('div', {
10 | ...data,
11 | }, children)
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/src/Toolbar/ToolbarSection.js:
--------------------------------------------------------------------------------
1 | export default {
2 | functional: true,
3 |
4 | props: {
5 | align: {
6 | default: 'start',
7 | },
8 | shrink: Boolean,
9 | },
10 |
11 | render (h, {
12 | children,
13 | props: {
14 | align,
15 | shrink,
16 | },
17 | data,
18 | }) {
19 | let staticClass = 'mdc-toolbar__section'
20 | if (align !== 'center') {
21 | staticClass += ` mdc-toolbar__section--align-${align}`
22 | }
23 | if (shrink) staticClass += ' mdc-toolbar__section--shrink-to-fit'
24 | data.staticClass = data.staticClass
25 | ? `${data.staticClass} ${staticClass}`
26 | : staticClass
27 | return h('section', {
28 | ...data,
29 | }, children)
30 | },
31 | }
32 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Button from './Button'
2 |
3 | import Card from './Card/Card'
4 | import CardActions from './Card/CardActions'
5 | import CardHorizontalBlock from './Card/CardHorizontalBlock'
6 | import CardMedia from './Card/CardMedia'
7 | import CardPrimary from './Card/CardPrimary'
8 | import CardSubtitle from './Card/CardSubtitle'
9 | import CardSupportingText from './Card/CardSupportingText'
10 | import CardTitle from './Card/CardTitle'
11 |
12 | import Checkbox from './Checkbox'
13 |
14 | import Dialog from './Dialog/Dialog'
15 | import DialogHeaderTitle from './Dialog/DialogHeaderTitle'
16 |
17 | import Drawer from './Drawer/Drawer'
18 | import DrawerHeader from './Drawer/DrawerHeader'
19 | import DrawerNav from './Drawer/DrawerNav'
20 | import DrawerNavItem from './Drawer/DrawerNavItem'
21 |
22 | import Fab from './Fab'
23 |
24 | import List from './List/List'
25 | import ListDivider from './List/ListDivider'
26 | import ListItem from './List/ListItem'
27 |
28 | import Toolbar from './Toolbar/Toolbar'
29 | import ToolbarRow from './Toolbar/ToolbarRow'
30 | import ToolbarSection from './Toolbar/ToolbarSection'
31 |
32 | import ripple from './ripple'
33 |
34 | const components = {
35 | Button,
36 |
37 | Card,
38 | CardActions,
39 | CardHorizontalBlock,
40 | CardMedia,
41 | CardPrimary,
42 | CardSubtitle,
43 | CardSupportingText,
44 | CardTitle,
45 |
46 | Checkbox,
47 |
48 | Dialog,
49 | DialogHeaderTitle,
50 |
51 | Drawer,
52 | DrawerHeader,
53 | DrawerNav,
54 | DrawerNavItem,
55 |
56 | Fab,
57 |
58 | List,
59 | ListDivider,
60 | ListItem,
61 |
62 | Toolbar,
63 | ToolbarRow,
64 | ToolbarSection,
65 | }
66 |
67 | function plugin (Vue, opts = { prefix: 'Mdc' }) {
68 | const { prefix } = opts
69 | const compNames = Object.keys(components)
70 | for (let i = 0; i < compNames.length; i++) {
71 | const name = compNames[i]
72 | Vue.component(`${prefix}${name}`, components[name])
73 | }
74 | Vue.directive('ripple', ripple)
75 | }
76 |
77 | export default plugin
78 | // Export all components too
79 | const version = '__VERSION__'
80 | export {
81 | version,
82 |
83 | // components
84 | Button,
85 |
86 | Card,
87 | CardActions,
88 | CardHorizontalBlock,
89 | CardMedia,
90 | CardPrimary,
91 | CardSubtitle,
92 | CardSupportingText,
93 | CardTitle,
94 |
95 | Checkbox,
96 |
97 | Dialog,
98 | DialogHeaderTitle,
99 |
100 | Drawer,
101 | DrawerHeader,
102 | DrawerNav,
103 | DrawerNavItem,
104 |
105 | Fab,
106 |
107 | List,
108 | ListDivider,
109 | ListItem,
110 |
111 | Toolbar,
112 | ToolbarRow,
113 | ToolbarSection,
114 |
115 | // directives
116 | ripple,
117 | }
118 |
--------------------------------------------------------------------------------
/src/ripple.js:
--------------------------------------------------------------------------------
1 | import MDCRippleFoundation from '@material/ripple/foundation'
2 | import { supportsCssVariables, getMatchesProperty } from '@material/ripple/util'
3 | import '@material/ripple/mdc-ripple.scss'
4 |
5 | const surfaceClass = 'mdc-ripple-surface'
6 |
7 | let MATCHES
8 | function createAdapter (el) {
9 | MATCHES = MATCHES || getMatchesProperty(HTMLElement.prototype)
10 | return {
11 | browserSupportsCssVars: () => supportsCssVariables(window),
12 | isSurfaceActive: /* istanbul ignore next */ () => el[MATCHES](':active'),
13 | addClass: (className) => el.classList.add(className),
14 | removeClass: (className) => el.classList.remove(className),
15 | registerInteractionHandler: (evtType, handler) => el.addEventListener(evtType, handler),
16 | deregisterInteractionHandler: (evtType, handler) => el.removeEventListener(evtType, handler),
17 | registerResizeHandler: (handler) => window.addEventListener('resize', handler),
18 | deregisterResizeHandler: (handler) => window.removeEventListener('resize', handler),
19 | updateCssVariable: (varName, value) => el.style.setProperty(varName, value),
20 | computeBoundingRect: () => el.getBoundingClientRect(),
21 | getWindowPageOffset: /* istanbul ignore next */ () => ({ x: window.pageXOffset, y: window.pageYOffset }),
22 | }
23 | }
24 |
25 | export default {
26 | bind (el, binding) {
27 | if (!binding.modifiers.custom) {
28 | el.classList.add(surfaceClass)
29 | }
30 | const unbounded = binding.modifiers.unbounded
31 | el.mdcRipple_ = new MDCRippleFoundation(
32 | Object.assign(createAdapter(el), {
33 | isUnbounded: () => unbounded,
34 | })
35 | )
36 | el.mdcRipple_.init()
37 | },
38 |
39 | inserted (el, binding, { componentInstance }) {
40 | // TODO add test case
41 | // I have been unable to reproduce the bug outside
42 | // of vue-mdc docs, so it may have to deal with SSR
43 | if (componentInstance) {
44 | componentInstance.$on('hook:updated', () => {
45 | el.mdcRipple_.destroy()
46 | binding.def.bind(el, binding)
47 | })
48 | }
49 | },
50 |
51 | componentUpdated (el, binding, vnode, oldVnode) {
52 | // Always recreate for functional components
53 | if (vnode.functionalContext) {
54 | el.mdcRipple_.destroy()
55 | binding.def.bind(el, binding)
56 | }
57 | },
58 |
59 | unbind (el, binding) {
60 | // istanbul ignore else
61 | if (el.mdcRipple_) {
62 | el.mdcRipple_.destroy()
63 | el.classList.remove(surfaceClass)
64 | delete el.mdcRipple_
65 | }
66 | },
67 | }
68 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import decamelize from 'decamelize'
2 |
3 | export function propsToClasses (base, props) {
4 | return Object.keys(props).reduce((classes, key) => {
5 | if (props[key]) classes += ` ${base}--${decamelize(key, '-')}`
6 | return classes
7 | }, base)
8 | }
9 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true,
7 | "sinon": true,
8 | "should": true,
9 | "requestAnimationFrame": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/helpers/Test.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ buttonText }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
38 |
--------------------------------------------------------------------------------
/test/helpers/index.js:
--------------------------------------------------------------------------------
1 | import camelcase from 'camelcase'
2 | import { createVM, Vue } from './utils'
3 | import { nextTick } from './wait-for-update'
4 |
5 | export function dataPropagationTest (Component) {
6 | return function () {
7 | const spy = sinon.spy()
8 | const vm = createVM(this, function (h) {
9 | return (
10 | Hello
11 | )
12 | })
13 | spy.should.have.not.been.called
14 | vm.$('.custom').should.exist
15 | vm.$('.custom').click()
16 | if (Component.functional) spy.should.have.been.calledOnce
17 | }
18 | }
19 |
20 | export function attrTest (it, base, Component, attr) {
21 | const attrs = Array.isArray(attr) ? attr : [attr]
22 |
23 | attrs.forEach(attr => {
24 | it(attr, function (done) {
25 | const vm = createVM(this, function (h) {
26 | const opts = {
27 | props: {
28 | [camelcase(attr)]: this.active,
29 | },
30 | }
31 | return (
32 | {attr}
33 | )
34 | }, {
35 | data: { active: true },
36 | })
37 | vm.$(`.${base}`).should.have.class(`${base}--${attr}`)
38 | vm.active = false
39 | nextTick().then(() => {
40 | vm.$(`.${base}`).should.not.have.class(`${base}--${attr}`)
41 | vm.active = true
42 | }).then(done)
43 | })
44 | })
45 | }
46 | export function foundationDetroyTest (Component) {
47 | return function (done) {
48 | const vm = createVM(this, function (h) {
49 | return (
50 | { this.show && }
51 | )
52 | }, {
53 | data: { show: true },
54 | })
55 | const foundation = vm.$refs.comp.foundation
56 | sinon.spy(foundation, 'destroy')
57 | foundation.destroy.should.have.not.been.called
58 | vm.show = false
59 | nextTick().then(() => {
60 | foundation.destroy.should.have.been.called.once
61 | foundation.destroy.restore()
62 | }).then(done)
63 | }
64 | }
65 |
66 | export {
67 | createVM,
68 | Vue,
69 | nextTick,
70 | }
71 |
--------------------------------------------------------------------------------
/test/helpers/style.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 | @import 'https://fonts.googleapis.com/icon?family=Material+Icons';
3 |
4 | #mocha .test-dom-container {
5 | border: 1px solid #eee;
6 | border-bottom-color: #ddd;
7 | border-radius: 3px;
8 | clear: left;
9 | display: block;
10 | margin: 5px;
11 | max-width: calc(100% - 42px);
12 | padding: 15px;
13 | position: relative;
14 | }
15 |
16 | /* Allow overflow */
17 | #mocha li.test {
18 | overflow: visible;
19 | }
20 |
21 | .test-dom-container {
22 | font: initial;
23 | }
24 |
25 | #mocha .test-dom-container--hidden {
26 | padding: 0;
27 | border: 0;
28 | margin: 0;
29 | }
30 |
31 | .test-dom-container__toggle {
32 | background-color: #edeeee;
33 | border-radius: 2px;
34 | color: #202129;
35 | cursor: pointer;
36 | display: block;
37 | left: -28px;
38 | padding: 0 3px;
39 | position: absolute;
40 | text-align: center;
41 | top: -24px;
42 |
43 | /* width - container border - spacing */
44 | user-select: none;
45 | width: 15px;
46 | }
47 |
48 | .test-dom-container__toggle:hover {
49 | background-color: #e1e2e2;
50 | }
51 |
52 | .test-dom-container__toggle:active {
53 | background-color: #d5d6d6;
54 | }
55 |
56 | .test-dom-container--hidden .test-dom-container__toggle {
57 | /* Add the 1px from border to both */
58 | top: -18px;
59 |
60 | /* width - spacing */
61 | left: -22px;
62 | }
63 |
64 | .test-dom-container--hidden .test-dom-container__content {
65 | display: none;
66 | visibility: hidden;
67 | }
68 |
69 | .mdc-card--theme-dark .mdc-card__primary,
70 | .mdc-card--theme-dark .mdc-card__actions {
71 | background: rgba(0, 0, 0, .4);
72 | }
73 |
--------------------------------------------------------------------------------
/test/helpers/utils.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue/dist/vue.js'
2 | import Test from './Test.vue'
3 |
4 | const isKarma = !!window.__karma__
5 |
6 | export function createVM (context, template, opts = {}) {
7 | return isKarma
8 | ? createKarmaTest(context, template, opts)
9 | : createVisualTest(context, template, opts)
10 | }
11 |
12 | const emptyNodes = document.querySelectorAll('nonexistant')
13 | Vue.prototype.$$ = function $$ (selector) {
14 | const els = document.querySelectorAll(selector)
15 | const vmEls = this.$el.querySelectorAll(selector)
16 | const fn = vmEls.length
17 | ? el => vmEls.find(el)
18 | : el => this.$el === el
19 | const found = Array.from(els).filter(fn)
20 | return found.length
21 | ? found
22 | : emptyNodes
23 | }
24 |
25 | Vue.prototype.$ = function $ (selector) {
26 | const els = document.querySelectorAll(selector)
27 | const vmEl = this.$el.querySelector(selector)
28 | const fn = vmEl
29 | ? el => el === vmEl
30 | : el => el === this.$el
31 | // Allow should chaining for tests
32 | return Array.from(els).find(fn) || emptyNodes
33 | }
34 |
35 | export function createKarmaTest (context, template, opts) {
36 | const el = document.createElement('div')
37 | document.getElementById('tests').appendChild(el)
38 | const render = typeof template === 'string'
39 | ? { template: `${template}
` }
40 | : { render: template }
41 | return new Vue({
42 | el,
43 | name: 'Test',
44 | ...render,
45 | ...opts,
46 | })
47 | }
48 |
49 | export function createVisualTest (context, template, opts) {
50 | let vm
51 | if (typeof template === 'string') {
52 | opts.components = opts.components || {}
53 | // Let the user define a test component
54 | if (!opts.components.Test) {
55 | opts.components.Test = Test
56 | }
57 | vm = new Vue({
58 | name: 'TestContainer',
59 | el: context.DOMElement,
60 | template: `${template}`,
61 | ...opts,
62 | })
63 | } else {
64 | // TODO allow redefinition of Test component
65 | vm = new Vue({
66 | name: 'TestContainer',
67 | el: context.DOMElement,
68 | render (h) {
69 | return h(Test, {
70 | attrs: {
71 | id: context.DOMElement.id,
72 | },
73 | // render the passed component with this scope
74 | }, [template.call(this, h)])
75 | },
76 | ...opts,
77 | })
78 | }
79 |
80 | context.DOMElement.vm = vm
81 | return vm
82 | }
83 |
84 | export function register (name, component) {
85 | Vue.component(name, component)
86 | }
87 |
88 | export { isKarma, Vue }
89 |
--------------------------------------------------------------------------------
/test/helpers/wait-for-update.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue/dist/vue.js'
2 |
3 | // Testing helper
4 | // nextTick().then(() => {
5 | //
6 | // Automatically waits for nextTick
7 | // }).then(() => {
8 | // return a promise or value to skip the wait
9 | // })
10 | function nextTick () {
11 | const jobs = []
12 | let done
13 |
14 | const chainer = {
15 | then (cb) {
16 | jobs.push(cb)
17 | return chainer
18 | },
19 | }
20 |
21 | function shift (...args) {
22 | const job = jobs.shift()
23 | let result
24 | try {
25 | result = job(...args)
26 | } catch (e) {
27 | jobs.length = 0
28 | done(e)
29 | }
30 |
31 | // wait for nextTick
32 | if (result !== undefined) {
33 | if (result.then) {
34 | result.then(shift)
35 | } else {
36 | shift(result)
37 | }
38 | } else if (jobs.length) {
39 | requestAnimationFrame(() => Vue.nextTick(shift))
40 | }
41 | }
42 |
43 | // First time
44 | Vue.nextTick(() => {
45 | done = jobs[jobs.length - 1]
46 | if (done.toString().slice(0, 14) !== 'function (err)') {
47 | throw new Error('waitForUpdate chain is missing .then(done)')
48 | }
49 | shift()
50 | })
51 |
52 | return chainer
53 | }
54 |
55 | exports.nextTick = nextTick
56 | exports.delay = time => new Promise(resolve => setTimeout(resolve, time))
57 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | // require all src files for coverage.
2 | // you can also change this to match only the subset of files that
3 | // you want coverage for.
4 | const srcContext = require.context('../src', true, /^\.\/(?!index(\.js)?$)/)
5 | srcContext.keys().forEach(srcContext)
6 |
7 | // Use a div to insert elements
8 | before(function () {
9 | const el = document.createElement('DIV')
10 | el.id = 'tests'
11 | document.body.appendChild(el)
12 | })
13 |
14 | // Remove every test html scenario
15 | afterEach(function () {
16 | const el = document.getElementById('tests')
17 | for (let i = 0; i < el.children.length; ++i) {
18 | el.removeChild(el.children[i])
19 | }
20 | })
21 |
22 | const specsContext = require.context('./specs', true)
23 | specsContext.keys().forEach(specsContext)
24 |
--------------------------------------------------------------------------------
/test/karma.conf.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge')
2 | const baseConfig = require('../build/webpack.config.dev.js')
3 |
4 | const webpackConfig = merge(baseConfig, {
5 | // use inline sourcemap for karma-sourcemap-loader
6 | devtool: '#inline-source-map',
7 | })
8 |
9 | webpackConfig.plugins = []
10 |
11 | const vueRule = webpackConfig.module.rules.find(rule => rule.loader === 'vue-loader')
12 | vueRule.options = vueRule.options || {}
13 | vueRule.options.loaders = vueRule.options.loaders || {}
14 | vueRule.options.loaders.js = 'babel-loader'
15 |
16 | // no need for app entry during tests
17 | delete webpackConfig.entry
18 |
19 | module.exports = function (config) {
20 | config.set({
21 | // to run in additional browsers:
22 | // 1. install corresponding karma launcher
23 | // http://karma-runner.github.io/0.13/config/browsers.html
24 | // 2. add it to the `browsers` array below.
25 | browsers: ['Chrome'],
26 | frameworks: ['mocha', 'chai-dom', 'sinon-chai'],
27 | reporters: ['spec', 'coverage'],
28 | files: ['./index.js'],
29 | preprocessors: {
30 | './index.js': ['webpack', 'sourcemap'],
31 | },
32 | webpack: webpackConfig,
33 | webpackMiddleware: {
34 | noInfo: true,
35 | },
36 | coverageReporter: {
37 | dir: './coverage',
38 | reporters: [
39 | { type: 'lcov', subdir: '.' },
40 | { type: 'text-summary' },
41 | ],
42 | },
43 | })
44 | }
45 |
--------------------------------------------------------------------------------
/test/specs/Button.spec.js:
--------------------------------------------------------------------------------
1 | import Button from 'src/Button.vue'
2 | import {
3 | createVM,
4 | dataPropagationTest,
5 | attrTest,
6 | } from '../helpers'
7 |
8 | describe('Button.vue', function () {
9 | it('renders an upgraded button', function () {
10 | const vm = createVM(this, `
11 |
12 | `, {
13 | components: { Button },
14 | })
15 | vm.$('button').should.have.text('Click me')
16 | vm.$('button').should.have.class('mdc-button')
17 | })
18 |
19 | it('keeps original tag data', dataPropagationTest(Button))
20 |
21 | describe('attrs', function () {
22 | attrTest(it, 'mdc-button', Button, [
23 | 'dense',
24 | 'raised',
25 | 'compact',
26 | 'primary',
27 | 'accent',
28 | 'theme-dark',
29 | ])
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/test/specs/Card.spec.js:
--------------------------------------------------------------------------------
1 | import MdcCard from 'src/Card/Card.vue'
2 | import MdcButton from 'src/Button.vue'
3 | import MdcCardActions from 'src/Card/CardActions.js'
4 | import MdcCardHorizontalBlock from 'src/Card/CardHorizontalBlock.js'
5 | import MdcCardMedia from 'src/Card/CardMedia.js'
6 | import MdcCardPrimary from 'src/Card/CardPrimary.js'
7 | import MdcCardTitle from 'src/Card/CardTitle.js'
8 | import MdcCardSubtitle from 'src/Card/CardSubtitle.js'
9 | import MdcCardSupportingText from 'src/Card/CardSupportingText.js'
10 | import {
11 | createVM,
12 | nextTick,
13 | dataPropagationTest,
14 | attrTest,
15 | } from '../helpers'
16 | const largeImg = 'http://material-components-web.appspot.com/images/16-9.jpg'
17 | const squareImg = 'http://material-components-web.appspot.com/images/1-1.jpg'
18 |
19 | describe('Card.vue', function () {
20 | it('can have a title', function () {
21 | const vm = createVM(this, `
22 |
23 | Title
24 |
25 |
26 | `, {
27 | components: { MdcCard, MdcCardTitle },
28 | })
29 | vm.$('.title .mdc-card__title').should.have.text('Title')
30 | vm.$('.no-title .mdc-card__title').should.not.exist
31 | })
32 |
33 | it('can have a title as a prop', function (done) {
34 | const vm = createVM(this, `
35 |
36 | `, {
37 | data: { title: 'Title' },
38 | components: { MdcCard },
39 | })
40 | vm.$('.prop .mdc-card__title').should.have.text('Title')
41 | // It's large by default
42 | vm.$('.prop .mdc-card__title').should.have.class('mdc-card__title--large')
43 | vm.title = ''
44 | nextTick().then(() => {
45 | vm.$('.prop .mdc-card__title').should.not.exist
46 | }).then(done)
47 | })
48 |
49 | it('can have a title and subtitle', function () {
50 | const vm = createVM(this, `
51 |
52 | Title
53 | subtitle
54 |
55 | `, {
56 | components: { MdcCard, MdcCardTitle, MdcCardSubtitle },
57 | })
58 | vm.$('.multi .mdc-card__title').should.have.text('Title')
59 | vm.$('.multi .mdc-card__subtitle').should.have.text('subtitle')
60 | })
61 |
62 | it('can have a subtitle as a prop', function (done) {
63 | const vm = createVM(this, `
64 |
65 | `, {
66 | data: { sub: 'sub' },
67 | components: { MdcCard },
68 | })
69 | vm.$('.prop .mdc-card__subtitle').should.have.text('sub')
70 | vm.sub = ''
71 | nextTick().then(() => {
72 | vm.$('.prop .mdc-card__sub').should.not.exist
73 | }).then(done)
74 | })
75 |
76 | it('keeps original data in Title', dataPropagationTest(MdcCardTitle))
77 | it('keeps original data in Subtitle', dataPropagationTest(MdcCardSubtitle))
78 | it('keeps original data in Primary', dataPropagationTest(MdcCardPrimary))
79 | it('keeps original data in HorizontalBlock', dataPropagationTest(MdcCardHorizontalBlock))
80 | it('keeps original data in SupportingText', dataPropagationTest(MdcCardSupportingText))
81 | it('keeps original data in Actions', dataPropagationTest(MdcCardActions))
82 |
83 | it('has a media section before the title', function (done) {
84 | const vm = createVM(this, `
85 |
87 |
88 | `, {
89 | data: { media: largeImg },
90 | components: { MdcCard },
91 | })
92 | vm.$('.mdc-card__media + .mdc-card__primary').should.exist
93 | vm.$('.mdc-card__primary + .mdc-card__media').should.not.exist
94 | const style = vm.$('.mdc-card__media').style
95 | style.height.should.equal('12.313rem')
96 | style.backgroundImage.should.equal(`url("${largeImg}")`)
97 | vm.media = ''
98 | nextTick().then(() => {
99 | vm.$('.mdc-card__media').should.not.exist
100 | }).then(done)
101 | })
102 |
103 | it('can reorder elements', function () {
104 | const vm = createVM(this, `
105 |
106 |
107 |
108 | `, {
109 | components: { MdcCard, MdcCardMedia },
110 | })
111 | vm.$('.mdc-card__media + .mdc-card__primary').should.not.exist
112 | vm.$('.mdc-card__primary + .mdc-card__media').should.exist
113 | })
114 |
115 | it('has a supporting text', function (done) {
116 | const vm = createVM(this, `
117 |
118 |
119 | `, {
120 | data: { text: 'Hello there' },
121 | components: { MdcCard, MdcCardMedia },
122 | })
123 | vm.$('.mdc-card__supporting-text').should.exist
124 | vm.$('.mdc-card__supporting-text').should.have.text('Hello there')
125 | vm.text = ''
126 | nextTick().then(() => {
127 | vm.$('.mdc-card__supporting-text').should.not.exist
128 | }).then(done)
129 | })
130 |
131 | it('has a slot in supporting text', function (done) {
132 | const vm = createVM(this, `
133 |
134 | A slot
135 |
136 | `, {
137 | data: { show: true },
138 | components: { MdcCard, MdcCardSupportingText },
139 | })
140 | vm.$('.mdc-card__supporting-text').should.exist
141 | vm.$('.mdc-card__supporting-text').should.have.text('A slot')
142 | vm.show = false
143 | nextTick().then(() => {
144 | vm.$('.mdc-card__supporting-text').should.not.exist
145 | }).then(done)
146 | })
147 |
148 | it('has an actions slot', function (done) {
149 | const vm = createVM(this, `
150 |
151 | Actions
152 | Actions
153 |
154 | `, {
155 | data: { show: true },
156 | components: { MdcCard, MdcButton },
157 | })
158 | vm.$('.mdc-card__actions').should.exist
159 | vm.$('.mdc-card__actions p').should.have.text('Actions')
160 | vm.$('.mdc-card__actions button').should.have.class('mdc-card__action')
161 | vm.$('.mdc-card__actions p').should.not.have.class('mdc-card__action')
162 | vm.show = false
163 | nextTick().then(() => {
164 | vm.$('.mdc-card__actions').should.not.exist
165 | vm.show = true
166 | }).then(done)
167 | })
168 |
169 | it('can apply a dark theme', function () {
170 | const vm = createVM(this, `
171 |
172 | Action
173 |
174 | `, {
175 | components: { MdcCard, MdcButton },
176 | })
177 | vm.$('.mdc-card').should.have.class('mdc-card--theme-dark')
178 | })
179 |
180 | it('horizontal block', function () {
181 | const vm = createVM(this, `
182 |
183 |
184 |
185 | Title Here
186 | Subtitle Here
187 |
188 |
189 |
190 | Action 1
191 | Action 2
192 |
193 | `, {
194 | components: {
195 | MdcCard,
196 | MdcButton,
197 | MdcCardTitle,
198 | MdcCardSubtitle,
199 | MdcCardPrimary,
200 | MdcCardHorizontalBlock,
201 | },
202 | })
203 | vm.$('.mdc-card__horizontal-block .mdc-card__primary .mdc-card__title').should.exist
204 | vm.$('.mdc-card__horizontal-block .mdc-card__primary .mdc-card__subtitle').should.exist
205 | })
206 |
207 | attrTest(it, 'mdc-card__actions', MdcCardActions, 'vertical')
208 | })
209 |
--------------------------------------------------------------------------------
/test/specs/Checkbox.spec.js:
--------------------------------------------------------------------------------
1 | import MdcCheckbox from 'src/Checkbox.vue'
2 | import { createVM } from '../helpers/utils.js'
3 | import { nextTick } from '../helpers/wait-for-update.js'
4 |
5 | describe('Checkbox.vue', function () {
6 | it('renders checkbox', function () {
7 | const vm = createVM(this, function (h) {
8 | return (
9 |
10 | )
11 | }, {
12 | components: { MdcCheckbox },
13 | })
14 | vm.$('input').should.have.attr('type', 'checkbox')
15 | })
16 |
17 | it('works with v-model and bool', function (done) {
18 | const vm = createVM(this, ``, {
19 | data: {
20 | checked: true,
21 | },
22 | components: { MdcCheckbox },
23 | })
24 | vm.checked.should.be.true
25 | vm.$('input').should.be.checked
26 | vm.checked = false
27 | nextTick().then(() => {
28 | vm.checked.should.be.false
29 | vm.$('input').should.not.be.checked
30 | }).then(done)
31 | })
32 |
33 | it('works with v-model and arrays', function (done) {
34 | const vm = createVM(this, `
35 |
36 |
37 | `, {
38 | data: {
39 | opts: ['one'],
40 | },
41 | components: { MdcCheckbox },
42 | })
43 | vm.opts.should.eql(['one'])
44 | vm.$('.one').should.be.checked
45 | vm.$('.two').should.not.be.checked
46 | vm.$('.one input').should.have.value('one')
47 | vm.opts = ['one', 'two']
48 | nextTick().then(() => {
49 | vm.opts.should.eql(['one', 'two'])
50 | vm.$('.one').should.be.checked
51 | vm.$('.two').should.be.checked
52 | vm.$('.one input').click()
53 | }).then(() => {
54 | vm.opts.should.eql(['two'])
55 | vm.$('.one').should.be.not.checked
56 | vm.$('.two').should.be.checked
57 | }).then(done)
58 | })
59 |
60 | it('can have classic attrs: id, disabled', function () {
61 | const vm = createVM(this, `
62 |
63 | `, {
64 | components: { MdcCheckbox },
65 | })
66 | vm.$('input').should.have.attr('disabled')
67 | vm.$('input').should.have.id('foo')
68 | })
69 | })
70 |
--------------------------------------------------------------------------------
/test/specs/Dialog.spec.js:
--------------------------------------------------------------------------------
1 | import Dialog from 'src/Dialog/Dialog'
2 | import DialogHeaderTitle from 'src/Dialog/DialogHeaderTitle'
3 | import {
4 | createVM,
5 | dataPropagationTest,
6 | foundationDetroyTest,
7 | nextTick,
8 | } from '../helpers'
9 |
10 | describe('Dialog', function () {
11 | describe('DialogHeaderTitle', function () {
12 | it('keeps original tag data', dataPropagationTest(DialogHeaderTitle))
13 | })
14 |
15 | it('keeps original tag data', dataPropagationTest(Dialog))
16 | it('calls foundation destroy', foundationDetroyTest(Dialog))
17 |
18 | it('can be scrollable', function (done) {
19 | const vm = createVM(this, function (h) {
20 | return (
21 |
22 |
23 |
24 |
25 | )
26 | }, {
27 | data: {
28 | scrollable: true,
29 | },
30 | })
31 |
32 | vm.$('.mdc-dialog__body')
33 | .should.exist.and.have.class('mdc-dialog__body--scrollable')
34 | vm.scrollable = false
35 | nextTick().then(() => {
36 | vm.$('.mdc-dialog__body')
37 | .should.exist.and.not.have.class('mdc-dialog__body--scrollable')
38 | }).then(done)
39 | })
40 |
41 | it('has a title', function () {
42 | const vm = createVM(this, function (h) {
43 | return (
44 |
45 |
46 |
47 |
48 | )
49 | })
50 |
51 | vm.$('.mdc-dialog__header').should.exist.and.have.text('Title')
52 | })
53 |
54 | it('accept button is focused by default', function () {
55 | const vm = createVM(this, function (h) {
56 | return (
57 |
58 |
59 |
60 |
61 | )
62 | })
63 |
64 | vm.$('.mdc-dialog__footer__button--accept')
65 | .should.exist
66 | .and.have.text('OK')
67 | .and.be.focused
68 | })
69 |
70 | it('can be toggled', function (done) {
71 | const vm = createVM(this, function (h) {
72 | return (
73 |
74 |
75 |
76 |
77 | )
78 | })
79 | vm.$refs.dialog.$el.should.have.class('mdc-dialog')
80 | vm.$refs.dialog.$el.should.be.hidden
81 | vm.$refs.dialog.open()
82 | nextTick().then(() => {
83 | vm.$refs.dialog.$el.should.not.be.hidden
84 | vm.$refs.dialog.close()
85 | }).then(() => {
86 | vm.$refs.dialog.$el.should.be.hidden
87 | }).then(done)
88 | })
89 |
90 | it('displays its content', function () {
91 | const vm = createVM(this, function (h) {
92 | return (
93 |
94 |
97 |
98 |
99 | )
100 | })
101 | vm.$('.mdc-dialog__body').should.exist.and.have.text('Dialog content')
102 | })
103 |
104 | it('is visible if content changes', function (done) {
105 | const vm = createVM(this, function (h) {
106 | return (
107 |
108 |
115 |
116 |
117 | )
118 | }, {
119 | data: {
120 | n: 0,
121 | },
122 | })
123 | vm.$('.content').should.exist.and.have.text('n: 0')
124 | vm.$refs.dialog.$el.should.be.hidden
125 | vm.$refs.dialog.open()
126 | nextTick().then(() => {
127 | vm.$refs.dialog.$el.should.not.be.hidden
128 | vm.n = 20
129 | }).then(() => {
130 | vm.$refs.dialog.$el.should.not.be.hidden
131 | vm.$('.content').should.exist.and.have.text('n: 20')
132 | }).then(done)
133 | })
134 |
135 | ;['accept', 'cancel'].map(type => {
136 | it(`displays a ${type} button`, function (done) {
137 | const vm = createVM(this, function (h) {
138 | const data = {
139 | props: {
140 | [`${type}Text`]: this.text,
141 | },
142 | }
143 | return (
144 |
145 |
148 |
149 |
150 | )
151 | }, {
152 | data: {
153 | text: 'Yes',
154 | },
155 | })
156 | vm.$(`.mdc-dialog__footer__button--${type}`)
157 | .should.exist.and.have.text('Yes')
158 | vm.text = undefined
159 | nextTick().then(() => {
160 | if (type === 'cancel') {
161 | vm.$(`.mdc-dialog__footer__button--${type}`).should.not.exist
162 | } else if (type === 'accept') {
163 | // default text for accept
164 | vm.$(`.mdc-dialog__footer__button--${type}`).should.exist
165 | }
166 | vm.text = 'Yes'
167 | }).then(done)
168 | })
169 | })
170 |
171 | it('has a ripple in buttons', function () {
172 | const vm = createVM(this, function (h) {
173 | return (
174 |
175 |
176 |
177 |
178 | )
179 | }, {
180 | data: {
181 | n: 0,
182 | },
183 | })
184 | vm.$('.mdc-dialog__footer__button--accept').should.exist.and.have.class('mdc-ripple-surface')
185 | })
186 |
187 | describe('Events', function () {
188 | it('accepted', function (done) {
189 | const spy = sinon.spy()
190 | const vm = createVM(this, function (h) {
191 | return (
192 |
193 |
196 |
197 |
198 | )
199 | }, {
200 | data: {
201 | text: 'Yes',
202 | },
203 | })
204 | vm.$refs.dialog.open()
205 | vm.text = 'ok'
206 | nextTick().then(() => {
207 | spy.should.have.not.been.called
208 | vm.$('.mdc-dialog__footer__button--accept').click()
209 | }).then(() => {
210 | spy.should.have.been.called
211 | }).then(done)
212 | })
213 |
214 | it('canceled', function (done) {
215 | const spy = sinon.spy()
216 | const vm = createVM(this, function (h) {
217 | return (
218 |
219 |
222 |
223 |
224 | )
225 | }, {
226 | data: {
227 | text: 'No',
228 | },
229 | })
230 | vm.$refs.dialog.open()
231 | vm.text = 'nope'
232 | nextTick().then(() => {
233 | spy.should.have.not.been.called
234 | vm.$('.mdc-dialog__footer__button--cancel').click()
235 | }).then(() => {
236 | spy.should.have.been.called
237 | }).then(done)
238 | })
239 | })
240 |
241 | it('can override the header', function () {
242 | const vm = createVM(this, function (h) {
243 | return (
244 |
245 |
249 |
250 |
251 | )
252 | })
253 | vm.$('.mdc-dialog__header__title').should.exist.and.have.text('Custom')
254 | })
255 |
256 | it('should not have an id by default', function () {
257 | const vm = createVM(this, function (h) {
258 | return (
259 |
260 |
263 |
264 |
265 | )
266 | })
267 | vm.$('.mdc-dialog__header__title').should.not.have.attr('id')
268 | vm.$('.mdc-dialog__body').should.not.have.attr('id')
269 | vm.$refs.dialog.$el.should.not.have.attr('id')
270 | })
271 |
272 | it('should propagate id to aria labels', function (done) {
273 | const vm = createVM(this, function (h) {
274 | return (
275 |
276 |
279 |
280 |
281 | )
282 | }, {
283 | data: {
284 | id: 'my-dialog',
285 | },
286 | })
287 | vm.$('.mdc-dialog__header__title').should.have.id('my-dialog__label')
288 | vm.$('.mdc-dialog__body').should.have.id('my-dialog__description')
289 | vm.$refs.dialog.$el.should.have.id('my-dialog')
290 | vm.$refs.dialog.$el.should.have.attr('aria-labelledby', 'my-dialog__label')
291 | vm.$refs.dialog.$el.should.have.attr('aria-describedby', 'my-dialog__description')
292 | vm.id = false
293 | nextTick().then(() => {
294 | vm.$('.mdc-dialog__header__title').should.not.have.attr('id')
295 | vm.$('.mdc-dialog__body').should.not.have.attr('id')
296 | vm.$refs.dialog.$el.should.not.have.attr('id')
297 | }).then(done)
298 | })
299 | })
300 |
--------------------------------------------------------------------------------
/test/specs/Drawer.spec.js:
--------------------------------------------------------------------------------
1 | import Drawer from 'src/Drawer/Drawer'
2 | import DrawerNav from 'src/Drawer/DrawerNav'
3 | import DrawerNavItem from 'src/Drawer/DrawerNavItem'
4 | import DrawerHeader from 'src/Drawer/DrawerHeader'
5 | import ListDivider from 'src/List/ListDivider'
6 | import {
7 | createVM,
8 | dataPropagationTest,
9 | foundationDetroyTest,
10 | nextTick,
11 | } from '../helpers'
12 |
13 | describe('Drawer', function () {
14 | it('keeps original tag data', dataPropagationTest(Drawer))
15 |
16 | it('is temporary by default', function () {
17 | const vm = createVM(this, h => (
18 |
19 |
20 | ))
21 | vm.$refs.drawer.$el.should.have.class('mdc-temporary-drawer')
22 | })
23 |
24 | it('calls foundation destroy', foundationDetroyTest(Drawer))
25 |
26 | describe('temporary', function () {
27 | describe('no content', function () {
28 | beforeEach(function () {
29 | this.vm = createVM(this, function (h) {
30 | return (
31 |
32 |
33 |
34 |
35 |
36 | )
37 | })
38 | })
39 |
40 | afterEach(function () {
41 | this.vm.$refs.drawer.close()
42 | })
43 |
44 | it('makes the body unscrollable while open', function () {
45 | const scrollBlock = 'mdc-dialog-scroll-lock'
46 | document.body.should.not.have.class(scrollBlock)
47 | this.vm.$refs.drawer.open()
48 | document.body.should.have.class(scrollBlock)
49 | this.vm.$refs.drawer.close()
50 | document.body.should.not.have.class(scrollBlock)
51 | })
52 |
53 | it('can be closed', function (done) {
54 | this.vm.$refs.drawer.$el.should.not.have.class('mdc-temporary-drawer--open')
55 | this.vm.$refs.drawer.close()
56 | nextTick().then(() => {
57 | this.vm.$refs.drawer.$el.should.not.have.class('mdc-temporary-drawer--open')
58 | }).then(done)
59 | })
60 |
61 | it('closes when clicking outside, function ()', function () {
62 | this.vm.$refs.drawer.open()
63 | this.vm.$refs.drawer.$el.should.have.class('mdc-temporary-drawer--open')
64 | this.vm.$('.mdc-temporary-drawer').click()
65 | this.vm.$refs.drawer.$el.should.not.have.class('mdc-temporary-drawer--open')
66 | })
67 |
68 | it('can be opened', function (done) {
69 | this.vm.$refs.drawer.$el.should.not.have.class('mdc-temporary-drawer--open')
70 | this.vm.$refs.drawer.open()
71 | nextTick().then(() => {
72 | this.vm.$refs.drawer.$el.should.have.class('mdc-temporary-drawer--open')
73 | // should do nothing
74 | this.vm.$refs.drawer.open()
75 | }).then(() => {
76 | this.vm.$refs.drawer.$el.should.have.class('mdc-temporary-drawer--open')
77 | this.vm.$refs.drawer.close()
78 | }).then(done)
79 | })
80 | })
81 |
82 | describe('with a nav', function () {
83 | beforeEach(function () {
84 | this.vm = createVM(this, function (h) {
85 | const select = e => {
86 | this.selected = e.target.textContent
87 | }
88 | const menu1 = [
89 | { icon: 'inbox', text: 'Inbox' },
90 | { icon: 'star', text: 'Star' },
91 | { icon: 'send', text: 'Sent Mail' },
92 | { icon: 'drafts', text: 'Drafts' },
93 | ]
94 | const menu2 = [
95 | { icon: 'email', text: 'All Mail' },
96 | { icon: 'delete', text: 'Trash' },
97 | { icon: 'report', text: 'Spam' },
98 | ]
99 | const isSelected = id => id === this.selected
100 |
101 | return (
102 |
103 |
104 |
107 | Header Here
108 |
109 |
110 |
111 | {menu1.map(({ icon, text }) => (
112 |
116 | {icon}{text}
118 |
119 | ))}
120 |
121 |
122 |
123 |
124 |
125 | {menu2.map(({ icon, text }) => (
126 |
130 | {icon}{text}
132 |
133 | ))}
134 |
135 |
136 |
137 |
138 | )
139 | }, {
140 | data: {
141 | selected: 'inboxInbox',
142 | },
143 | })
144 | })
145 |
146 | it('holds a header and nav', function () {
147 | this.vm.$('.mdc-temporary-drawer__header').should.exist
148 | this.vm.$('.mdc-temporary-drawer__content').should.match('.mdc-list-group')
149 | this.vm.$('.mdc-list-group .mdc-list').should.contain('.mdc-list-item')
150 | this.vm.$('.mdc-list-item').should.have.text('inboxInbox')
151 | this.vm.$('.mdc-list-item').should.have.class('mdc-temporary-drawer--selected')
152 | })
153 | })
154 | })
155 |
156 | describe('DrawerNav', function () {
157 | it('is a nav by default', function () {
158 | const vm = createVM(this, h => (
159 |
160 | ))
161 | vm.$('.mdc-list').should.exist.and.match('nav')
162 | })
163 | it('can render a custom tag', function () {
164 | const vm = createVM(this, h => (
165 |
166 | ))
167 | vm.$('.mdc-list').should.exist.and.match('div')
168 | })
169 | })
170 |
171 | describe('DrawerNavItem', function () {
172 | it('is an a by default', function () {
173 | const vm = createVM(this, h => (
174 | Hello
175 | ))
176 | vm.$('.mdc-list-item').should.exist.and.match('a')
177 | })
178 | it('can render a custom tag', function () {
179 | const vm = createVM(this, h => (
180 | Hello
181 | ))
182 | vm.$('.mdc-list-item').should.exist.and.match('div')
183 | })
184 | })
185 | })
186 |
--------------------------------------------------------------------------------
/test/specs/Fab.spec.js:
--------------------------------------------------------------------------------
1 | import Fab from 'src/Fab'
2 | import {
3 | createVM,
4 | dataPropagationTest,
5 | attrTest,
6 | } from '../helpers'
7 |
8 | describe('Fab', function () {
9 | it('keeps original tag data', dataPropagationTest(Fab))
10 |
11 | it('renders a button by default', function () {
12 | const vm = createVM(this, function (h) {
13 | return (
14 |
15 | )
16 | })
17 | vm.$('.mdc-fab')
18 | .should.have.match('button')
19 | .and.have.class('material-icons')
20 | })
21 |
22 | it('contains an icon', function () {
23 | const vm = createVM(this, function (h) {
24 | return (
25 |
26 | )
27 | })
28 | vm.$('.mdc-fab__icon').should.have.match('span')
29 | })
30 |
31 | it('contains custom content', function () {
32 | const vm = createVM(this, function (h) {
33 | return (
34 | 💃🏻
35 | )
36 | })
37 | vm.$('.mdc-fab').should.not.have.class('material-icons')
38 | vm.$('.mdc-fab__icon').should.not.exist
39 | })
40 |
41 | describe('attrs', function () {
42 | attrTest(it, 'mdc-fab', Fab, [
43 | 'mini',
44 | 'plain',
45 | ])
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/test/specs/List.spec.js:
--------------------------------------------------------------------------------
1 | import List from 'src/List/List'
2 | import ListItem from 'src/List/ListItem'
3 | import ListDivider from 'src/List/ListDivider'
4 | import ripple from 'src/ripple'
5 | import {
6 | createVM,
7 | dataPropagationTest,
8 | attrTest,
9 | } from '../helpers'
10 |
11 | describe('List', function () {
12 | it('renders an upgraded list', function () {
13 | const vm = createVM(this, h => (
14 |
15 | Item 1
16 | Item 2
17 |
18 | ), {
19 | })
20 | vm.$refs.list.should.have.class('mdc-list')
21 | vm.$refs.list.should.match('ul')
22 | })
23 |
24 | it('can render a custom tag', function () {
25 | const vm = createVM(this, h => (
26 | Hello
27 | ))
28 | vm.$refs.list.should.have.class('mdc-list')
29 | vm.$refs.list.should.match('div')
30 | })
31 |
32 | it('keeps original tag data', dataPropagationTest(List))
33 |
34 | describe('attrs', function () {
35 | attrTest(it, 'mdc-list', List, [
36 | 'dense',
37 | 'two-lines',
38 | ])
39 | })
40 |
41 | describe('ListItem', function () {
42 | it('renders an upgraded list item', function () {
43 | const vm = createVM(this, h => (
44 |
45 |
46 | Item 1
47 |
48 | network_wifi
50 |
51 | Two-line item
52 | Secondary text
53 |
54 |
55 |
56 |
57 | ), { directives: { ripple }})
58 | vm.$refs.item.should.have.class('mdc-list-item')
59 | vm.$refs.item.should.match('li')
60 | })
61 |
62 | it('can render a custom tag', function () {
63 | const vm = createVM(this, h => (
64 | Hello
65 | ))
66 | vm.$refs.item.should.have.class('mdc-list-item')
67 | vm.$refs.item.should.match('a')
68 | })
69 |
70 | it('keeps original tag data', dataPropagationTest(ListItem))
71 | })
72 |
73 | describe('ListDivider', function () {
74 | it('keeps original tag data', dataPropagationTest(ListDivider))
75 | })
76 | })
77 |
--------------------------------------------------------------------------------
/test/specs/Toolbar.spec.js:
--------------------------------------------------------------------------------
1 | import Toolbar from 'src/Toolbar/Toolbar'
2 | import ToolbarRow from 'src/Toolbar/ToolbarRow'
3 | import ToolbarSection from 'src/Toolbar/ToolbarSection'
4 | import {
5 | createVM,
6 | dataPropagationTest,
7 | nextTick,
8 | } from '../helpers'
9 |
10 | describe('Toolbar', function () {
11 | it('keeps original tag data', dataPropagationTest(Toolbar))
12 |
13 | it('has default icon', function () {
14 | const vm = createVM(this, function (h) {
15 | return (
16 |
17 |
18 | )
19 | })
20 | vm.$refs.toolbar.$el.should.have.class('mdc-toolbar')
21 | vm.$('.mdc-toolbar__menu').should.have.text('menu')
22 | })
23 |
24 | it('can set the title', function () {
25 | const vm = createVM(this, function (h) {
26 | return (
27 |
28 |
29 | )
30 | })
31 | vm.$('.mdc-toolbar__title').should.have.text('My Title')
32 | })
33 |
34 | it('can set the icon', function () {
35 | const vm = createVM(this, function (h) {
36 | return (
37 |
38 |
39 | )
40 | })
41 | vm.$('.mdc-toolbar__menu').should.have.text('star')
42 | })
43 |
44 | it('emits when clicking the menu', function (done) {
45 | const spy = sinon.spy()
46 | const vm = createVM(this, function (h) {
47 | return (
48 |
49 |
50 | )
51 | })
52 | spy.should.have.not.been.called
53 | vm.$('.mdc-toolbar__menu').click()
54 | nextTick().then(() => {
55 | spy.should.have.been.called
56 | }).then(done)
57 | })
58 |
59 | it('aligns start by default', function () {
60 | const vm = createVM(this, function (h) {
61 | return (
62 |
63 |
64 | )
65 | })
66 | vm.$('.mdc-toolbar__section')
67 | .should.have.class('mdc-toolbar__section--align-start')
68 | .and.not.have.class('mdc-toolbar__section--align-end')
69 | })
70 |
71 | it('can accomodate more sections')
72 |
73 | describe('ToolbarSection', function () {
74 | it('keeps original tag data', dataPropagationTest(ToolbarSection))
75 |
76 | it('aligns start by default', function () {
77 | const vm = createVM(this, function (h) {
78 | return (
79 | Content
80 | )
81 | })
82 | vm.$('.mdc-toolbar__section')
83 | .should.have.class('mdc-toolbar__section--align-start')
84 | .and.have.text('Content')
85 | .and.not.have.class('mdc-toolbar__section--align-end')
86 | })
87 |
88 | it('can align center', function () {
89 | const vm = createVM(this, function (h) {
90 | return (
91 |
92 |
93 | )
94 | })
95 | vm.$('.mdc-toolbar__section')
96 | .should.not.have.class('mdc-toolbar__section--align-start')
97 | .and.not.have.class('mdc-toolbar__section--align-end')
98 | })
99 |
100 | it('can align end', function () {
101 | const vm = createVM(this, function (h) {
102 | return (
103 |
104 |
105 | )
106 | })
107 | vm.$('.mdc-toolbar__section')
108 | .should.have.class('mdc-toolbar__section--align-end')
109 | .and.not.have.class('mdc-toolbar__section--align-start')
110 | })
111 |
112 | it('shrinks', function () {
113 | const vm = createVM(this, function (h) {
114 | return (
115 |
116 |
117 | )
118 | })
119 | vm.$('.mdc-toolbar__section')
120 | .should.have.class('mdc-toolbar__section--shrink-to-fit')
121 | })
122 | })
123 |
124 | describe('ToolbarRow', function () {
125 | it('keeps original tag data', dataPropagationTest(ToolbarRow))
126 | })
127 | })
128 |
--------------------------------------------------------------------------------
/test/specs/VueMdc.spec.js:
--------------------------------------------------------------------------------
1 | import VueMdc from 'src'
2 | import * as all from 'src'
3 | import { Vue } from '../helpers/utils'
4 |
5 | function comp (Vue, name) {
6 | return function () {
7 | should.exist(Vue.component(name))
8 | }
9 | }
10 |
11 | function dir (Vue, name) {
12 | return function () {
13 | should.exist(Vue.directive(name))
14 | }
15 | }
16 |
17 | const components = [
18 | 'Button',
19 |
20 | 'Card',
21 | 'CardActions',
22 | 'CardHorizontalBlock',
23 | 'CardMedia',
24 | 'CardPrimary',
25 | 'CardSubtitle',
26 | 'CardSupportingText',
27 | 'CardTitle',
28 |
29 | 'Checkbox',
30 |
31 | 'Dialog',
32 | 'DialogHeaderTitle',
33 |
34 | 'Drawer',
35 | 'DrawerHeader',
36 | 'DrawerNav',
37 | 'DrawerNavItem',
38 |
39 | 'Fab',
40 |
41 | 'List',
42 | 'ListDivider',
43 | 'ListItem',
44 |
45 | 'Toolbar',
46 | 'ToolbarRow',
47 | 'ToolbarSection',
48 | ]
49 |
50 | describe('VueMdc', function () {
51 | it('exporting all components', function () {
52 | Object.keys(all).should.eql(
53 | ['version', ...components, 'ripple', 'default']
54 | )
55 | })
56 |
57 | describe('Register components', function () {
58 | const Vue1 = Vue.extend()
59 | components.forEach(name => {
60 | Vue1.use(VueMdc)
61 | it(`registers Mdc${name}`, comp(Vue1, `Mdc${name}`))
62 | })
63 |
64 | it('registers v-ripple', dir(Vue1, 'ripple'))
65 | })
66 |
67 | describe('Prefixes', function () {
68 | const Vue2 = Vue.extend()
69 | before(function () {
70 | VueMdc.installed = false
71 | Vue2.use(VueMdc, {
72 | prefix: 'Ui',
73 | })
74 | })
75 | components.forEach(name => {
76 | it(`registers Ui${name}`, comp(Vue2, `Ui${name}`))
77 | })
78 | })
79 | })
80 |
--------------------------------------------------------------------------------
/test/specs/ripple.spec.js:
--------------------------------------------------------------------------------
1 | import ripple from 'src/ripple.js'
2 | import { createVM } from '../helpers/utils.js'
3 | import { nextTick } from '../helpers/wait-for-update.js'
4 |
5 | describe('Ripple', function () {
6 | it('upgrades a div', function (done) {
7 | const vm = createVM(this, function (h) {
8 | return (
9 | Simple
10 | )
11 | }, {
12 | directives: { ripple },
13 | })
14 | nextTick().then(() => {
15 | const ripple = vm.$('.my-ripple')
16 | ripple.should.have.class('mdc-ripple-surface')
17 | }).then(() => {
18 | const ripple = vm.$('.my-ripple')
19 | ripple.should.have.class('mdc-ripple-upgraded')
20 | }).then(done)
21 | })
22 |
23 | it('can use custom class', function (done) {
24 | const vm = createVM(
25 | this,
26 | `Custom
`,
27 | { directives: { ripple }}
28 | )
29 | nextTick().then(() => {
30 | const ripple = vm.$('.my-ripple')
31 | ripple.should.not.have.class('mdc-ripple-surface')
32 | }).then(() => {
33 | const ripple = vm.$('.my-ripple')
34 | ripple.should.have.class('mdc-ripple-upgraded')
35 | }).then(done)
36 | })
37 |
38 | it('unbounds events and clases', function (done) {
39 | const vm = createVM(this, function (h) {
40 | const opts = {
41 | directives: [],
42 | }
43 | if (this.show) {
44 | opts.directives.push({
45 | name: 'ripple',
46 | })
47 | }
48 | return (
49 | Unbind
50 | )
51 | }, {
52 | data: {
53 | show: true,
54 | },
55 | directives: { ripple },
56 | })
57 | vm.show = false
58 | nextTick().then(() => {
59 | const ripple = vm.$('.my-ripple')
60 | ripple.should.not.have.class('mdc-ripple-upgraded')
61 | ripple.should.not.have.class('mdc-ripple-surface')
62 | }).then(done)
63 | })
64 |
65 | it('works with regular elements', function (done) {
66 | const vm = createVM(
67 | this,
68 | `A div
`,
69 | {
70 | data: {
71 | text: 'foo',
72 | },
73 | directives: { ripple },
74 | }
75 | )
76 | const oldEl = vm.$('.my-ripple')
77 | vm.text = 'bar'
78 | nextTick().then(() => {
79 | // we need to wait an extra tick
80 | }).then(() => {
81 | const ripple = vm.$('.my-ripple')
82 | oldEl.should.not.equal(ripple)
83 | ripple.should.have.class('mdc-ripple-upgraded')
84 | ripple.should.have.class('mdc-ripple-surface')
85 | }).then(done)
86 | })
87 |
88 | it('works with regular components', function (done) {
89 | const vm = createVM(
90 | this,
91 | `A foo`,
92 | {
93 | data: {
94 | text: 'foo',
95 | },
96 | directives: { ripple },
97 | components: {
98 | Foo: {
99 | template: '
',
100 | },
101 | },
102 | }
103 | )
104 | const oldEl = vm.$('.my-ripple')
105 | vm.text = 'bar'
106 | nextTick().then(() => {
107 | // we need to wait an extra tick
108 | }).then(() => {
109 | const ripple = vm.$('.my-ripple')
110 | oldEl.should.not.equal(ripple)
111 | ripple.should.have.class('mdc-ripple-upgraded')
112 | ripple.should.have.class('mdc-ripple-surface')
113 | }).then(done)
114 | })
115 |
116 | it('works with functional components', function (done) {
117 | const vm = createVM(
118 | this,
119 | `{{text}}`,
120 | {
121 | data: {
122 | text: 'foo',
123 | },
124 | directives: { ripple },
125 | components: {
126 | Foo: {
127 | functional: true,
128 | render: (h, { data, children }) => h('div', data, children),
129 | },
130 | },
131 | }
132 | )
133 | const oldEl = vm.$('.my-ripple')
134 | vm.text = 'bar'
135 | nextTick().then(() => {
136 | // we need to wait an extra tick
137 | }).then(() => {
138 | const ripple = vm.$('.my-ripple')
139 | // The same object is reused
140 | oldEl.should.equal(ripple)
141 | ripple.should.have.class('mdc-ripple-upgraded')
142 | ripple.should.have.class('mdc-ripple-surface')
143 | }).then(done)
144 | })
145 |
146 | it('works when the node is reused', function (done) {
147 | const vm = createVM(
148 | this,
149 | `{{text}}
`,
150 | {
151 | data: {
152 | text: 'foo',
153 | },
154 | directives: { ripple },
155 | }
156 | )
157 | const oldEl = vm.$('.my-ripple')
158 | vm.text = 'bar'
159 | nextTick().then(() => {
160 | // we need to wait an extra tick
161 | }).then(() => {
162 | const ripple = vm.$('.my-ripple')
163 | // The same object is reused
164 | oldEl.should.equal(ripple)
165 | ripple.should.have.class('mdc-ripple-upgraded')
166 | ripple.should.have.class('mdc-ripple-surface')
167 | }).then(done)
168 | })
169 | })
170 |
--------------------------------------------------------------------------------
/test/visual.js:
--------------------------------------------------------------------------------
1 | /* global requestAnimationFrame */
2 | import 'style-loader!css-loader!mocha-css'
3 | import 'style-loader!css-loader!./helpers/style.css'
4 |
5 | // create a div where mocha can add its stuff
6 | const mochaDiv = document.createElement('DIV')
7 | mochaDiv.id = 'mocha'
8 | document.body.appendChild(mochaDiv)
9 |
10 | import 'mocha/mocha.js'
11 | import sinon from 'sinon'
12 | import chai from 'chai'
13 | window.mocha.setup({
14 | ui: 'bdd',
15 | slow: 750,
16 | timeout: 5000,
17 | globals: [
18 | '__VUE_DEVTOOLS_INSTANCE_MAP__',
19 | 'script',
20 | 'inject',
21 | 'originalOpenFunction',
22 | // some chrome plugins things
23 | 'pb_blacklist',
24 | 'pb_whitelist',
25 | 'pb_isRunning',
26 | ],
27 | })
28 | window.sinon = sinon
29 | chai.use(require('chai-dom'))
30 | chai.use(require('sinon-chai'))
31 | window.should = chai.should()
32 |
33 | let vms = []
34 | let testId = 0
35 |
36 | beforeEach(function () {
37 | this.DOMElement = document.createElement('DIV')
38 | this.DOMElement.id = `test-${++testId}`
39 | document.body.appendChild(this.DOMElement)
40 | })
41 |
42 | afterEach(function () {
43 | const testReportElements = document.getElementsByClassName('test')
44 | const lastReportElement = testReportElements[testReportElements.length - 1]
45 |
46 | if (!lastReportElement) return
47 | const el = document.getElementById(this.DOMElement.id)
48 | if (el) lastReportElement.appendChild(el)
49 | // Save the vm to hide it later
50 | if (this.DOMElement.vm) vms.push(this.DOMElement.vm)
51 | })
52 |
53 | // Hide all tests at the end to prevent some weird bugs
54 | before(function () {
55 | vms = []
56 | testId = 0
57 | })
58 | after(function () {
59 | requestAnimationFrame(function () {
60 | setTimeout(function () {
61 | vms.forEach(vm => {
62 | // Hide if test passed
63 | if (vm.$el.parentElement &&
64 | !vm.$el.parentElement.classList.contains('fail')) {
65 | vm.$children[0].visible = false
66 | }
67 | })
68 | }, 100)
69 | })
70 | })
71 |
72 | const specsContext = require.context('./specs', true)
73 | specsContext.keys().forEach(specsContext)
74 |
75 | window.mocha.checkLeaks()
76 | window.mocha.run()
77 |
--------------------------------------------------------------------------------