UNPKG

@gitlab/ui

Version:
150 lines (120 loc) 4.5 kB
import { shallowMount } from '@vue/test-utils'; import { forbiddenDataAttrs } from './constants'; import { SafeHtmlDirective as safeHtml } from './safe_html'; /* eslint-disable no-script-url */ const invalidProtocolUrls = [ 'javascript:alert(1)', 'jAvascript:alert(1)', 'data:text/html,<script>alert(1);</script>', ' javascript:', 'javascript :', ]; /* eslint-enable no-script-url */ const validProtocolUrls = ['slack://open', 'x-devonthink-item://90909', 'x-devonthink-item:90909']; describe('safe html directive', () => { let wrapper; const createComponent = ({ template, html, config } = {}) => { const defaultTemplate = `<div v-safe-html="rawHtml"></div>`; const defaultHtml = 'hello <script>alert(1)</script>world'; const component = { directives: { safeHtml, }, data() { return { rawHtml: html || defaultHtml, config: config || {}, }; }, template: template || defaultTemplate, }; wrapper = shallowMount(component); }; describe('default', () => { it('should remove the script tag', () => { createComponent(); expect(wrapper.html()).toEqual('<div>hello world</div>'); }); it('should remove javascript hrefs', () => { createComponent({ html: '<a href="javascript:prompt(1)">click here</a>' }); expect(wrapper.html()).toEqual('<div><a>click here</a></div>'); }); it('should remove style tags', () => { createComponent({ html: '<style>p {width:50%;}</style>' }); expect(wrapper.html()).toEqual('<div></div>'); }); it('should remove mstyle tags', () => { createComponent({ html: '<math><mstyle displaystyle="true"></mstyle></math>' }); expect(wrapper.html()).toEqual('<div><math></math></div>'); }); it('should remove form tags', () => { createComponent({ html: '<form method="post" action="path"></form>' }); expect(wrapper.html()).toEqual('<div></div>'); }); it('should remove any existing children', () => { createComponent({ template: `<div v-safe-html="rawHtml">foo <i>bar</i></div>`, }); expect(wrapper.html()).toEqual('<div>hello world</div>'); }); describe('with non-http links', () => { it.each(validProtocolUrls)('should allow %s', (url) => { createComponent({ html: `<a href="${url}">internal link</a>`, }); expect(wrapper.html()).toContain(`<a href="${url}">internal link</a>`); }); it.each(invalidProtocolUrls)('should not allow %s', (url) => { createComponent({ html: `<a href="${url}">internal link</a>`, }); expect(wrapper.html()).toContain(`<a>internal link</a>`); }); }); describe('handles data attributes correctly', () => { const allowedDataAttrs = ['data-safe', 'data-random']; it.each(forbiddenDataAttrs)('removes dangerous `%s` attribute', (attr) => { const html = `<a ${attr}="true"></a>`; createComponent({ html }); expect(wrapper.html()).not.toContain(html); }); it.each(allowedDataAttrs)('does not remove allowed `%s` attribute', (attr) => { const html = `<a ${attr}="true"></a>`; createComponent({ html }); expect(wrapper.html()).toContain(html); }); }); }); describe('advance config', () => { const template = '<div v-safe-html:[config]="rawHtml"></div>'; it('should only allow <b> tags', () => { createComponent({ template, html: '<a href="javascript:prompt(1)"><b>click here</b></a>', config: { ALLOWED_TAGS: ['b'] }, }); expect(wrapper.html()).toEqual('<div><b>click here</b></div>'); }); it('should strip all html tags', () => { createComponent({ template, html: '<a href="javascript:prompt(1)"><u>click here</u></a>', config: { ALLOWED_TAGS: [] }, }); expect(wrapper.html()).toEqual('<div>click here</div>'); }); }); describe('unbind', () => { it('should clear the text content during unbind', () => { createComponent(); wrapper.destroy(); expect(wrapper.element.textContent).toEqual(''); }); it('should clear the text content with custom HTML during unbind', () => { const customHtml = '<div>custom html</div>'; createComponent({ html: customHtml }); wrapper.destroy(); expect(wrapper.element.textContent).toEqual(''); }); }); });