diff options
author | Paul Slaughter <pslaughter@gitlab.com> | 2018-08-15 13:06:50 -0500 |
---|---|---|
committer | Paul Slaughter <pslaughter@gitlab.com> | 2018-08-21 08:20:38 -0500 |
commit | 243a209372cfe18c4963b6414036244762b3407d (patch) | |
tree | 0a1f17c77b65b4c37e2b982210c01877e897787f | |
parent | 97572d4223092a75fd9dbd1c72c947d933c6d520 (diff) | |
download | gitlab-ce-243a209372cfe18c4963b6414036244762b3407d.tar.gz |
Create TooltipOnTruncate component to show tooltip only when needed
-rw-r--r-- | app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue | 67 | ||||
-rw-r--r-- | spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js | 162 |
2 files changed, 229 insertions, 0 deletions
diff --git a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue new file mode 100644 index 00000000000..125826da6c3 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue @@ -0,0 +1,67 @@ +<script> +import _ from 'underscore'; +import tooltip from '../directives/tooltip'; + +export default { + directives: { + tooltip, + }, + props: { + title: { + type: String, + required: false, + default: '', + }, + placement: { + type: String, + required: false, + default: 'top', + }, + truncateTarget: { + type: [String, Function], + required: false, + default: '', + }, + }, + data() { + return { + showTooltip: false, + }; + }, + mounted() { + const target = this.selectTarget(); + + if (target && target.scrollWidth > target.offsetWidth) { + this.showTooltip = true; + } + }, + methods: { + selectTarget() { + if (_.isFunction(this.truncateTarget)) { + return this.truncateTarget(this.$el); + } else if (this.truncateTarget === 'child') { + return this.$el.childNodes[0]; + } + + return this.$el; + }, + }, +}; +</script> + +<template> + <span + v-tooltip + v-if="showTooltip" + :title="title" + :data-placement="placement" + class="js-show-tooltip" + > + <slot></slot> + </span> + <span + v-else + > + <slot></slot> + </span> +</template> diff --git a/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js b/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js new file mode 100644 index 00000000000..8465757deb6 --- /dev/null +++ b/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js @@ -0,0 +1,162 @@ +import { mountComponentWithRender } from 'spec/helpers/vue_mount_component_helper'; +import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; + +const TEST_TITLE = 'lorem-ipsum-dolar-sit-amit-consectur-adipiscing-elit-sed-do'; +const CLASS_SHOW_TOOLTIP = 'js-show-tooltip'; +const STYLE_TRUNCATED = { + display: 'inline-block', + 'max-width': '20px', +}; +const STYLE_NORMAL = { + display: 'inline-block', + 'max-width': '1000px', +}; + +function mountTooltipOnTruncate(options, createChildren) { + return mountComponentWithRender(h => h(TooltipOnTruncate, options, createChildren(h)), '#app'); +} + +describe('TooltipOnTruncate component', () => { + let vm; + + beforeEach(() => { + const el = document.createElement('div'); + el.id = 'app'; + document.body.appendChild(el); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('with default target', () => { + it('renders tooltip if truncated', done => { + const options = { + style: STYLE_TRUNCATED, + props: { + title: TEST_TITLE, + }, + }; + + vm = mountTooltipOnTruncate(options, () => [TEST_TITLE]); + + vm.$nextTick() + .then(() => { + expect(vm.$el).toHaveClass(CLASS_SHOW_TOOLTIP); + expect(vm.$el).toHaveData('original-title', TEST_TITLE); + expect(vm.$el).toHaveData('placement', 'top'); + }) + .then(done) + .catch(done.fail); + }); + + it('does not render tooltip if normal', done => { + const options = { + style: STYLE_NORMAL, + props: { + title: TEST_TITLE, + }, + }; + + vm = mountTooltipOnTruncate(options, () => [TEST_TITLE]); + + vm.$nextTick() + .then(() => { + expect(vm.$el).not.toHaveClass(CLASS_SHOW_TOOLTIP); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('with child target', () => { + it('renders tooltip if truncated', done => { + const options = { + style: STYLE_NORMAL, + props: { + title: TEST_TITLE, + truncateTarget: 'child', + }, + }; + + vm = mountTooltipOnTruncate(options, (h) => [ + h('a', { style: STYLE_TRUNCATED }, TEST_TITLE), + ]); + + vm.$nextTick() + .then(() => { + expect(vm.$el).toHaveClass(CLASS_SHOW_TOOLTIP); + }) + .then(done) + .catch(done.fail); + }); + + it('does not render tooltip if normal', done => { + const options = { + props: { + title: TEST_TITLE, + truncateTarget: 'child', + }, + }; + + vm = mountTooltipOnTruncate(options, (h) => [ + h('a', { style: STYLE_NORMAL }, TEST_TITLE), + ]); + + vm.$nextTick() + .then(() => { + expect(vm.$el).not.toHaveClass(CLASS_SHOW_TOOLTIP); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('with fn target', () => { + it('renders tooltip if truncated', done => { + const options = { + style: STYLE_NORMAL, + props: { + title: TEST_TITLE, + truncateTarget: (el) => el.childNodes[1], + }, + }; + + vm = mountTooltipOnTruncate(options, (h) => [ + h('a', { style: STYLE_NORMAL }, TEST_TITLE), + h('span', { style: STYLE_TRUNCATED }, TEST_TITLE), + ]); + + vm.$nextTick() + .then(() => { + expect(vm.$el).toHaveClass(CLASS_SHOW_TOOLTIP); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('placement', () => { + it('sets data-placement when tooltip is rendered', done => { + const options = { + props: { + title: TEST_TITLE, + truncateTarget: 'child', + placement: 'bottom', + }, + }; + + vm = mountTooltipOnTruncate(options, (h) => [ + h('a', { style: STYLE_TRUNCATED }, TEST_TITLE), + ]); + + vm.$nextTick() + .then(() => { + expect(vm.$el).toHaveClass(CLASS_SHOW_TOOLTIP); + expect(vm.$el).toHaveData('placement', options.props.placement); + }) + .then(done) + .catch(done.fail); + }); + }); +}); |