aboutsummaryrefslogtreecommitdiff
path: root/js/tests/unit/util
diff options
context:
space:
mode:
Diffstat (limited to 'js/tests/unit/util')
-rw-r--r--js/tests/unit/util/index.spec.js2
-rw-r--r--js/tests/unit/util/sanitizer.spec.js25
-rw-r--r--js/tests/unit/util/swipe.spec.js281
-rw-r--r--js/tests/unit/util/template-factory.spec.js305
4 files changed, 611 insertions, 2 deletions
diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js
index 38e94dc6b..ccfe5e2c2 100644
--- a/js/tests/unit/util/index.spec.js
+++ b/js/tests/unit/util/index.spec.js
@@ -1,6 +1,4 @@
import * as Util from '../../../src/util/index'
-
-/** Test helpers */
import { clearFixture, getFixture } from '../../helpers/fixture'
describe('Util', () => {
diff --git a/js/tests/unit/util/sanitizer.spec.js b/js/tests/unit/util/sanitizer.spec.js
index 7379d221f..28d624c87 100644
--- a/js/tests/unit/util/sanitizer.spec.js
+++ b/js/tests/unit/util/sanitizer.spec.js
@@ -23,6 +23,31 @@ describe('Sanitizer', () => {
expect(result).not.toContain('href="javascript:alert(7)')
})
+ it('should sanitize template and work with multiple regex', () => {
+ const template = [
+ '<div>',
+ ' <a href="javascript:alert(7)" aria-label="This is a link" data-foo="bar">Click me</a>',
+ ' <span>Some content</span>',
+ '</div>'
+ ].join('')
+
+ const myDefaultAllowList = DefaultAllowlist
+ // With the default allow list
+ let result = sanitizeHtml(template, myDefaultAllowList, null)
+
+ // `data-foo` won't be present
+ expect(result).not.toContain('data-foo="bar"')
+
+ // Add the following regex too
+ myDefaultAllowList['*'].push(/^data-foo/)
+
+ result = sanitizeHtml(template, myDefaultAllowList, null)
+
+ expect(result).not.toContain('href="javascript:alert(7)') // This is in the default list
+ expect(result).toContain('aria-label="This is a link"') // This is in the default list
+ expect(result).toContain('data-foo="bar"') // We explicitly allow this
+ })
+
it('should allow aria attributes and safe attributes', () => {
const template = [
'<div aria-pressed="true">',
diff --git a/js/tests/unit/util/swipe.spec.js b/js/tests/unit/util/swipe.spec.js
new file mode 100644
index 000000000..474e34f65
--- /dev/null
+++ b/js/tests/unit/util/swipe.spec.js
@@ -0,0 +1,281 @@
+import { clearFixture, getFixture } from '../../helpers/fixture'
+import EventHandler from '../../../src/dom/event-handler'
+import Swipe from '../../../src/util/swipe'
+import { noop } from '../../../src/util'
+
+describe('Swipe', () => {
+ const { Simulator, PointerEvent } = window
+ const originWinPointerEvent = PointerEvent
+ const supportPointerEvent = Boolean(PointerEvent)
+
+ let fixtureEl
+ let swipeEl
+ const clearPointerEvents = () => {
+ window.PointerEvent = null
+ }
+
+ const restorePointerEvents = () => {
+ window.PointerEvent = originWinPointerEvent
+ }
+
+ // The headless browser does not support touch events, so we need to fake it
+ // in order to test that touch events are added properly
+ const defineDocumentElementOntouchstart = () => {
+ document.documentElement.ontouchstart = noop
+ }
+
+ const deleteDocumentElementOntouchstart = () => {
+ delete document.documentElement.ontouchstart
+ }
+
+ const mockSwipeGesture = (element, options = {}, type = 'touch') => {
+ Simulator.setType(type)
+ const _options = { deltaX: 0, deltaY: 0, ...options }
+
+ Simulator.gestures.swipe(element, _options)
+ }
+
+ beforeEach(() => {
+ fixtureEl = getFixture()
+ const cssStyle = [
+ '<style>',
+ ' #fixture .pointer-event {',
+ ' touch-action: pan-y;',
+ ' }',
+ ' #fixture div {',
+ ' width: 300px;',
+ ' height: 300px;',
+ ' }',
+ '</style>'
+ ].join('')
+
+ fixtureEl.innerHTML = '<div id="swipeEl"></div>' + cssStyle
+ swipeEl = fixtureEl.querySelector('div')
+ })
+
+ afterEach(() => {
+ clearFixture()
+ deleteDocumentElementOntouchstart()
+ })
+
+ describe('constructor', () => {
+ it('should add touch event listeners by default', () => {
+ defineDocumentElementOntouchstart()
+
+ spyOn(Swipe.prototype, '_initEvents').and.callThrough()
+ const swipe = new Swipe(swipeEl)
+ expect(swipe._initEvents).toHaveBeenCalled()
+ })
+
+ it('should not add touch event listeners if touch is not supported', () => {
+ spyOn(Swipe, 'isSupported').and.returnValue(false)
+
+ spyOn(Swipe.prototype, '_initEvents').and.callThrough()
+ const swipe = new Swipe(swipeEl)
+
+ expect(swipe._initEvents).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('Config', () => {
+ it('Test leftCallback', done => {
+ const spyRight = jasmine.createSpy('spy')
+ clearPointerEvents()
+ defineDocumentElementOntouchstart()
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ leftCallback: () => {
+ expect(spyRight).not.toHaveBeenCalled()
+ restorePointerEvents()
+ done()
+ },
+ rightCallback: spyRight
+ })
+
+ mockSwipeGesture(swipeEl, {
+ pos: [300, 10],
+ deltaX: -300
+ })
+ })
+
+ it('Test rightCallback', done => {
+ const spyLeft = jasmine.createSpy('spy')
+ clearPointerEvents()
+ defineDocumentElementOntouchstart()
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ rightCallback: () => {
+ expect(spyLeft).not.toHaveBeenCalled()
+ restorePointerEvents()
+ done()
+ },
+ leftCallback: spyLeft
+ })
+
+ mockSwipeGesture(swipeEl, {
+ pos: [10, 10],
+ deltaX: 300
+ })
+ })
+
+ it('Test endCallback', done => {
+ clearPointerEvents()
+ defineDocumentElementOntouchstart()
+ let isFirstTime = true
+
+ const callback = () => {
+ if (isFirstTime) {
+ isFirstTime = false
+ return
+ }
+
+ expect().nothing()
+ restorePointerEvents()
+ done()
+ }
+
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ endCallback: callback
+ })
+ mockSwipeGesture(swipeEl, {
+ pos: [10, 10],
+ deltaX: 300
+ })
+
+ mockSwipeGesture(swipeEl, {
+ pos: [300, 10],
+ deltaX: -300
+ })
+ })
+ })
+
+ describe('Functionality on PointerEvents', () => {
+ it('should not allow pinch with touch events', () => {
+ Simulator.setType('touch')
+ clearPointerEvents()
+ deleteDocumentElementOntouchstart()
+
+ const swipe = new Swipe(swipeEl)
+ spyOn(swipe, '_handleSwipe')
+
+ mockSwipeGesture(swipeEl, {
+ pos: [300, 10],
+ deltaX: -300,
+ deltaY: 0,
+ touches: 2
+ })
+
+ restorePointerEvents()
+ expect(swipe._handleSwipe).not.toHaveBeenCalled()
+ })
+
+ it('should allow swipeRight and call "rightCallback" with pointer events', done => {
+ if (!supportPointerEvent) {
+ expect().nothing()
+ done()
+ return
+ }
+
+ const style = '#fixture .pointer-event { touch-action: none !important; }'
+ fixtureEl.innerHTML += style
+
+ defineDocumentElementOntouchstart()
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ rightCallback: () => {
+ deleteDocumentElementOntouchstart()
+ expect().nothing()
+ done()
+ }
+ })
+
+ mockSwipeGesture(swipeEl, { deltaX: 300 }, 'pointer')
+ })
+
+ it('should allow swipeLeft and call "leftCallback" with pointer events', done => {
+ if (!supportPointerEvent) {
+ expect().nothing()
+ done()
+ return
+ }
+
+ const style = '#fixture .pointer-event { touch-action: none !important; }'
+ fixtureEl.innerHTML += style
+
+ defineDocumentElementOntouchstart()
+ // eslint-disable-next-line no-new
+ new Swipe(swipeEl, {
+ leftCallback: () => {
+ expect().nothing()
+ deleteDocumentElementOntouchstart()
+ done()
+ }
+ })
+
+ mockSwipeGesture(swipeEl, {
+ pos: [300, 10],
+ deltaX: -300
+ }, 'pointer')
+ })
+ })
+
+ describe('Dispose', () => {
+ it('should call EventHandler.off', () => {
+ defineDocumentElementOntouchstart()
+ spyOn(EventHandler, 'off').and.callThrough()
+ const swipe = new Swipe(swipeEl)
+
+ swipe.dispose()
+ expect(EventHandler.off).toHaveBeenCalledWith(swipeEl, '.bs.swipe')
+ })
+
+ it('should destroy', () => {
+ const addEventSpy = spyOn(fixtureEl, 'addEventListener').and.callThrough()
+ const removeEventSpy = spyOn(EventHandler, 'off').and.callThrough()
+ defineDocumentElementOntouchstart()
+
+ const swipe = new Swipe(fixtureEl)
+
+ const expectedArgs =
+ swipe._supportPointerEvents ?
+ [
+ ['pointerdown', jasmine.any(Function), jasmine.any(Boolean)],
+ ['pointerup', jasmine.any(Function), jasmine.any(Boolean)]
+ ] :
+ [
+ ['touchstart', jasmine.any(Function), jasmine.any(Boolean)],
+ ['touchmove', jasmine.any(Function), jasmine.any(Boolean)],
+ ['touchend', jasmine.any(Function), jasmine.any(Boolean)]
+ ]
+
+ expect(addEventSpy.calls.allArgs()).toEqual(expectedArgs)
+
+ swipe.dispose()
+
+ expect(removeEventSpy).toHaveBeenCalledWith(fixtureEl, '.bs.swipe')
+ deleteDocumentElementOntouchstart()
+ })
+ })
+
+ describe('"isSupported" static', () => {
+ it('should return "true" if "touchstart" exists in document element)', () => {
+ Object.defineProperty(window.navigator, 'maxTouchPoints', () => 0)
+ defineDocumentElementOntouchstart()
+
+ expect(Swipe.isSupported()).toBeTrue()
+ })
+
+ it('should return "false" if "touchstart" not exists in document element and "navigator.maxTouchPoints" are zero (0)', () => {
+ Object.defineProperty(window.navigator, 'maxTouchPoints', () => 0)
+ deleteDocumentElementOntouchstart()
+
+ if ('ontouchstart' in document.documentElement) {
+ expect().nothing()
+ return
+ }
+
+ expect(Swipe.isSupported()).toBeFalse()
+ })
+ })
+})
diff --git a/js/tests/unit/util/template-factory.spec.js b/js/tests/unit/util/template-factory.spec.js
new file mode 100644
index 000000000..842c480c2
--- /dev/null
+++ b/js/tests/unit/util/template-factory.spec.js
@@ -0,0 +1,305 @@
+import { clearFixture, getFixture } from '../../helpers/fixture'
+import TemplateFactory from '../../../src/util/template-factory'
+
+describe('TemplateFactory', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ describe('NAME', () => {
+ it('should return plugin NAME', () => {
+ expect(TemplateFactory.NAME).toEqual('TemplateFactory')
+ })
+ })
+
+ describe('Default', () => {
+ it('should return plugin default config', () => {
+ expect(TemplateFactory.Default).toEqual(jasmine.any(Object))
+ })
+ })
+
+ describe('toHtml', () => {
+ describe('Sanitization', () => {
+ it('should use "sanitizeHtml" to sanitize template', () => {
+ const factory = new TemplateFactory({
+ sanitize: true,
+ template: '<div><a href="javascript:alert(7)">Click me</a></div>'
+ })
+ const spy = spyOn(factory, '_maybeSanitize').and.callThrough()
+
+ expect(factory.toHtml().innerHTML).not.toContain('href="javascript:alert(7)')
+ expect(spy).toHaveBeenCalled()
+ })
+
+ it('should not sanitize template', () => {
+ const factory = new TemplateFactory({
+ sanitize: false,
+ template: '<div><a href="javascript:alert(7)">Click me</a></div>'
+ })
+ const spy = spyOn(factory, '_maybeSanitize').and.callThrough()
+
+ expect(factory.toHtml().innerHTML).toContain('href="javascript:alert(7)')
+ expect(spy).toHaveBeenCalled()
+ })
+
+ it('should use "sanitizeHtml" to sanitize content', () => {
+ const factory = new TemplateFactory({
+ sanitize: true,
+ html: true,
+ template: '<div id="foo"></div>',
+ content: { '#foo': '<a href="javascript:alert(7)">Click me</a>' }
+ })
+ expect(factory.toHtml().innerHTML).not.toContain('href="javascript:alert(7)')
+ })
+
+ it('should not sanitize content', () => {
+ const factory = new TemplateFactory({
+ sanitize: false,
+ html: true,
+ template: '<div id="foo"></div>',
+ content: { '#foo': '<a href="javascript:alert(7)">Click me</a>' }
+ })
+ expect(factory.toHtml().innerHTML).toContain('href="javascript:alert(7)')
+ })
+
+ it('should sanitize content only if "config.html" is enabled', () => {
+ const factory = new TemplateFactory({
+ sanitize: true,
+ html: false,
+ template: '<div id="foo"></div>',
+ content: { '#foo': '<a href="javascript:alert(7)">Click me</a>' }
+ })
+ const spy = spyOn(factory, '_maybeSanitize').and.callThrough()
+
+ expect(spy).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('Extra Class', () => {
+ it('should add extra class', () => {
+ const factory = new TemplateFactory({
+ extraClass: 'testClass'
+ })
+ expect(factory.toHtml().classList.contains('testClass')).toBeTrue()
+ })
+
+ it('should add extra classes', () => {
+ const factory = new TemplateFactory({
+ extraClass: 'testClass testClass2'
+ })
+ expect(factory.toHtml().classList.contains('testClass')).toBeTrue()
+ expect(factory.toHtml().classList.contains('testClass2')).toBeTrue()
+ })
+
+ it('should resolve class if function is given', () => {
+ const factory = new TemplateFactory({
+ extraClass: arg => {
+ expect(arg).toEqual(factory)
+ return 'testClass'
+ }
+ })
+
+ expect(factory.toHtml().classList.contains('testClass')).toBeTrue()
+ })
+ })
+ })
+
+ describe('Content', () => {
+ it('add simple text content', () => {
+ const template = [
+ '<div>' +
+ '<div class="foo"></div>' +
+ '<div class="foo2"></div>' +
+ '</div>'
+ ].join(' ')
+
+ const factory = new TemplateFactory({
+ template,
+ content: {
+ '.foo': 'bar',
+ '.foo2': 'bar2'
+ }
+ })
+
+ const html = factory.toHtml()
+ expect(html.querySelector('.foo').textContent).toBe('bar')
+ expect(html.querySelector('.foo2').textContent).toBe('bar2')
+ })
+
+ it('should not fill template if selector not exists', () => {
+ const factory = new TemplateFactory({
+ sanitize: true,
+ html: true,
+ template: '<div id="foo"></div>',
+ content: { '#bar': 'test' }
+ })
+
+ expect(factory.toHtml().outerHTML).toBe('<div id="foo"></div>')
+ })
+
+ it('should remove template selector, if content is null', () => {
+ const factory = new TemplateFactory({
+ sanitize: true,
+ html: true,
+ template: '<div><div id="foo"></div></div>',
+ content: { '#foo': null }
+ })
+
+ expect(factory.toHtml().outerHTML).toBe('<div></div>')
+ })
+
+ it('should resolve content if is function', () => {
+ const factory = new TemplateFactory({
+ sanitize: true,
+ html: true,
+ template: '<div><div id="foo"></div></div>',
+ content: { '#foo': () => null }
+ })
+
+ expect(factory.toHtml().outerHTML).toBe('<div></div>')
+ })
+
+ it('if content is element and "config.html=false", should put content\'s textContent', () => {
+ fixtureEl.innerHTML = '<div>foo<span>bar</span></div>'
+ const contentElement = fixtureEl.querySelector('div')
+
+ const factory = new TemplateFactory({
+ html: false,
+ template: '<div><div id="foo"></div></div>',
+ content: { '#foo': contentElement }
+ })
+
+ const fooEl = factory.toHtml().querySelector('#foo')
+ expect(fooEl.innerHTML).not.toBe(contentElement.innerHTML)
+ expect(fooEl.textContent).toBe(contentElement.textContent)
+ expect(fooEl.textContent).toBe('foobar')
+ })
+
+ it('if content is element and "config.html=true", should put content\'s outerHtml as child', () => {
+ fixtureEl.innerHTML = '<div>foo<span>bar</span></div>'
+ const contentElement = fixtureEl.querySelector('div')
+
+ const factory = new TemplateFactory({
+ html: true,
+ template: '<div><div id="foo"></div></div>',
+ content: { '#foo': contentElement }
+ })
+
+ const fooEl = factory.toHtml().querySelector('#foo')
+ expect(fooEl.innerHTML).toBe(contentElement.outerHTML)
+ expect(fooEl.textContent).toBe(contentElement.textContent)
+ })
+ })
+
+ describe('getContent', () => {
+ it('should get content as array', () => {
+ const factory = new TemplateFactory({
+ content: {
+ '.foo': 'bar',
+ '.foo2': 'bar2'
+ }
+ })
+ expect(factory.getContent()).toEqual(['bar', 'bar2'])
+ })
+
+ it('should filter empties', () => {
+ const factory = new TemplateFactory({
+ content: {
+ '.foo': 'bar',
+ '.foo2': '',
+ '.foo3': null,
+ '.foo4': () => 2,
+ '.foo5': () => null
+ }
+ })
+ expect(factory.getContent()).toEqual(['bar', 2])
+ })
+ })
+
+ describe('hasContent', () => {
+ it('should return true, if it has', () => {
+ const factory = new TemplateFactory({
+ content: {
+ '.foo': 'bar',
+ '.foo2': 'bar2',
+ '.foo3': ''
+ }
+ })
+ expect(factory.hasContent()).toBeTrue()
+ })
+
+ it('should return false, if filtered content is empty', () => {
+ const factory = new TemplateFactory({
+ content: {
+ '.foo2': '',
+ '.foo3': null,
+ '.foo4': () => null
+ }
+ })
+ expect(factory.hasContent()).toBeFalse()
+ })
+ })
+ describe('changeContent', () => {
+ it('should change Content', () => {
+ const template = [
+ '<div>' +
+ '<div class="foo"></div>' +
+ '<div class="foo2"></div>' +
+ '</div>'
+ ].join(' ')
+
+ const factory = new TemplateFactory({
+ template,
+ content: {
+ '.foo': 'bar',
+ '.foo2': 'bar2'
+ }
+ })
+
+ const html = selector => factory.toHtml().querySelector(selector).textContent
+ expect(html('.foo')).toEqual('bar')
+ expect(html('.foo2')).toEqual('bar2')
+ factory.changeContent({
+ '.foo': 'test',
+ '.foo2': 'test2'
+ })
+
+ expect(html('.foo')).toEqual('test')
+ expect(html('.foo2')).toEqual('test2')
+ })
+
+ it('should change only the given, content', () => {
+ const template = [
+ '<div>' +
+ '<div class="foo"></div>' +
+ '<div class="foo2"></div>' +
+ '</div>'
+ ].join(' ')
+
+ const factory = new TemplateFactory({
+ template,
+ content: {
+ '.foo': 'bar',
+ '.foo2': 'bar2'
+ }
+ })
+
+ const html = selector => factory.toHtml().querySelector(selector).textContent
+ expect(html('.foo')).toEqual('bar')
+ expect(html('.foo2')).toEqual('bar2')
+ factory.changeContent({
+ '.foo': 'test',
+ '.wrong': 'wrong'
+ })
+
+ expect(html('.foo')).toEqual('test')
+ expect(html('.foo2')).toEqual('bar2')
+ })
+ })
+})