Permalink
Browse files

feat(badge): functional component (#820)

* feat(badge): functional component

* fix(deps): remove jquery dep

* Revert "fix(deps): remove jquery dep"

This reverts commit e4ca173.
  • Loading branch information...
alexsasharegan committed Aug 12, 2017
1 parent 88e2dbb commit 8c172c18a298b7e269526085babf17b408169db9
Showing with 162 additions and 106 deletions.
  1. +34 −0 lib/components/badge.js
  2. +0 −28 lib/components/badge.vue
  3. +1 −1 lib/components/index.js
  4. +7 −6 lib/utils/index.js
  5. +3 −2 package.json
  6. +24 −31 tests/components/badge.spec.js
  7. +89 −38 tests/helpers.js
  8. +4 −0 yarn.lock
@@ -0,0 +1,34 @@
import { mergeData } from "../utils";
export const props = {
tag: {
type: String,
default: "span"
},
variant: {
type: String,
default: "default"
},
pill: {
type: Boolean,
default: false
}
};
export default {
functional: true,
props,
render(h, { props, data, children }) {
return h(
props.tag,
mergeData(data, {
staticClass: "badge",
class: [
!props.variant ? "badge-default" : `badge-${props.variant}`,
{ "badge-pill": Boolean(props.pill) }
]
}),
children
);
}
};

This file was deleted.

Oops, something went wrong.
@@ -1,4 +1,5 @@
import bAlert from './alert.vue';
import bBadge from './badge';
import bBreadcrumb from './breadcrumb.vue';
import bButton from './button.vue';
import bButtonToolbar from './button-toolbar.vue';
@@ -25,7 +26,6 @@ import bFormInputStatic from './form-input-static.vue';
import bFormFile from './form-file.vue';
import bFormSelect from './form-select.vue';
import bJumbotron from './jumbotron.vue';
import bBadge from './badge.vue';
import bListGroup from './list-group.vue';
import bListGroupItem from './list-group-item.vue';
import bMedia from './media.vue';
@@ -1,7 +1,8 @@
import addEventListenerOnce from "./addEventListenerOnce"
import * as array from "./array"
import * as object from "./object"
import observeDom from "./observe-dom"
import warn from "./warn"
import addEventListenerOnce from "./addEventListenerOnce";
import * as array from "./array";
import * as object from "./object";
import mergeData from "vue-functional-data-merge/dist/lib.common.js";
import observeDom from "./observe-dom";
import warn from "./warn";
export { addEventListenerOnce, array, object, observeDom, warn }
export { addEventListenerOnce, array, mergeData, object, observeDom, warn };
@@ -63,10 +63,11 @@
},
"dependencies": {
"bootstrap": "^4.0.0-beta",
"jquery": "jquery@>=3.0.0",
"popper.js": "^1.11.0",
"tether": "latest",
"vue": "^2.4.2",
"jquery": "jquery@>=3.0.0",
"popper.js": "^1.11.0"
"vue-functional-data-merge": "^1.0.5"
},
"devDependencies": {
"@nuxtjs/pwa": "^0.2.0",
@@ -1,42 +1,35 @@
import { loadFixture, testVM } from '../helpers'
import { loadFixture, testVM } from "../helpers";
const variantList = [
'default',
'primary',
'success',
'info',
'warning',
'danger',
].map(variant => {
return { ref: `badge_${variant}`, variant }
})
const variantList = ["default", "primary", "success", "info", "warning", "danger"].map(variant => {
return { ref: `badge_${variant}`, variant };
});
describe('badge', async() => {
beforeEach(loadFixture('badge'))
testVM()
describe("badge", async () => {
beforeEach(loadFixture("badge"));
testVM();
it('should apply variant classes', async() => {
const { app: { $refs, $el } } = window
it("should apply variant classes", async () => {
const { app: { $refs } } = window;
expect($refs.badge_pill).toHaveAllClasses(['badge', 'badge-pill'])
expect($refs.badge_pill).toHaveAllClasses(["badge", "badge-pill"]);
variantList.forEach(({ ref, variant }) => {
const vm = $refs[ref][0]
expect(vm).toHaveAllClasses(['badge', `badge-${variant}`])
})
})
const vm = $refs[ref][0];
expect(vm).toHaveAllClasses(["badge", `badge-${variant}`]);
});
});
it('should apply secondary pill class when not passed variant', async() => {
const { app: { $refs, $el } } = window
it("should apply default pill class when not passed variant", async () => {
const { app: { $refs } } = window;
const vm = $refs.no_props
expect(vm).toHaveClass('badge-secondary')
})
const vm = $refs.no_props;
expect(vm).toHaveClass("badge-default");
});
it('should not apply pill class when not passed pill boolean prop', async() => {
const { app: { $refs, $el } } = window
it("should not apply pill class when not passed pill boolean prop", async () => {
const { app: { $refs } } = window;
const vm = $refs.no_props
expect(vm).not.toHaveClass('badge-pill')
})
const vm = $refs.no_props;
expect(vm).not.toHaveClass("badge-pill");
});
});
@@ -1,27 +1,15 @@
import { readFileSync } from 'fs';
import { resolve } from 'path';
import Vue from 'vue/dist/vue.common';
import BootstrapVue from '../lib';
import { readFileSync } from "fs";
import { resolve } from "path";
import Vue from "vue/dist/vue.common";
import BootstrapVue from "../lib";
const readFile = (path) => String(readFileSync(resolve(__dirname, '../examples', path)));
const throwIfNotVueInstance = vm => {
if (!vm instanceof Vue) {
// debugging breadcrumbs in case a non-Vue instance gets erroneously passed
// makes the error easier to fix than example: "Cannot read _prevClass of undefined"
throw new TypeError(`The matcher function expects Vue instance. Given ${typeof vm}`)
}
}
const throwIfNotArray = array => {
if (!Array.isArray(array)) {
throw new TypeError(`The matcher requires an array. Given ${typeof array}`)
}
}
const readFile = path => String(readFileSync(resolve(__dirname, "../examples", path)));
export function loadFixture(name) {
const template = readFile(`${name}/demo.html`);
const js = readFile(`${name}/demo.js`);
return async() => {
return async () => {
// Mount template
document.body.innerHTML = template;
@@ -38,14 +26,14 @@ export function loadFixture(name) {
}
export async function testVM() {
it(`vm mounts`, async() => {
it(`vm mounts`, async () => {
return expect(window.app.$el).toBeDefined();
});
}
export function nextTick() {
return new Promise((resolve, reject) => {
Vue.nextTick(resolve)
Vue.nextTick(resolve);
});
}
@@ -60,42 +48,105 @@ export function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}
const isVueInstance = vm => vm instanceof Vue;
const isHTMLElement = el => el instanceof HTMLElement;
const throwIfNotVueInstance = vm => {
if (!isVueInstance(vm)) {
// debugging breadcrumbs in case a non-Vue instance gets erroneously passed
// makes the error easier to fix than example: "Cannot read _prevClass of undefined"
console.error(vm);
throw new TypeError(`The matcher function expects Vue instance. Given ${typeof vm}`);
}
};
const throwIfNotHTMLElement = el => {
if (!isHTMLElement(el)) {
console.error(el);
throw new TypeError(`The matcher function expects an HTML Element. Given ${typeof el}`);
}
};
const throwIfNotArray = array => {
if (!Array.isArray(array)) {
throw new TypeError(`The matcher requires an array. Given ${typeof array}`);
}
};
const vmHasClass = (vm, className) => {
throwIfNotVueInstance(vm);
return vm.$el._prevClass.indexOf(className) !== -1;
};
/**
* @param {HTMLElement} el
* @param {string} className
* @return {boolean}
*/
const elHasClass = (el, className) => {
throwIfNotHTMLElement(el);
return el.classList.contains(className);
};
/**
* @param {Vue|HTMLElement} node
* @param {string} className
* @return {boolean}
*/
const hasClass = (node, className) => (isVueInstance(node) ? vmHasClass(node, className) : elHasClass(node, className));
const getVmTag = vm => vm.$options._componentTag;
const getHTMLTag = el => String(el.tagName).toLowerCase();
const getTagName = node => (isVueInstance(node) ? getVmTag(node) : getHTMLTag(node));
// Extend Jest marchers
expect.extend({
toHaveClass(vm, className) {
throwIfNotVueInstance(vm)
toHaveClass(node, className) {
return {
message: `expected <${vm.$options._componentTag}> to have class '${className}'`,
pass: vm.$el._prevClass.indexOf(className) !== -1,
message: `expected <${getTagName(node)}> to have class '${className}'`,
pass: hasClass(node, className)
};
},
toHaveAllClasses(vm, classList) {
throwIfNotVueInstance(vm)
throwIfNotArray(classList)
toHaveAllClasses(node, classList) {
throwIfNotArray(classList);
let pass = true;
let missingClassNames = []
let missingClassNames = [];
classList.forEach(className => {
if (!vm.$el._prevClass.includes(className)) {
pass = false
missingClassNames.push(className)
if (!hasClass(node, className)) {
pass = false;
missingClassNames.push(className);
}
})
});
const plural = missingClassNames.length > 1;
const classStr = classList.join(", ");
const missingClassStr = missingClassNames.join(", ");
const tagName = getTagName(node);
return {
// more debugging breadcrumbs
message: `Expected <${vm.$options._componentTag}> to have all classes in [ ${classList.join(', ')} ], but was missing [ ${missingClassNames.join(', ')} ] class${missingClassNames.length > 1 ? 'es' : ''}.`,
message: `Expected <${tagName}> to have all classes in [${classStr}], but was missing [${missingClassStr}] class${plural
? "es"
: ""}.`,
pass
}
};
},
toBeComponent(vm, componentTag) {
throwIfNotVueInstance(vm)
throwIfNotVueInstance(vm);
return {
message: `expected to be <${componentTag}>`,
pass: vm.$options._componentTag === componentTag
message: `Expected to be <${componentTag}>. Received: ${getVmTag(vm)}`,
pass: getVmTag(vm) === componentTag
};
},
toBeElement(el, tagName) {
throwIfNotHTMLElement(el);
return {
message: `Expected to be <${String(tagName).toLowerCase()}>. Received: ${el.tagName.toLowerCase()}`,
pass: el.tagName === String(tagName).toUpperCase()
};
}
});

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.

0 comments on commit 8c172c1

Please sign in to comment.