Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(badge): functional component #820

Merged
merged 3 commits into from
Aug 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions lib/components/badge.js
Original file line number Diff line number Diff line change
@@ -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
);
}
};
28 changes: 0 additions & 28 deletions lib/components/badge.vue

This file was deleted.

2 changes: 1 addition & 1 deletion lib/components/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down
13 changes: 7 additions & 6 deletions lib/utils/index.js
Original file line number Diff line number Diff line change
@@ -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 };
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@
},
"dependencies": {
"bootstrap": "^4.0.0-beta",
"jquery": "jquery@>=3.0.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize we have a jQuery dependency now...

Copy link
Member Author

@alexsasharegan alexsasharegan Aug 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... when did that get in there? Is that in master?

Nope, not in master. Is someone working on something that uses that? I'm gonna kill it and we'll find out later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be in there for a temp solution for something.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not in dev branch either. only 1.x branch

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Say bye-bye 👋

"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",
Expand Down
55 changes: 24 additions & 31 deletions tests/components/badge.spec.js
Original file line number Diff line number Diff line change
@@ -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");
});
});
127 changes: 89 additions & 38 deletions tests/helpers.js
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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);
});
}

Expand All @@ -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()
};
}
});
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6648,6 +6648,10 @@ vue-class-component@^5.0.0:
version "5.0.2"
resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-5.0.2.tgz#3dcdc005c58c4e88d8ec2d46e01c74f4d90135c8"

vue-functional-data-merge@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-1.0.5.tgz#228c3bf5a531ec6d60881038e08457cece2691a2"

vue-hot-reload-api@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.1.0.tgz#9ca58a6e0df9078554ce1708688b6578754d86de"
Expand Down