Skip to content

Instantly share code, notes, and snippets.

@jaythomas
Last active March 19, 2024 12:45
Show Gist options
  • Save jaythomas/908b3a022a7111844409b70227b1d0bc to your computer and use it in GitHub Desktop.
Save jaythomas/908b3a022a7111844409b70227b1d0bc to your computer and use it in GitHub Desktop.
Markup.vue (Parse and display Markdown-ish text)
import { strictEqual } from 'assert';
import { describe, it } from '@jest/globals';
import { createLocalVue, mount, RouterLinkStub } from '@vue/test-utils';
import Vue from 'vue';
import { findId } from '~/helpers/test-utils';
import ElMarkup from './Markup';
const createWrapper = (options = {}) => {
const localVue = createLocalVue();
localVue.component('RouterLink', RouterLinkStub);
return mount(ElMarkup, {
localVue,
...options
});
};
// Convenience function to DRY up all the assertions below
const wrapperHas = (wrapper, expectedOutput) => {
strictEqual(
findId(wrapper, 'markup:text').element.innerHTML.trim(),
expectedOutput
);
};
const wrapperHasTitle = (wrapper, expectedOutput) => {
strictEqual(
findId(wrapper, 'markup:text').element.title.trim(),
expectedOutput
);
};
describe('ElMarkup component', () => {
it('does not parse markup by default', () => {
const wrapper = createWrapper({
propsData: {
parse: false,
val: '<p>Hello <b>W</b>orld</p>'
}
});
wrapperHas(wrapper, '&lt;p&gt;Hello &lt;b&gt;W&lt;/b&gt;orld&lt;/p&gt;');
wrapperHasTitle(wrapper, '');
});
it('renders html', () => {
const wrapper = createWrapper({
propsData: {
parse: true,
val: '<p>Hello&nbsp;<b>W</b>orld</p>&trade;🅪'
}
});
wrapperHas(wrapper, '<p>Hello&nbsp;<b>W</b>orld</p>™🅪');
wrapperHasTitle(wrapper, 'Hello&nbsp;World&trade;🅪');
});
it('renders uncolored tags', () => {
const wrapper = createWrapper({
propsData: {
parse: true,
val: '[My Tag]'
}
});
wrapperHas(wrapper, '<span class="tag">My Tag</span>');
wrapperHasTitle(wrapper, '[My Tag]');
});
it('renders colored tags', async () => {
const wrapper = createWrapper({
propsData: {
parse: true,
val: 'bl[Blue]'
}
});
wrapperHas(wrapper, '<span class="tag tag-bl">Blue</span>');
wrapperHasTitle(wrapper, '[Blue]');
wrapper.setProps({ val: 'db[Dark Blue]' });
await Vue.nextTick();
wrapperHas(wrapper, '<span class="tag tag-db">Dark Blue</span>');
wrapper.setProps({ val: 'lb[Light Blue]' });
await Vue.nextTick();
wrapperHas(wrapper, '<span class="tag tag-lb">Light Blue</span>');
wrapper.setProps({ val: 'lt[Light]' });
await Vue.nextTick();
wrapperHas(wrapper, '<span class="tag tag-lt">Light</span>');
wrapper.setProps({ val: 'wh[White]' });
await Vue.nextTick();
wrapperHas(wrapper, '<span class="tag tag-wh">White</span>');
wrapper.setProps({ val: 'dk[Dark]' });
await Vue.nextTick();
wrapperHas(wrapper, '<span class="tag tag-dk">Dark</span>');
wrapper.setProps({ val: 'dg[Dark (Gizmo)]' });
await Vue.nextTick();
wrapperHas(wrapper, '<span class="tag tag-dg">Dark (Gizmo)</span>');
wrapper.setProps({ val: 'bg[Black (Gizmo)]' });
await Vue.nextTick();
wrapperHas(wrapper, '<span class="tag tag-bg">Black (Gizmo)</span>');
wrapper.setProps({ val: 'yw[Yellow]' });
await Vue.nextTick();
wrapperHas(wrapper, '<span class="tag tag-yw">Yellow</span>');
wrapper.setProps({ val: 'or[Orange]' });
await Vue.nextTick();
wrapperHas(wrapper, '<span class="tag tag-or">Orange</span>');
wrapper.setProps({ val: 'pu[Purple]' });
await Vue.nextTick();
wrapperHas(wrapper, '<span class="tag tag-pu">Purple</span>');
wrapper.setProps({ val: 'gn[Green]' });
await Vue.nextTick();
wrapperHas(wrapper, '<span class="tag tag-gn">Green</span>');
});
it('renders urls as anchor tags', () => {
const wrapper = createWrapper({
propsData: {
parse: true,
val: 'This is a url: https://example.com'
}
});
wrapperHas(
wrapper,
'This is a url: <a target="_blank" href="https://example.com">https://example.com</a>'
);
wrapperHasTitle(
wrapper,
'This is a url: https://example.com'
);
});
it('renders markdown-style inline urls', () => {
const wrapper = createWrapper({
propsData: {
parse: true,
val: 'Now [this is the url](https://example.com)'
}
});
wrapperHas(
wrapper,
'Now <a target="_blank" href="https://example.com">this is the url</a>'
);
wrapperHasTitle(
wrapper,
'Now [this is the url](https://example.com)'
);
});
it('renders icon with slash', () => {
const wrapper = createWrapper({
propsData: {
parse: true,
val: '/compass/ Dashboard Text'
}
});
wrapperHas(
wrapper,
'<i class="icn fas fa-compass"></i> Dashboard Text'
);
wrapperHasTitle(
wrapper,
'Dashboard Text'
);
});
});
<template>
<!-- eslint-disable vue/no-v-html -->
<span>
<span
v-if="parse"
:data-test-id="textTestId + ':text'"
:title="stripMarkup(val)"
class="el-md"
v-html="parseMarkDown(val)"
></span>
<span
v-else
:data-test-id="textTestId + ':text'"
class="el-md"
>{{ val }}</span>
</span>
</template>
<script>
export default {
name: 'ElMarkup',
props: {
val: {
type: String,
required: true
},
parse: {
type: [Array, Boolean],
default: null
},
textTestId: {
type: String,
default: 'markup'
}
},
methods: {
parseMarkDown(val) {
// Simple rich text processing. See specs file for example usage.
val = ` ${val} `;
return val
.replace(
/(?:\s\/)([^/<\n]+)(?:\/\s)/g,
' <i class="icn fas fa-$1"></i> '
)
.replace(/(?:\*)([^*<\n]+)(?:\*)/g, '<strong>$1</strong>')
.replace(/(?:_)([^_<\n]+)(?:_)/g, '<i>$1</i>')
.replace(
/(\s(..)\[)([^[<\n]+)(?:\]\s)/gi,
' <span class="tag tag-$2">$3</span> '
)
.replace(
/(\s(..)\[)([^[<\n]+)(?:\](?!\()\s)/gi,
' <span class="tag tag-$2">$3</span> '
)
.replace(/(?:\[)([^[<\n]+)(?:\](?!\())/g, ' <span class="tag">$1</span> ')
.replace(
/\s(\b(?:https?:\/\/)(?:(?:[a-z,-]+\.)+)[^\s,]+\b)/g,
' <a target="_blank" href="$1">$1</a> '
)
.replace(
/\s\[([\w\s\d]+)\]\((https?:\/\/[\w\d./?=#-]+)\)\s$/gm,
' <a target="_blank" href="$2">$1</a> '
);
},
stripMarkup(val) {
return ` ${val} `
.replace(/(?:\s\/)([^/<\n]+)(?:\/\s)/g, '')
.replace(/(?:\*)([^*<\n]+)(?:\*)/g, '$1')
.replace(/(?:_)([^_<\n]+)(?:_)/g, '$1')
.replace(/<\/?\w+>/g, '')
.replace(
/(\s(..)\[)([^[<\n]+)(?:\]\s)/gi,
' [$3] '
)
.replace(
/(\s(..)\[)([^[<\n]+)(?:\](?!\()\s)/gi,
' [$3] '
);
}
}
};
</script>
<style lang="scss">
.el-md {
overflow: hidden;
text-overflow: ellipsis;
strong {
font-weight: bolder;
}
a {
text-decoration: none;
color: #5bcefd;
}
.tag {
font-size: 0.8em;
padding: 0 0.35em;
border-radius: 0.25em;
display: inline-block;
margin-bottom: 0.15em;
vertical-align: middle;
line-height: normal;
white-space: nowrap;
// gizmos stem-case tag
&.tag-bl {
color: #fff !important;
background: #2783a8 !important;
}
&.tag-db {
color: #fff !important;
background: #1b5c75 !important;
}
// gizmos handbook tag
&.tag-lb {
color: #1b5c75 !important;
background: #e1effc !important;
}
&.tag-lt,
&.tag-wh {
color: #222 !important;
background: #fff !important;
}
&.tag-dk {
color: #fff !important;
background: #222 !important;
}
// gizmos updated tag
&.tag-dg {
color: #fff !important;
background: #757575 !important;
}
// gizmos metric tag
&.tag-bg {
color: #fff !important;
background: #1A1A1A !important;
}
&.tag-yw {
color: #444 !important;
background: rgb(238, 206, 26) !important;
}
&.tag-or {
color: #fff !important;
background: #ed9137 !important;
}
&.tag-pu {
color: #fff !important;
background: #974bb7 !important;
}
&.tag-gn {
color: #fff !important;
background: #41b445 !important;
}
}
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment