Merging upstream version 5.3.0+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
a87fc4fcc7
commit
e92720b6a7
605 changed files with 15320 additions and 9495 deletions
|
@ -1,6 +1,7 @@
|
|||
/* eslint-env node */
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
'use strict'
|
||||
|
||||
const browsers = {
|
||||
safariMac: {
|
||||
base: 'BrowserStack',
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable import/extensions, import/no-unassigned-import */
|
||||
|
||||
import Tooltip from '../../dist/tooltip'
|
||||
import '../../dist/carousel'
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-env node */
|
||||
'use strict'
|
||||
|
||||
const commonjs = require('@rollup/plugin-commonjs')
|
||||
const configRollup = require('./rollup.bundle')
|
||||
const configRollup = require('./rollup.bundle.js')
|
||||
|
||||
const config = {
|
||||
...configRollup,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-env node */
|
||||
'use strict'
|
||||
|
||||
const { babel } = require('@rollup/plugin-babel')
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint-env node */
|
||||
|
||||
'use strict'
|
||||
|
||||
const path = require('node:path')
|
||||
|
@ -8,7 +6,7 @@ const { babel } = require('@rollup/plugin-babel')
|
|||
const istanbul = require('rollup-plugin-istanbul')
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
||||
const replace = require('@rollup/plugin-replace')
|
||||
const { browsers } = require('./browsers')
|
||||
const { browsers } = require('./browsers.js')
|
||||
|
||||
const ENV = process.env
|
||||
const BROWSERSTACK = Boolean(ENV.BROWSERSTACK)
|
||||
|
@ -105,7 +103,7 @@ if (BROWSERSTACK) {
|
|||
config.browserStack = {
|
||||
username: ENV.BROWSER_STACK_USERNAME,
|
||||
accessKey: ENV.BROWSER_STACK_ACCESS_KEY,
|
||||
build: `bootstrap-${ENV.GITHUB_SHA ? ENV.GITHUB_SHA.slice(0, 7) + '-' : ''}${new Date().toISOString()}`,
|
||||
build: `bootstrap-${ENV.GITHUB_SHA ? `${ENV.GITHUB_SHA.slice(0, 7)}-` : ''}${new Date().toISOString()}`,
|
||||
project: 'Bootstrap',
|
||||
retryLimit: 2
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"extends": [
|
||||
"../../../.eslintrc.json"
|
||||
],
|
||||
"env": {
|
||||
"jasmine": true
|
||||
},
|
||||
"rules": {
|
||||
"unicorn/consistent-function-scoping": "off",
|
||||
"unicorn/no-useless-undefined": "off",
|
||||
"unicorn/prefer-add-event-listener": "off"
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import Alert from '../../src/alert'
|
||||
import { getTransitionDurationFromElement } from '../../src/util/index'
|
||||
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import Alert from '../../src/alert.js'
|
||||
import { getTransitionDurationFromElement } from '../../src/util/index.js'
|
||||
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Alert', () => {
|
||||
let fixtureEl
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import BaseComponent from '../../src/base-component'
|
||||
import { clearFixture, getFixture } from '../helpers/fixture'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
import { noop } from '../../src/util'
|
||||
import BaseComponent from '../../src/base-component.js'
|
||||
import EventHandler from '../../src/dom/event-handler.js'
|
||||
import { noop } from '../../src/util/index.js'
|
||||
import { clearFixture, getFixture } from '../helpers/fixture.js'
|
||||
|
||||
class DummyClass extends BaseComponent {
|
||||
constructor(element) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Button from '../../src/button'
|
||||
import { getFixture, clearFixture, jQueryMock } from '../helpers/fixture'
|
||||
import Button from '../../src/button.js'
|
||||
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Button', () => {
|
||||
let fixtureEl
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import Carousel from '../../src/carousel'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import { isRTL, noop } from '../../src/util/index'
|
||||
import Swipe from '../../src/util/swipe'
|
||||
import Carousel from '../../src/carousel.js'
|
||||
import EventHandler from '../../src/dom/event-handler.js'
|
||||
import { isRTL, noop } from '../../src/util/index.js'
|
||||
import Swipe from '../../src/util/swipe.js'
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Carousel', () => {
|
||||
const { Simulator, PointerEvent } = window
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Collapse from '../../src/collapse'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import Collapse from '../../src/collapse.js'
|
||||
import EventHandler from '../../src/dom/event-handler.js'
|
||||
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Collapse', () => {
|
||||
let fixtureEl
|
||||
|
@ -277,25 +277,25 @@ describe('Collapse', () => {
|
|||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="parentGroup" class="accordion">',
|
||||
' <div id="parentHeader" class="accordion-header">',
|
||||
' <button data-bs-target="#parentContent" data-bs-toggle="collapse" role="button" class="accordion-toggle">Parent</button>',
|
||||
' <div class="accordion-header">',
|
||||
' <button data-bs-target="#parentContent" data-bs-toggle="collapse" class="accordion-toggle">Parent</button>',
|
||||
' </div>',
|
||||
' <div id="parentContent" class="accordion-collapse collapse" aria-labelledby="parentHeader" data-bs-parent="#parentGroup">',
|
||||
' <div id="parentContent" class="accordion-collapse collapse" data-bs-parent="#parentGroup">',
|
||||
' <div class="accordion-body">',
|
||||
' <div id="childGroup" class="accordion">',
|
||||
' <div class="accordion-item">',
|
||||
' <div id="childHeader1" class="accordion-header">',
|
||||
' <button data-bs-target="#childContent1" data-bs-toggle="collapse" role="button" class="accordion-toggle">Child 1</button>',
|
||||
' <div class="accordion-header">',
|
||||
' <button data-bs-target="#childContent1" data-bs-toggle="collapse" class="accordion-toggle">Child 1</button>',
|
||||
' </div>',
|
||||
' <div id="childContent1" class="accordion-collapse collapse" aria-labelledby="childHeader1" data-bs-parent="#childGroup">',
|
||||
' <div id="childContent1" class="accordion-collapse collapse" data-bs-parent="#childGroup">',
|
||||
' <div>content</div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' <div class="accordion-item">',
|
||||
' <div id="childHeader2" class="accordion-header">',
|
||||
' <button data-bs-target="#childContent2" data-bs-toggle="collapse" role="button" class="accordion-toggle">Child 2</button>',
|
||||
' <div class="accordion-header">',
|
||||
' <button data-bs-target="#childContent2" data-bs-toggle="collapse" class="accordion-toggle">Child 2</button>',
|
||||
' </div>',
|
||||
' <div id="childContent2" class="accordion-collapse collapse" aria-labelledby="childHeader2" data-bs-parent="#childGroup">',
|
||||
' <div id="childContent2" class="accordion-collapse collapse" data-bs-parent="#childGroup">',
|
||||
' <div>content</div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
|
@ -338,12 +338,12 @@ describe('Collapse', () => {
|
|||
fixtureEl.innerHTML = [
|
||||
'<div class="accordion" id="accordionExample">',
|
||||
' <div class="accordion-item">',
|
||||
' <h2 class="accordion-header" id="headingOne">',
|
||||
' <h2 class="accordion-header">',
|
||||
' <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">',
|
||||
' Accordion Item #1',
|
||||
' </button>',
|
||||
' </h2>',
|
||||
' <div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordionExample">',
|
||||
' <div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#accordionExample">',
|
||||
' <div class="accordion-body">',
|
||||
' <nav>',
|
||||
' <div class="nav nav-tabs" id="nav-tab" role="tablist">',
|
||||
|
@ -640,11 +640,11 @@ describe('Collapse', () => {
|
|||
'<div id="accordion">',
|
||||
' <div class="item">',
|
||||
' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>',
|
||||
' <div id="collapseOne" class="collapse" role="tabpanel" aria-labelledby="headingThree" data-bs-parent="#accordion"></div>',
|
||||
' <div id="collapseOne" class="collapse" role="tabpanel" data-bs-parent="#accordion"></div>',
|
||||
' </div>',
|
||||
' <div class="item">',
|
||||
' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>',
|
||||
' <div id="collapseTwo" class="collapse show" role="tabpanel" aria-labelledby="headingTwo" data-bs-parent="#accordion"></div>',
|
||||
' <div id="collapseTwo" class="collapse show" role="tabpanel" data-bs-parent="#accordion"></div>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
@ -699,13 +699,13 @@ describe('Collapse', () => {
|
|||
' <div class="col-lg-6">',
|
||||
' <div class="item">',
|
||||
' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>',
|
||||
' <div id="collapseOne" class="collapse" role="tabpanel" aria-labelledby="headingThree" data-bs-parent="#accordion"></div>',
|
||||
' <div id="collapseOne" class="collapse" role="tabpanel" data-bs-parent="#accordion"></div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' <div class="col-lg-6">',
|
||||
' <div class="item">',
|
||||
' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>',
|
||||
' <div id="collapseTwo" class="collapse show" role="tabpanel" aria-labelledby="headingTwo" data-bs-parent="#accordion"></div>',
|
||||
' <div id="collapseTwo" class="collapse show" role="tabpanel" data-bs-parent="#accordion"></div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
|
@ -829,18 +829,18 @@ describe('Collapse', () => {
|
|||
'<div id="accordion">',
|
||||
' <div class="item">',
|
||||
' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>',
|
||||
' <div id="collapseOne" data-bs-parent="#accordion" class="collapse" role="tabpanel" aria-labelledby="headingThree">',
|
||||
' <div id="collapseOne" data-bs-parent="#accordion" class="collapse" role="tabpanel">',
|
||||
' <div id="nestedAccordion">',
|
||||
' <div class="item">',
|
||||
' <a id="nestedLinkTrigger" data-bs-toggle="collapse" href="#nestedCollapseOne" aria-expanded="false" aria-controls="nestedCollapseOne"></a>',
|
||||
' <div id="nestedCollapseOne" data-bs-parent="#nestedAccordion" class="collapse" role="tabpanel" aria-labelledby="headingThree"></div>',
|
||||
' <div id="nestedCollapseOne" data-bs-parent="#nestedAccordion" class="collapse" role="tabpanel"></div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
' <div class="item">',
|
||||
' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>',
|
||||
' <div id="collapseTwo" data-bs-parent="#accordion" class="collapse show" role="tabpanel" aria-labelledby="headingTwo"></div>',
|
||||
' <div id="collapseTwo" data-bs-parent="#accordion" class="collapse show" role="tabpanel"></div>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
@ -887,17 +887,17 @@ describe('Collapse', () => {
|
|||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="trigger1" role="button" data-bs-toggle="collapse" href="#test1"></a>',
|
||||
'<a id="trigger2" role="button" data-bs-toggle="collapse" href="#test2"></a>',
|
||||
'<a id="trigger2" role="button" data-bs-toggle="collapse" href="#0/my/id"></a>',
|
||||
'<a id="trigger3" role="button" data-bs-toggle="collapse" href=".multi"></a>',
|
||||
'<div id="test1" class="multi"></div>',
|
||||
'<div id="test2" class="multi"></div>'
|
||||
'<div id="0/my/id" class="multi"></div>'
|
||||
].join('')
|
||||
|
||||
const trigger1 = fixtureEl.querySelector('#trigger1')
|
||||
const trigger2 = fixtureEl.querySelector('#trigger2')
|
||||
const trigger3 = fixtureEl.querySelector('#trigger3')
|
||||
const target1 = fixtureEl.querySelector('#test1')
|
||||
const target2 = fixtureEl.querySelector('#test2')
|
||||
const target2 = fixtureEl.querySelector(`#${CSS.escape('0/my/id')}`)
|
||||
|
||||
const target2Shown = () => {
|
||||
expect(trigger1).not.toHaveClass('collapsed')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Data from '../../../src/dom/data'
|
||||
import { getFixture, clearFixture } from '../../helpers/fixture'
|
||||
import Data from '../../../src/dom/data.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('Data', () => {
|
||||
const TEST_KEY = 'bs.test'
|
||||
|
@ -89,7 +89,6 @@ describe('Data', () => {
|
|||
expect(Data.get(div, TEST_KEY)).toBeNull()
|
||||
})
|
||||
|
||||
/* eslint-disable no-console */
|
||||
it('should console.error a message if called with multiple keys', () => {
|
||||
console.error = jasmine.createSpy('console.error')
|
||||
|
||||
|
@ -102,5 +101,4 @@ describe('Data', () => {
|
|||
expect(console.error).toHaveBeenCalled()
|
||||
expect(Data.get(div, UNKNOWN_KEY)).toBeNull()
|
||||
})
|
||||
/* eslint-enable no-console */
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import EventHandler from '../../../src/dom/event-handler'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import { noop } from '../../../src/util'
|
||||
import EventHandler from '../../../src/dom/event-handler.js'
|
||||
import { noop } from '../../../src/util/index.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('EventHandler', () => {
|
||||
let fixtureEl
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Manipulator from '../../../src/dom/manipulator'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import Manipulator from '../../../src/dom/manipulator.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('Manipulator', () => {
|
||||
let fixtureEl
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import SelectorEngine from '../../../src/dom/selector-engine'
|
||||
import { getFixture, clearFixture } from '../../helpers/fixture'
|
||||
import SelectorEngine from '../../../src/dom/selector-engine.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('SelectorEngine', () => {
|
||||
let fixtureEl
|
||||
|
@ -232,5 +232,159 @@ describe('SelectorEngine', () => {
|
|||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSelectorFromElement', () => {
|
||||
it('should get selector from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should get selector from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should get selector from href if data-bs-target equal to #', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should return null if a selector from a href is a url without an anchor', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href="foo/bar.html"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return the anchor if a selector from a href is a url', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>',
|
||||
'<div id="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('#target')
|
||||
})
|
||||
|
||||
it('should return null if selector not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getElementFromSelector', () => {
|
||||
it('should get element from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
|
||||
})
|
||||
|
||||
it('should get element from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
|
||||
})
|
||||
|
||||
it('should return null if element not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getElementFromSelector(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(SelectorEngine.getElementFromSelector(testEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getMultipleElementsFromSelector', () => {
|
||||
it('should get elements from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
|
||||
})
|
||||
|
||||
it('should get elements in array, from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
|
||||
})
|
||||
|
||||
it('should return empty array if elements not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toHaveSize(0)
|
||||
})
|
||||
|
||||
it('should return empty array if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toHaveSize(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Dropdown from '../../src/dropdown'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
import { noop } from '../../src/util/index'
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import EventHandler from '../../src/dom/event-handler.js'
|
||||
import Dropdown from '../../src/dropdown.js'
|
||||
import { noop } from '../../src/util/index.js'
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Dropdown', () => {
|
||||
let fixtureEl
|
||||
|
@ -75,6 +75,7 @@ describe('Dropdown', () => {
|
|||
resolve()
|
||||
})
|
||||
|
||||
expect().nothing()
|
||||
dropdown.show()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
/* eslint-env jquery */
|
||||
|
||||
import Alert from '../../src/alert'
|
||||
import Button from '../../src/button'
|
||||
import Carousel from '../../src/carousel'
|
||||
import Collapse from '../../src/collapse'
|
||||
import Dropdown from '../../src/dropdown'
|
||||
import Modal from '../../src/modal'
|
||||
import Offcanvas from '../../src/offcanvas'
|
||||
import Popover from '../../src/popover'
|
||||
import ScrollSpy from '../../src/scrollspy'
|
||||
import Tab from '../../src/tab'
|
||||
import Toast from '../../src/toast'
|
||||
import Tooltip from '../../src/tooltip'
|
||||
import { clearFixture, getFixture } from '../helpers/fixture'
|
||||
import Alert from '../../src/alert.js'
|
||||
import Button from '../../src/button.js'
|
||||
import Carousel from '../../src/carousel.js'
|
||||
import Collapse from '../../src/collapse.js'
|
||||
import Dropdown from '../../src/dropdown.js'
|
||||
import Modal from '../../src/modal.js'
|
||||
import Offcanvas from '../../src/offcanvas.js'
|
||||
import Popover from '../../src/popover.js'
|
||||
import ScrollSpy from '../../src/scrollspy.js'
|
||||
import Tab from '../../src/tab.js'
|
||||
import Toast from '../../src/toast.js'
|
||||
import Tooltip from '../../src/tooltip.js'
|
||||
import { clearFixture, getFixture } from '../helpers/fixture.js'
|
||||
|
||||
describe('jQuery', () => {
|
||||
let fixtureEl
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Modal from '../../src/modal'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
import ScrollBarHelper from '../../src/util/scrollbar'
|
||||
import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import EventHandler from '../../src/dom/event-handler.js'
|
||||
import Modal from '../../src/modal.js'
|
||||
import ScrollBarHelper from '../../src/util/scrollbar.js'
|
||||
import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Modal', () => {
|
||||
let fixtureEl
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import Offcanvas from '../../src/offcanvas'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import { isVisible } from '../../src/util/index'
|
||||
import ScrollBarHelper from '../../src/util/scrollbar'
|
||||
import EventHandler from '../../src/dom/event-handler.js'
|
||||
import Offcanvas from '../../src/offcanvas.js'
|
||||
import { isVisible } from '../../src/util/index.js'
|
||||
import ScrollBarHelper from '../../src/util/scrollbar.js'
|
||||
import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Offcanvas', () => {
|
||||
let fixtureEl
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Popover from '../../src/popover'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import EventHandler from '../../src/dom/event-handler.js'
|
||||
import Popover from '../../src/popover.js'
|
||||
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Popover', () => {
|
||||
let fixtureEl
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import ScrollSpy from '../../src/scrollspy'
|
||||
|
||||
/** Test helpers */
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
import EventHandler from '../../src/dom/event-handler.js'
|
||||
import ScrollSpy from '../../src/scrollspy.js'
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('ScrollSpy', () => {
|
||||
let fixtureEl
|
||||
|
@ -942,5 +940,39 @@ describe('ScrollSpy', () => {
|
|||
}, 100)
|
||||
link.click()
|
||||
})
|
||||
|
||||
it('should smoothscroll to observable with anchor link that contains a french word as id', done => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<nav id="navBar" class="navbar">',
|
||||
' <ul class="nav">',
|
||||
' <li class="nav-item"><a id="li-jsm-1" class="nav-link" href="#présentation">div 1</a></li>',
|
||||
' </ul>',
|
||||
'</nav>',
|
||||
'<div class="content" data-bs-target="#navBar" style="overflow-y: auto">',
|
||||
' <div id="présentation">div 1</div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
const link = fixtureEl.querySelector('[href="#présentation"]')
|
||||
const observable = fixtureEl.querySelector('#présentation')
|
||||
const clickSpy = getElementScrollSpy(div)
|
||||
// eslint-disable-next-line no-new
|
||||
new ScrollSpy(div, {
|
||||
offset: 1,
|
||||
smoothScroll: true
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (div.scrollTo) {
|
||||
expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop, behavior: 'smooth' })
|
||||
} else {
|
||||
expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop)
|
||||
}
|
||||
|
||||
done()
|
||||
}, 100)
|
||||
link.click()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Tab from '../../src/tab'
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import Tab from '../../src/tab.js'
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Tab', () => {
|
||||
let fixtureEl
|
||||
|
@ -177,6 +177,43 @@ describe('Tab', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('should work with tab id being an int', done => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="card-header d-block d-inline-block">',
|
||||
' <ul class="nav nav-tabs card-header-tabs" id="page_tabs">',
|
||||
' <li class="nav-item">',
|
||||
' <a class="nav-link" draggable="false" data-toggle="tab" href="#tab1">',
|
||||
' Working Tab 1 (#tab1)',
|
||||
' </a>',
|
||||
' </li>',
|
||||
' <li class="nav-item">',
|
||||
' <a id="trigger2" class="nav-link" draggable="false" data-toggle="tab" href="#2">',
|
||||
' Tab with numeric ID should work (#2)',
|
||||
' </a>',
|
||||
' </li>',
|
||||
' </ul>',
|
||||
'</div>',
|
||||
'<div class="card-body">',
|
||||
' <div class="tab-content" id="page_content">',
|
||||
' <div class="tab-pane fade" id="tab1">',
|
||||
' Working Tab 1 (#tab1) Content Here',
|
||||
' </div>',
|
||||
' <div class="tab-pane fade" id="2">',
|
||||
' Working Tab 2 (#2) with numeric ID',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
const profileTriggerEl = fixtureEl.querySelector('#trigger2')
|
||||
const tab = new Tab(profileTriggerEl)
|
||||
|
||||
profileTriggerEl.addEventListener('shown.bs.tab', () => {
|
||||
expect(fixtureEl.querySelector(`#${CSS.escape('2')}`)).toHaveClass('active')
|
||||
done()
|
||||
})
|
||||
|
||||
tab.show()
|
||||
})
|
||||
|
||||
it('should not fire shown when show is prevented', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = '<div class="nav"><div class="nav-link"></div></div>'
|
||||
|
@ -477,7 +514,7 @@ describe('Tab', () => {
|
|||
expect(tabPanel.hasAttribute('tabindex')).toBeFalse()
|
||||
expect(tabPanel.hasAttribute('tabindex2')).toBeFalse()
|
||||
|
||||
expect(tabPanel.getAttribute('aria-labelledby')).toEqual('#foo')
|
||||
expect(tabPanel.getAttribute('aria-labelledby')).toEqual('foo')
|
||||
expect(tabPanel2.hasAttribute('aria-labelledby')).toBeFalse()
|
||||
})
|
||||
})
|
||||
|
@ -603,19 +640,19 @@ describe('Tab', () => {
|
|||
'</div>'
|
||||
].join('')
|
||||
|
||||
const tabEl = fixtureEl.querySelector('#tab1')
|
||||
const tabEl1 = fixtureEl.querySelector('#tab1')
|
||||
const tabEl2 = fixtureEl.querySelector('#tab2')
|
||||
const tabEl3 = fixtureEl.querySelector('#tab3')
|
||||
const tabEl4 = fixtureEl.querySelector('#tab4')
|
||||
const tab = new Tab(tabEl)
|
||||
const tab1 = new Tab(tabEl1)
|
||||
const tab2 = new Tab(tabEl2)
|
||||
const tab3 = new Tab(tabEl3)
|
||||
const tab4 = new Tab(tabEl4)
|
||||
const spy1 = spyOn(tab, 'show').and.callThrough()
|
||||
const spy1 = spyOn(tab1, 'show').and.callThrough()
|
||||
const spy2 = spyOn(tab2, 'show').and.callThrough()
|
||||
const spy3 = spyOn(tab3, 'show').and.callThrough()
|
||||
const spy4 = spyOn(tab4, 'show').and.callThrough()
|
||||
const spyFocus1 = spyOn(tabEl, 'focus').and.callThrough()
|
||||
const spyFocus1 = spyOn(tabEl1, 'focus').and.callThrough()
|
||||
const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()
|
||||
const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough()
|
||||
const spyFocus4 = spyOn(tabEl4, 'focus').and.callThrough()
|
||||
|
@ -623,7 +660,7 @@ describe('Tab', () => {
|
|||
const keydown = createEvent('keydown')
|
||||
keydown.key = 'ArrowRight'
|
||||
|
||||
tabEl.dispatchEvent(keydown)
|
||||
tabEl1.dispatchEvent(keydown)
|
||||
expect(spy1).not.toHaveBeenCalled()
|
||||
expect(spy2).not.toHaveBeenCalled()
|
||||
expect(spy3).not.toHaveBeenCalled()
|
||||
|
@ -644,19 +681,19 @@ describe('Tab', () => {
|
|||
'</div>'
|
||||
].join('')
|
||||
|
||||
const tabEl = fixtureEl.querySelector('#tab1')
|
||||
const tabEl1 = fixtureEl.querySelector('#tab1')
|
||||
const tabEl2 = fixtureEl.querySelector('#tab2')
|
||||
const tabEl3 = fixtureEl.querySelector('#tab3')
|
||||
const tabEl4 = fixtureEl.querySelector('#tab4')
|
||||
const tab = new Tab(tabEl)
|
||||
const tab1 = new Tab(tabEl1)
|
||||
const tab2 = new Tab(tabEl2)
|
||||
const tab3 = new Tab(tabEl3)
|
||||
const tab4 = new Tab(tabEl4)
|
||||
const spy1 = spyOn(tab, 'show').and.callThrough()
|
||||
const spy1 = spyOn(tab1, 'show').and.callThrough()
|
||||
const spy2 = spyOn(tab2, 'show').and.callThrough()
|
||||
const spy3 = spyOn(tab3, 'show').and.callThrough()
|
||||
const spy4 = spyOn(tab4, 'show').and.callThrough()
|
||||
const spyFocus1 = spyOn(tabEl, 'focus').and.callThrough()
|
||||
const spyFocus1 = spyOn(tabEl1, 'focus').and.callThrough()
|
||||
const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()
|
||||
const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough()
|
||||
const spyFocus4 = spyOn(tabEl4, 'focus').and.callThrough()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Toast from '../../src/toast'
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import Toast from '../../src/toast.js'
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Toast', () => {
|
||||
let fixtureEl
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Tooltip from '../../src/tooltip'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
import { noop } from '../../src/util/index'
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import EventHandler from '../../src/dom/event-handler.js'
|
||||
import Tooltip from '../../src/tooltip.js'
|
||||
import { noop } from '../../src/util/index.js'
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Tooltip', () => {
|
||||
let fixtureEl
|
||||
|
@ -56,7 +56,7 @@ describe('Tooltip', () => {
|
|||
|
||||
describe('constructor', () => {
|
||||
it('should take care of element either passed as a CSS selector or DOM element', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" id="tooltipEl" rel="tooltip" title="Nice and short title">'
|
||||
fixtureEl.innerHTML = '<a href="#" id="tooltipEl" rel="tooltip" title="Nice and short title"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('#tooltipEl')
|
||||
const tooltipBySelector = new Tooltip('#tooltipEl')
|
||||
|
@ -67,7 +67,7 @@ describe('Tooltip', () => {
|
|||
})
|
||||
|
||||
it('should not take care of disallowed data attributes', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-sanitize="false" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-sanitize="false" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -76,7 +76,7 @@ describe('Tooltip', () => {
|
|||
})
|
||||
|
||||
it('should convert title and content to string if numbers', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -98,7 +98,7 @@ describe('Tooltip', () => {
|
|||
trigger: 'click'
|
||||
})
|
||||
|
||||
containerEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
containerEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipInContainerEl = containerEl.querySelector('a')
|
||||
|
||||
|
@ -114,7 +114,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should create offset modifier when offset is passed as a function', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Offset from function">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Offset from function"></a>'
|
||||
|
||||
const getOffset = jasmine.createSpy('getOffset').and.returnValue([10, 20])
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
|
@ -141,7 +141,7 @@ describe('Tooltip', () => {
|
|||
})
|
||||
|
||||
it('should create offset modifier when offset option is passed in data attribute', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-offset="10,20" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-offset="10,20" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -150,7 +150,7 @@ describe('Tooltip', () => {
|
|||
})
|
||||
|
||||
it('should allow to pass config to Popper with `popperConfig`', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -165,7 +165,7 @@ describe('Tooltip', () => {
|
|||
})
|
||||
|
||||
it('should allow to pass config to Popper with `popperConfig` as a function', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const getPopperConfig = jasmine.createSpy('getPopperConfig').and.returnValue({ placement: 'left' })
|
||||
|
@ -192,7 +192,7 @@ describe('Tooltip', () => {
|
|||
describe('enable', () => {
|
||||
it('should enable a tooltip', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -212,7 +212,7 @@ describe('Tooltip', () => {
|
|||
describe('disable', () => {
|
||||
it('should disable tooltip', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -235,7 +235,7 @@ describe('Tooltip', () => {
|
|||
|
||||
describe('toggleEnabled', () => {
|
||||
it('should toggle enabled', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -251,7 +251,7 @@ describe('Tooltip', () => {
|
|||
describe('toggle', () => {
|
||||
it('should do nothing if disabled', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -273,7 +273,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should show a tooltip', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -289,7 +289,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should call toggle and show the tooltip when trigger is "click"', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -309,7 +309,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should hide a tooltip', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -329,7 +329,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should call toggle and hide the tooltip when trigger is "click"', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -354,7 +354,7 @@ describe('Tooltip', () => {
|
|||
|
||||
describe('dispose', () => {
|
||||
it('should destroy a tooltip', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const addEventSpy = spyOn(tooltipEl, 'addEventListener').and.callThrough()
|
||||
|
@ -381,7 +381,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should destroy a tooltip after it is shown and hidden', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -402,7 +402,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should destroy a tooltip and remove it from the dom', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -443,7 +443,7 @@ describe('Tooltip', () => {
|
|||
describe('show', () => {
|
||||
it('should show a tooltip', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -488,7 +488,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should show a tooltip on mobile', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -509,7 +509,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should show a tooltip relative to placement option', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -532,7 +532,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should not error when trying to show a tooltip that has been removed from the dom', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -561,7 +561,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should show a tooltip with a dom element container', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -579,7 +579,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should show a tooltip with a jquery element container', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -600,7 +600,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should show a tooltip with a selector in container', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -618,7 +618,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should show a tooltip with placement as a function', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const spy = jasmine.createSpy('placement').and.returnValue('top')
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
|
@ -638,7 +638,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should show a tooltip without the animation', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -658,7 +658,7 @@ describe('Tooltip', () => {
|
|||
})
|
||||
|
||||
it('should throw an error the element is not visible', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" style="display: none" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" style="display: none" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -672,7 +672,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should not show a tooltip if show.bs.tooltip is prevented', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -699,7 +699,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should show tooltip if leave event hasn\'t occurred before delay expires', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -723,7 +723,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should not show tooltip if leave event occurs before delay expires', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -845,7 +845,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should only trigger inserted event if a new tooltip element was created', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -878,7 +878,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should show a tooltip with custom class provided in data attributes', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-custom-class="custom-class">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-custom-class="custom-class"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -896,7 +896,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should show a tooltip with custom class provided as a string in config', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -917,7 +917,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should show a tooltip with custom class provided as a function in config', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const spy = jasmine.createSpy('customClass').and.returnValue('custom-class')
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
|
@ -956,7 +956,7 @@ describe('Tooltip', () => {
|
|||
describe('hide', () => {
|
||||
it('should hide a tooltip', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -974,7 +974,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should hide a tooltip on mobile', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -998,7 +998,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should hide a tooltip without animation', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
|
@ -1018,7 +1018,7 @@ describe('Tooltip', () => {
|
|||
|
||||
it('should not hide a tooltip if hide event is prevented', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const assertDone = () => {
|
||||
setTimeout(() => {
|
||||
|
@ -1063,7 +1063,7 @@ describe('Tooltip', () => {
|
|||
describe('update', () => {
|
||||
it('should call popper update', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -1082,7 +1082,7 @@ describe('Tooltip', () => {
|
|||
})
|
||||
|
||||
it('should do nothing if the tooltip is not shown', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -1094,7 +1094,7 @@ describe('Tooltip', () => {
|
|||
|
||||
describe('_isWithContent', () => {
|
||||
it('should return true if there is content', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -1103,7 +1103,7 @@ describe('Tooltip', () => {
|
|||
})
|
||||
|
||||
it('should return false if there is no content', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title=""></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -1114,7 +1114,7 @@ describe('Tooltip', () => {
|
|||
|
||||
describe('_getTipElement', () => {
|
||||
it('should create the tip element and return it', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -1126,7 +1126,7 @@ describe('Tooltip', () => {
|
|||
})
|
||||
|
||||
it('should return the created tip element', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -1145,7 +1145,7 @@ describe('Tooltip', () => {
|
|||
|
||||
describe('setContent', () => {
|
||||
it('should set tip content', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, { animation: false })
|
||||
|
@ -1160,7 +1160,7 @@ describe('Tooltip', () => {
|
|||
})
|
||||
|
||||
it('should re-show tip if it was already shown', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -1175,7 +1175,7 @@ describe('Tooltip', () => {
|
|||
})
|
||||
|
||||
it('should keep tip hidden, if it was already hidden before', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -1190,7 +1190,7 @@ describe('Tooltip', () => {
|
|||
})
|
||||
|
||||
it('"setContent" should keep the initial template', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
@ -1207,7 +1207,7 @@ describe('Tooltip', () => {
|
|||
|
||||
describe('setContent', () => {
|
||||
it('should do nothing if the element is null', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Backdrop from '../../../src/util/backdrop'
|
||||
import { getTransitionDurationFromElement } from '../../../src/util/index'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import Backdrop from '../../../src/util/backdrop.js'
|
||||
import { getTransitionDurationFromElement } from '../../../src/util/index.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
const CLASS_BACKDROP = '.modal-backdrop'
|
||||
const CLASS_NAME_FADE = 'fade'
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
/* Test helpers */
|
||||
|
||||
import { clearFixture, createEvent, getFixture } from '../../helpers/fixture'
|
||||
import { enableDismissTrigger } from '../../../src/util/component-functions'
|
||||
import BaseComponent from '../../../src/base-component'
|
||||
import BaseComponent from '../../../src/base-component.js'
|
||||
import { enableDismissTrigger } from '../../../src/util/component-functions.js'
|
||||
import { clearFixture, createEvent, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
class DummyClass2 extends BaseComponent {
|
||||
static get NAME() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Config from '../../../src/util/config'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import Config from '../../../src/util/config.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
class DummyConfigClass extends Config {
|
||||
static get NAME() {
|
||||
|
@ -128,7 +128,7 @@ describe('Config', () => {
|
|||
const obj = new DummyConfigClass()
|
||||
expect(() => {
|
||||
obj._typeCheckConfig(config)
|
||||
}).toThrowError(TypeError, obj.constructor.NAME.toUpperCase() + ': Option "parent" provided type "number" but expected type "(string|element)".')
|
||||
}).toThrowError(TypeError, `${obj.constructor.NAME.toUpperCase()}: Option "parent" provided type "number" but expected type "(string|element)".`)
|
||||
})
|
||||
|
||||
it('should return null stringified when null is passed', () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import FocusTrap from '../../../src/util/focustrap'
|
||||
import EventHandler from '../../../src/dom/event-handler'
|
||||
import SelectorEngine from '../../../src/dom/selector-engine'
|
||||
import { clearFixture, createEvent, getFixture } from '../../helpers/fixture'
|
||||
import EventHandler from '../../../src/dom/event-handler.js'
|
||||
import SelectorEngine from '../../../src/dom/selector-engine.js'
|
||||
import FocusTrap from '../../../src/util/focustrap.js'
|
||||
import { clearFixture, createEvent, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('FocusTrap', () => {
|
||||
let fixtureEl
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as Util from '../../../src/util/index'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import { noop } from '../../../src/util/index'
|
||||
import * as Util from '../../../src/util/index.js'
|
||||
import { noop } from '../../../src/util/index.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('Util', () => {
|
||||
let fixtureEl
|
||||
|
@ -22,119 +22,6 @@ describe('Util', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('getSelectorFromElement', () => {
|
||||
it('should get selector from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should get selector from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should get selector from href if data-bs-target equal to #', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should return null if a selector from a href is a url without an anchor', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href="foo/bar.html"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return the anchor if a selector from a href is a url', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>',
|
||||
'<div id="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toEqual('#target')
|
||||
})
|
||||
|
||||
it('should return null if selector not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getElementFromSelector', () => {
|
||||
it('should get element from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
|
||||
})
|
||||
|
||||
it('should get element from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
|
||||
})
|
||||
|
||||
it('should return null if element not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getElementFromSelector(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Util.getElementFromSelector(testEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTransitionDurationFromElement', () => {
|
||||
it('should get transition from element', () => {
|
||||
fixtureEl.innerHTML = '<div style="transition: all 300ms ease-out;"></div>'
|
||||
|
@ -631,6 +518,25 @@ describe('Util', () => {
|
|||
Util.execute(spy)
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should execute if arg is function & return the result', () => {
|
||||
const functionFoo = (num1, num2 = 10) => num1 + num2
|
||||
const resultFoo = Util.execute(functionFoo, [4, 5])
|
||||
expect(resultFoo).toBe(9)
|
||||
|
||||
const resultFoo1 = Util.execute(functionFoo, [4])
|
||||
expect(resultFoo1).toBe(14)
|
||||
|
||||
const functionBar = () => 'foo'
|
||||
const resultBar = Util.execute(functionBar)
|
||||
expect(resultBar).toBe('foo')
|
||||
})
|
||||
|
||||
it('should not execute if arg is not function & return default argument', () => {
|
||||
const foo = 'bar'
|
||||
expect(Util.execute(foo)).toBe('bar')
|
||||
expect(Util.execute(foo, [], 4)).toBe(4)
|
||||
})
|
||||
})
|
||||
|
||||
describe('executeAfterTransition', () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { DefaultAllowlist, sanitizeHtml } from '../../../src/util/sanitizer'
|
||||
import { DefaultAllowlist, sanitizeHtml } from '../../../src/util/sanitizer.js'
|
||||
|
||||
describe('Sanitizer', () => {
|
||||
describe('sanitizeHtml', () => {
|
||||
|
@ -10,17 +10,75 @@ describe('Sanitizer', () => {
|
|||
expect(result).toEqual(empty)
|
||||
})
|
||||
|
||||
it('should retain tags with valid URLs', () => {
|
||||
const validUrls = [
|
||||
'',
|
||||
'http://abc',
|
||||
'HTTP://abc',
|
||||
'https://abc',
|
||||
'HTTPS://abc',
|
||||
'ftp://abc',
|
||||
'FTP://abc',
|
||||
'mailto:me@example.com',
|
||||
'MAILTO:me@example.com',
|
||||
'tel:123-123-1234',
|
||||
'TEL:123-123-1234',
|
||||
'sip:me@example.com',
|
||||
'SIP:me@example.com',
|
||||
'#anchor',
|
||||
'/page1.md',
|
||||
'http://JavaScript/my.js',
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', // Truncated.
|
||||
'data:video/webm;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:audio/opus;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'unknown-scheme:abc'
|
||||
]
|
||||
|
||||
for (const url of validUrls) {
|
||||
const template = [
|
||||
'<div>',
|
||||
` <a href="${url}">Click me</a>`,
|
||||
' <span>Some content</span>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const result = sanitizeHtml(template, DefaultAllowlist, null)
|
||||
|
||||
expect(result).toContain(`href="${url}"`)
|
||||
}
|
||||
})
|
||||
|
||||
it('should sanitize template by removing tags with XSS', () => {
|
||||
const template = [
|
||||
'<div>',
|
||||
' <a href="javascript:alert(7)">Click me</a>',
|
||||
' <span>Some content</span>',
|
||||
'</div>'
|
||||
].join('')
|
||||
const invalidUrls = [
|
||||
// eslint-disable-next-line no-script-url
|
||||
'javascript:alert(7)',
|
||||
// eslint-disable-next-line no-script-url
|
||||
'javascript:evil()',
|
||||
// eslint-disable-next-line no-script-url
|
||||
'JavaScript:abc',
|
||||
' javascript:abc',
|
||||
' \n Java\n Script:abc',
|
||||
'javascript:',
|
||||
'javascript:',
|
||||
'j avascript:',
|
||||
'javascript:',
|
||||
'javascript:',
|
||||
'jav	ascript:alert();',
|
||||
'jav\u0000ascript:alert();'
|
||||
]
|
||||
|
||||
const result = sanitizeHtml(template, DefaultAllowlist, null)
|
||||
for (const url of invalidUrls) {
|
||||
const template = [
|
||||
'<div>',
|
||||
` <a href="${url}">Click me</a>`,
|
||||
' <span>Some content</span>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
expect(result).not.toContain('href="javascript:alert(7)')
|
||||
const result = sanitizeHtml(template, DefaultAllowlist, null)
|
||||
|
||||
expect(result).not.toContain(`href="${url}"`)
|
||||
}
|
||||
})
|
||||
|
||||
it('should sanitize template and work with multiple regex', () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { clearBodyAndDocument, clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import Manipulator from '../../../src/dom/manipulator'
|
||||
import ScrollBarHelper from '../../../src/util/scrollbar'
|
||||
import Manipulator from '../../../src/dom/manipulator.js'
|
||||
import ScrollBarHelper from '../../../src/util/scrollbar.js'
|
||||
import { clearBodyAndDocument, clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('ScrollBar', () => {
|
||||
let fixtureEl
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import EventHandler from '../../../src/dom/event-handler'
|
||||
import Swipe from '../../../src/util/swipe'
|
||||
import { noop } from '../../../src/util'
|
||||
import EventHandler from '../../../src/dom/event-handler.js'
|
||||
import { noop } from '../../../src/util/index.js'
|
||||
import Swipe from '../../../src/util/swipe.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('Swipe', () => {
|
||||
const { Simulator, PointerEvent } = window
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import TemplateFactory from '../../../src/util/template-factory'
|
||||
import TemplateFactory from '../../../src/util/template-factory.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('TemplateFactory', () => {
|
||||
let fixtureEl
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"plugins": [
|
||||
"html"
|
||||
],
|
||||
"extends": "../../../.eslintrc.json",
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"settings": {
|
||||
"html/html-extensions": [
|
||||
".html"
|
||||
]
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"no-new": "off",
|
||||
"unicorn/no-array-for-each": "off"
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
</button>
|
||||
|
||||
<p>For checkboxes and radio buttons, ensure that keyboard behavior is functioning correctly.</p>
|
||||
<p>Navigate to the checkboxes with the keyboard (generally, using <kbd>TAB</kbd> / <kbd>SHIFT + TAB</kbd>), and ensure that <kbd>SPACE</kbd> toggles the currently focused checkbox. Click on one of the checkboxes using the mouse, ensure that focus was correctly set on the actual checkbox, and that <kbd>SPACE</kbd> toggles the checkbox again.</p>
|
||||
<p>Navigate to the checkboxes with the keyboard (generally, using <kbd>Tab</kbd> / <kbd><kbd>Shift</kbd> + <kbd>Tab</kbd></kbd>), and ensure that <kbd>Space</kbd> toggles the currently focused checkbox. Click on one of the checkboxes using the mouse, ensure that focus was correctly set on the actual checkbox, and that <kbd>Space</kbd> toggles the checkbox again.</p>
|
||||
|
||||
<div class="btn-group" data-bs-toggle="buttons">
|
||||
<label class="btn btn-primary active">
|
||||
|
@ -29,7 +29,7 @@
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<p>Navigate to the radio button group with the keyboard (generally, using <kbd>TAB</kbd> / <kbd>SHIFT + TAB</kbd>). If no radio button was initially set to be selected, the first/last radio button should receive focus (depending on whether you navigated "forward" to the group with <kbd>TAB</kbd> or "backwards" using <kbd>SHIFT + TAB</kbd>). If a radio button was already selected, navigating with the keyboard should set focus to that particular radio button. Only one radio button in a group should receive focus at any given time. Ensure that the selected radio button can be changed by using the <kbd>←</kbd> and <kbd>→</kbd> arrow keys. Click on one of the radio buttons with the mouse, ensure that focus was correctly set on the actual radio button, and that <kbd>←</kbd> and <kbd>→</kbd> change the selected radio button again.</p>
|
||||
<p>Navigate to the radio button group with the keyboard (generally, using <kbd>Tab</kbd> / <kbd><kbd>Shift</kbd> + <kbd>Tab</kbd></kbd>). If no radio button was initially set to be selected, the first/last radio button should receive focus (depending on whether you navigated "forward" to the group with <kbd>Tab</kbd> or "backwards" using <kbd><kbd>Shift</kbd> + <kbd>Tab</kbd></kbd>). If a radio button was already selected, navigating with the keyboard should set focus to that particular radio button. Only one radio button in a group should receive focus at any given time. Ensure that the selected radio button can be changed by using the <kbd>←</kbd> and <kbd>→</kbd> arrow keys. Click on one of the radio buttons with the mouse, ensure that focus was correctly set on the actual radio button, and that <kbd>←</kbd> and <kbd>→</kbd> change the selected radio button again.</p>
|
||||
|
||||
<div class="btn-group" data-bs-toggle="buttons">
|
||||
<label class="btn btn-primary active">
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
// Test to show that transition-duration can be changed with css
|
||||
carousel.addEventListener('slid.bs.carousel', event => {
|
||||
t1 = performance.now()
|
||||
console.log('transition-duration took ' + (t1 - t0) + 'ms, slid at ' + event.timeStamp)
|
||||
console.log(`transition-duration took ${t1 - t0}ms, slid at ${event.timeStamp}`)
|
||||
})
|
||||
carousel.addEventListener('slide.bs.carousel', () => {
|
||||
t0 = performance.now()
|
||||
|
|
78
js/tests/visual/input.html
Normal file
78
js/tests/visual/input.html
Normal file
|
@ -0,0 +1,78 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="../../../dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<title>Form</title>
|
||||
<style></style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Input <small>Bootstrap Visual Test</small></h1>
|
||||
|
||||
<h2>No layout</h2>
|
||||
|
||||
<div class="mb-3">
|
||||
Text
|
||||
<input class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
Email
|
||||
<input type="email" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
Number
|
||||
<input type="number" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
Date
|
||||
<input type="date" class="form-control">
|
||||
</div>
|
||||
|
||||
<h2>Flex</h2>
|
||||
|
||||
<div class="d-flex flex-wrap gap-3 mb-3">
|
||||
<div>
|
||||
Text
|
||||
<input class="form-control">
|
||||
</div>
|
||||
<div>
|
||||
Email
|
||||
<input type="email" class="form-control">
|
||||
</div>
|
||||
<div>
|
||||
Number
|
||||
<input type="number" class="form-control">
|
||||
</div>
|
||||
<div>
|
||||
Date
|
||||
<input type="date" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Grid</h2>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
Text
|
||||
<input class="form-control">
|
||||
</div>
|
||||
<div class="col">
|
||||
Email
|
||||
<input type="email" class="form-control">
|
||||
</div>
|
||||
<div class="col">
|
||||
Number
|
||||
<input type="number" class="form-control">
|
||||
</div>
|
||||
<div class="col">
|
||||
Date
|
||||
<input type="date" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../../../dist/js/bootstrap.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -264,7 +264,7 @@
|
|||
|
||||
slowModal.addEventListener('shown.bs.modal', () => {
|
||||
t1 = performance.now()
|
||||
console.log('transition-duration took ' + (t1 - t0) + 'ms.')
|
||||
console.log(`transition-duration took ${t1 - t0}ms.`)
|
||||
})
|
||||
|
||||
slowModal.addEventListener('show.bs.modal', () => {
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<li><a class="dropdown-item" href="#one">One</a></li>
|
||||
<li><a class="dropdown-item" href="#two">Two</a></li>
|
||||
<li><a class="dropdown-item" href="#three">Three</a></li>
|
||||
<li><a class="dropdown-item" href="#présentation">Présentation</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
|
@ -82,6 +83,14 @@
|
|||
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
|
||||
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
|
||||
<hr>
|
||||
<h2 id="présentation">Présentation</h2>
|
||||
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
|
||||
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
|
||||
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
|
||||
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
|
||||
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
|
||||
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
|
||||
<hr>
|
||||
<h2 id="final">Final section</h2>
|
||||
<p>Ad leggings keytar, brunch id art party dolor labore.</p>
|
||||
</div>
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
<div class="toast-header">
|
||||
<span class="d-block bg-primary rounded me-2" style="width: 20px; height: 20px;"></span>
|
||||
<strong class="me-auto">Bootstrap</strong>
|
||||
<small class="text-muted">2 seconds ago</small>
|
||||
<small class="text-body-secondary">2 seconds ago</small>
|
||||
<button type="button" class="ms-2 mb-1 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<div class="container">
|
||||
<h1>Tooltip <small>Bootstrap Visual Test</small></h1>
|
||||
|
||||
<p class="text-muted">Tight pants next level keffiyeh <a href="#" data-bs-toggle="tooltip" title="Default tooltip">you probably</a> haven't heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's fixie sustainable quinoa 8-bit american apparel <a href="#" data-bs-toggle="tooltip" title="Another tooltip">have a</a> terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four loko mcsweeney's cleanse vegan chambray. A really ironic artisan <a href="#" data-bs-toggle="tooltip" title="Another one here too">whatever keytar</a>, scenester farm-to-table banksy Austin <a href="#" data-bs-toggle="tooltip" title="The last tip!">twitter handle</a> freegan cred raw denim single-origin coffee viral.</p>
|
||||
<p class="text-body-secondary">Tight pants next level keffiyeh <a href="#" data-bs-toggle="tooltip" title="Default tooltip">you probably</a> haven't heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's fixie sustainable quinoa 8-bit american apparel <a href="#" data-bs-toggle="tooltip" title="Another tooltip">have a</a> terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four loko mcsweeney's cleanse vegan chambray. A really ironic artisan <a href="#" data-bs-toggle="tooltip" title="Another one here too">whatever keytar</a>, scenester farm-to-table banksy Austin <a href="#" data-bs-toggle="tooltip" title="The last tip!">twitter handle</a> freegan cred raw denim single-origin coffee viral.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue