summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Slaughter <pslaughter@gitlab.com>2018-08-15 13:06:50 -0500
committerPaul Slaughter <pslaughter@gitlab.com>2018-08-21 08:20:38 -0500
commit243a209372cfe18c4963b6414036244762b3407d (patch)
tree0a1f17c77b65b4c37e2b982210c01877e897787f
parent97572d4223092a75fd9dbd1c72c947d933c6d520 (diff)
downloadgitlab-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.vue67
-rw-r--r--spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js162
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);
+ });
+ });
+});