offcanvas.spec.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. import EventHandler from '../../src/dom/event-handler.js'
  2. import Offcanvas from '../../src/offcanvas.js'
  3. import { isVisible } from '../../src/util/index.js'
  4. import ScrollBarHelper from '../../src/util/scrollbar.js'
  5. import {
  6. clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock
  7. } from '../helpers/fixture.js'
  8. describe('Offcanvas', () => {
  9. let fixtureEl
  10. beforeAll(() => {
  11. fixtureEl = getFixture()
  12. })
  13. afterEach(() => {
  14. clearFixture()
  15. document.body.classList.remove('offcanvas-open')
  16. clearBodyAndDocument()
  17. })
  18. beforeEach(() => {
  19. clearBodyAndDocument()
  20. })
  21. describe('VERSION', () => {
  22. it('should return plugin version', () => {
  23. expect(Offcanvas.VERSION).toEqual(jasmine.any(String))
  24. })
  25. })
  26. describe('Default', () => {
  27. it('should return plugin default config', () => {
  28. expect(Offcanvas.Default).toEqual(jasmine.any(Object))
  29. })
  30. })
  31. describe('DATA_KEY', () => {
  32. it('should return plugin data key', () => {
  33. expect(Offcanvas.DATA_KEY).toEqual('bs.offcanvas')
  34. })
  35. })
  36. describe('constructor', () => {
  37. it('should call hide when a element with data-bs-dismiss="offcanvas" is clicked', () => {
  38. fixtureEl.innerHTML = [
  39. '<div class="offcanvas">',
  40. ' <a href="#" data-bs-dismiss="offcanvas">Close</a>',
  41. '</div>'
  42. ].join('')
  43. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  44. const closeEl = fixtureEl.querySelector('a')
  45. const offCanvas = new Offcanvas(offCanvasEl)
  46. const spy = spyOn(offCanvas, 'hide')
  47. closeEl.click()
  48. expect(offCanvas._config.keyboard).toBeTrue()
  49. expect(spy).toHaveBeenCalled()
  50. })
  51. it('should hide if esc is pressed', () => {
  52. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  53. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  54. const offCanvas = new Offcanvas(offCanvasEl)
  55. const keyDownEsc = createEvent('keydown')
  56. keyDownEsc.key = 'Escape'
  57. const spy = spyOn(offCanvas, 'hide')
  58. offCanvasEl.dispatchEvent(keyDownEsc)
  59. expect(spy).toHaveBeenCalled()
  60. })
  61. it('should hide if esc is pressed and backdrop is static', () => {
  62. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  63. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  64. const offCanvas = new Offcanvas(offCanvasEl, { backdrop: 'static' })
  65. const keyDownEsc = createEvent('keydown')
  66. keyDownEsc.key = 'Escape'
  67. const spy = spyOn(offCanvas, 'hide')
  68. offCanvasEl.dispatchEvent(keyDownEsc)
  69. expect(spy).toHaveBeenCalled()
  70. })
  71. it('should not hide if esc is not pressed', () => {
  72. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  73. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  74. const offCanvas = new Offcanvas(offCanvasEl)
  75. const keydownTab = createEvent('keydown')
  76. keydownTab.key = 'Tab'
  77. const spy = spyOn(offCanvas, 'hide')
  78. offCanvasEl.dispatchEvent(keydownTab)
  79. expect(spy).not.toHaveBeenCalled()
  80. })
  81. it('should not hide if esc is pressed but with keyboard = false', () => {
  82. return new Promise(resolve => {
  83. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  84. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  85. const offCanvas = new Offcanvas(offCanvasEl, { keyboard: false })
  86. const keyDownEsc = createEvent('keydown')
  87. keyDownEsc.key = 'Escape'
  88. const spy = spyOn(offCanvas, 'hide')
  89. const hidePreventedSpy = jasmine.createSpy('hidePrevented')
  90. offCanvasEl.addEventListener('hidePrevented.bs.offcanvas', hidePreventedSpy)
  91. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  92. expect(offCanvas._config.keyboard).toBeFalse()
  93. offCanvasEl.dispatchEvent(keyDownEsc)
  94. expect(hidePreventedSpy).toHaveBeenCalled()
  95. expect(spy).not.toHaveBeenCalled()
  96. resolve()
  97. })
  98. offCanvas.show()
  99. })
  100. })
  101. it('should not hide if user clicks on static backdrop', () => {
  102. return new Promise(resolve => {
  103. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  104. const offCanvasEl = fixtureEl.querySelector('div')
  105. const offCanvas = new Offcanvas(offCanvasEl, { backdrop: 'static' })
  106. const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true })
  107. const spyClick = spyOn(offCanvas._backdrop._config, 'clickCallback').and.callThrough()
  108. const spyHide = spyOn(offCanvas._backdrop, 'hide').and.callThrough()
  109. const hidePreventedSpy = jasmine.createSpy('hidePrevented')
  110. offCanvasEl.addEventListener('hidePrevented.bs.offcanvas', hidePreventedSpy)
  111. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  112. expect(spyClick).toEqual(jasmine.any(Function))
  113. offCanvas._backdrop._getElement().dispatchEvent(clickEvent)
  114. expect(hidePreventedSpy).toHaveBeenCalled()
  115. expect(spyHide).not.toHaveBeenCalled()
  116. resolve()
  117. })
  118. offCanvas.show()
  119. })
  120. })
  121. it('should call `hide` on resize, if element\'s position is not fixed any more', () => {
  122. return new Promise(resolve => {
  123. fixtureEl.innerHTML = '<div class="offcanvas-lg"></div>'
  124. const offCanvasEl = fixtureEl.querySelector('div')
  125. const offCanvas = new Offcanvas(offCanvasEl)
  126. const spy = spyOn(offCanvas, 'hide').and.callThrough()
  127. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  128. const resizeEvent = createEvent('resize')
  129. offCanvasEl.style.removeProperty('position')
  130. window.dispatchEvent(resizeEvent)
  131. expect(spy).toHaveBeenCalled()
  132. resolve()
  133. })
  134. offCanvas.show()
  135. })
  136. })
  137. })
  138. describe('config', () => {
  139. it('should have default values', () => {
  140. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  141. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  142. const offCanvas = new Offcanvas(offCanvasEl)
  143. expect(offCanvas._config.backdrop).toBeTrue()
  144. expect(offCanvas._backdrop._config.isVisible).toBeTrue()
  145. expect(offCanvas._config.keyboard).toBeTrue()
  146. expect(offCanvas._config.scroll).toBeFalse()
  147. })
  148. it('should read data attributes and override default config', () => {
  149. fixtureEl.innerHTML = '<div class="offcanvas" data-bs-scroll="true" data-bs-backdrop="false" data-bs-keyboard="false"></div>'
  150. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  151. const offCanvas = new Offcanvas(offCanvasEl)
  152. expect(offCanvas._config.backdrop).toBeFalse()
  153. expect(offCanvas._backdrop._config.isVisible).toBeFalse()
  154. expect(offCanvas._config.keyboard).toBeFalse()
  155. expect(offCanvas._config.scroll).toBeTrue()
  156. })
  157. it('given a config object must override data attributes', () => {
  158. fixtureEl.innerHTML = '<div class="offcanvas" data-bs-scroll="true" data-bs-backdrop="false" data-bs-keyboard="false"></div>'
  159. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  160. const offCanvas = new Offcanvas(offCanvasEl, {
  161. backdrop: true,
  162. keyboard: true,
  163. scroll: false
  164. })
  165. expect(offCanvas._config.backdrop).toBeTrue()
  166. expect(offCanvas._config.keyboard).toBeTrue()
  167. expect(offCanvas._config.scroll).toBeFalse()
  168. })
  169. })
  170. describe('options', () => {
  171. it('if scroll is enabled, should allow body to scroll while offcanvas is open', () => {
  172. return new Promise(resolve => {
  173. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  174. const spyHide = spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()
  175. const spyReset = spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()
  176. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  177. const offCanvas = new Offcanvas(offCanvasEl, { scroll: true })
  178. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  179. expect(spyHide).not.toHaveBeenCalled()
  180. offCanvas.hide()
  181. })
  182. offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
  183. expect(spyReset).not.toHaveBeenCalled()
  184. resolve()
  185. })
  186. offCanvas.show()
  187. })
  188. })
  189. it('if scroll is disabled, should call ScrollBarHelper to handle scrollBar on body', () => {
  190. return new Promise(resolve => {
  191. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  192. const spyHide = spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()
  193. const spyReset = spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()
  194. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  195. const offCanvas = new Offcanvas(offCanvasEl, { scroll: false })
  196. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  197. expect(spyHide).toHaveBeenCalled()
  198. offCanvas.hide()
  199. })
  200. offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
  201. expect(spyReset).toHaveBeenCalled()
  202. resolve()
  203. })
  204. offCanvas.show()
  205. })
  206. })
  207. it('should hide a shown element if user click on backdrop', () => {
  208. return new Promise(resolve => {
  209. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  210. const offCanvasEl = fixtureEl.querySelector('div')
  211. const offCanvas = new Offcanvas(offCanvasEl, { backdrop: true })
  212. const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true })
  213. const spy = spyOn(offCanvas._backdrop._config, 'clickCallback').and.callThrough()
  214. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  215. expect(offCanvas._backdrop._config.clickCallback).toEqual(jasmine.any(Function))
  216. offCanvas._backdrop._getElement().dispatchEvent(clickEvent)
  217. })
  218. offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
  219. expect(spy).toHaveBeenCalled()
  220. resolve()
  221. })
  222. offCanvas.show()
  223. })
  224. })
  225. it('should not trap focus if scroll is allowed', () => {
  226. return new Promise(resolve => {
  227. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  228. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  229. const offCanvas = new Offcanvas(offCanvasEl, {
  230. scroll: true,
  231. backdrop: false
  232. })
  233. const spy = spyOn(offCanvas._focustrap, 'activate').and.callThrough()
  234. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  235. expect(spy).not.toHaveBeenCalled()
  236. resolve()
  237. })
  238. offCanvas.show()
  239. })
  240. })
  241. it('should trap focus if scroll is allowed OR backdrop is enabled', () => {
  242. return new Promise(resolve => {
  243. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  244. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  245. const offCanvas = new Offcanvas(offCanvasEl, {
  246. scroll: true,
  247. backdrop: true
  248. })
  249. const spy = spyOn(offCanvas._focustrap, 'activate').and.callThrough()
  250. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  251. expect(spy).toHaveBeenCalled()
  252. resolve()
  253. })
  254. offCanvas.show()
  255. })
  256. })
  257. })
  258. describe('toggle', () => {
  259. it('should call show method if show class is not present', () => {
  260. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  261. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  262. const offCanvas = new Offcanvas(offCanvasEl)
  263. const spy = spyOn(offCanvas, 'show')
  264. offCanvas.toggle()
  265. expect(spy).toHaveBeenCalled()
  266. })
  267. it('should call hide method if show class is present', () => {
  268. return new Promise(resolve => {
  269. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  270. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  271. const offCanvas = new Offcanvas(offCanvasEl)
  272. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  273. expect(offCanvasEl).toHaveClass('show')
  274. const spy = spyOn(offCanvas, 'hide')
  275. offCanvas.toggle()
  276. expect(spy).toHaveBeenCalled()
  277. resolve()
  278. })
  279. offCanvas.show()
  280. })
  281. })
  282. })
  283. describe('show', () => {
  284. it('should add `showing` class during opening and `show` class on end', () => {
  285. return new Promise(resolve => {
  286. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  287. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  288. const offCanvas = new Offcanvas(offCanvasEl)
  289. offCanvasEl.addEventListener('show.bs.offcanvas', () => {
  290. expect(offCanvasEl).not.toHaveClass('show')
  291. })
  292. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  293. expect(offCanvasEl).not.toHaveClass('showing')
  294. expect(offCanvasEl).toHaveClass('show')
  295. resolve()
  296. })
  297. offCanvas.show()
  298. expect(offCanvasEl).toHaveClass('showing')
  299. })
  300. })
  301. it('should do nothing if already shown', () => {
  302. fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
  303. const offCanvasEl = fixtureEl.querySelector('div')
  304. const offCanvas = new Offcanvas(offCanvasEl)
  305. offCanvas.show()
  306. expect(offCanvasEl).toHaveClass('show')
  307. const spyShow = spyOn(offCanvas._backdrop, 'show').and.callThrough()
  308. const spyTrigger = spyOn(EventHandler, 'trigger').and.callThrough()
  309. offCanvas.show()
  310. expect(spyTrigger).not.toHaveBeenCalled()
  311. expect(spyShow).not.toHaveBeenCalled()
  312. })
  313. it('should show a hidden element', () => {
  314. return new Promise(resolve => {
  315. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  316. const offCanvasEl = fixtureEl.querySelector('div')
  317. const offCanvas = new Offcanvas(offCanvasEl)
  318. const spy = spyOn(offCanvas._backdrop, 'show').and.callThrough()
  319. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  320. expect(offCanvasEl).toHaveClass('show')
  321. expect(spy).toHaveBeenCalled()
  322. resolve()
  323. })
  324. offCanvas.show()
  325. })
  326. })
  327. it('should not fire shown when show is prevented', () => {
  328. return new Promise((resolve, reject) => {
  329. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  330. const offCanvasEl = fixtureEl.querySelector('div')
  331. const offCanvas = new Offcanvas(offCanvasEl)
  332. const spy = spyOn(offCanvas._backdrop, 'show').and.callThrough()
  333. const expectEnd = () => {
  334. setTimeout(() => {
  335. expect(spy).not.toHaveBeenCalled()
  336. resolve()
  337. }, 10)
  338. }
  339. offCanvasEl.addEventListener('show.bs.offcanvas', event => {
  340. event.preventDefault()
  341. expectEnd()
  342. })
  343. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  344. reject(new Error('should not fire shown event'))
  345. })
  346. offCanvas.show()
  347. })
  348. })
  349. it('on window load, should make visible an offcanvas element, if its markup contains class "show"', () => {
  350. return new Promise(resolve => {
  351. fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
  352. const offCanvasEl = fixtureEl.querySelector('div')
  353. const spy = spyOn(Offcanvas.prototype, 'show').and.callThrough()
  354. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  355. resolve()
  356. })
  357. window.dispatchEvent(createEvent('load'))
  358. const instance = Offcanvas.getInstance(offCanvasEl)
  359. expect(instance).not.toBeNull()
  360. expect(spy).toHaveBeenCalled()
  361. })
  362. })
  363. it('should trap focus', () => {
  364. return new Promise(resolve => {
  365. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  366. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  367. const offCanvas = new Offcanvas(offCanvasEl)
  368. const spy = spyOn(offCanvas._focustrap, 'activate').and.callThrough()
  369. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  370. expect(spy).toHaveBeenCalled()
  371. resolve()
  372. })
  373. offCanvas.show()
  374. })
  375. })
  376. })
  377. describe('hide', () => {
  378. it('should add `hiding` class during closing and remover `show` & `hiding` classes on end', () => {
  379. return new Promise(resolve => {
  380. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  381. const offCanvasEl = fixtureEl.querySelector('.offcanvas')
  382. const offCanvas = new Offcanvas(offCanvasEl)
  383. offCanvasEl.addEventListener('hide.bs.offcanvas', () => {
  384. expect(offCanvasEl).not.toHaveClass('showing')
  385. expect(offCanvasEl).toHaveClass('show')
  386. })
  387. offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
  388. expect(offCanvasEl).not.toHaveClass('hiding')
  389. expect(offCanvasEl).not.toHaveClass('show')
  390. resolve()
  391. })
  392. offCanvas.show()
  393. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  394. offCanvas.hide()
  395. expect(offCanvasEl).not.toHaveClass('showing')
  396. expect(offCanvasEl).toHaveClass('hiding')
  397. })
  398. })
  399. })
  400. it('should do nothing if already shown', () => {
  401. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  402. const spyTrigger = spyOn(EventHandler, 'trigger').and.callThrough()
  403. const offCanvasEl = fixtureEl.querySelector('div')
  404. const offCanvas = new Offcanvas(offCanvasEl)
  405. const spyHide = spyOn(offCanvas._backdrop, 'hide').and.callThrough()
  406. offCanvas.hide()
  407. expect(spyHide).not.toHaveBeenCalled()
  408. expect(spyTrigger).not.toHaveBeenCalled()
  409. })
  410. it('should hide a shown element', () => {
  411. return new Promise(resolve => {
  412. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  413. const offCanvasEl = fixtureEl.querySelector('div')
  414. const offCanvas = new Offcanvas(offCanvasEl)
  415. const spy = spyOn(offCanvas._backdrop, 'hide').and.callThrough()
  416. offCanvas.show()
  417. offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
  418. expect(offCanvasEl).not.toHaveClass('show')
  419. expect(spy).toHaveBeenCalled()
  420. resolve()
  421. })
  422. offCanvas.hide()
  423. })
  424. })
  425. it('should not fire hidden when hide is prevented', () => {
  426. return new Promise((resolve, reject) => {
  427. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  428. const offCanvasEl = fixtureEl.querySelector('div')
  429. const offCanvas = new Offcanvas(offCanvasEl)
  430. const spy = spyOn(offCanvas._backdrop, 'hide').and.callThrough()
  431. offCanvas.show()
  432. const expectEnd = () => {
  433. setTimeout(() => {
  434. expect(spy).not.toHaveBeenCalled()
  435. resolve()
  436. }, 10)
  437. }
  438. offCanvasEl.addEventListener('hide.bs.offcanvas', event => {
  439. event.preventDefault()
  440. expectEnd()
  441. })
  442. offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
  443. reject(new Error('should not fire hidden event'))
  444. })
  445. offCanvas.hide()
  446. })
  447. })
  448. it('should release focus trap', () => {
  449. return new Promise(resolve => {
  450. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  451. const offCanvasEl = fixtureEl.querySelector('div')
  452. const offCanvas = new Offcanvas(offCanvasEl)
  453. const spy = spyOn(offCanvas._focustrap, 'deactivate').and.callThrough()
  454. offCanvas.show()
  455. offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
  456. expect(spy).toHaveBeenCalled()
  457. resolve()
  458. })
  459. offCanvas.hide()
  460. })
  461. })
  462. })
  463. describe('dispose', () => {
  464. it('should dispose an offcanvas', () => {
  465. fixtureEl.innerHTML = '<div class="offcanvas"></div>'
  466. const offCanvasEl = fixtureEl.querySelector('div')
  467. const offCanvas = new Offcanvas(offCanvasEl)
  468. const backdrop = offCanvas._backdrop
  469. const spyDispose = spyOn(backdrop, 'dispose').and.callThrough()
  470. const focustrap = offCanvas._focustrap
  471. const spyDeactivate = spyOn(focustrap, 'deactivate').and.callThrough()
  472. expect(Offcanvas.getInstance(offCanvasEl)).toEqual(offCanvas)
  473. offCanvas.dispose()
  474. expect(spyDispose).toHaveBeenCalled()
  475. expect(offCanvas._backdrop).toBeNull()
  476. expect(spyDeactivate).toHaveBeenCalled()
  477. expect(offCanvas._focustrap).toBeNull()
  478. expect(Offcanvas.getInstance(offCanvasEl)).toBeNull()
  479. })
  480. })
  481. describe('data-api', () => {
  482. it('should not prevent event for input', () => {
  483. return new Promise(resolve => {
  484. fixtureEl.innerHTML = [
  485. '<input type="checkbox" data-bs-toggle="offcanvas" data-bs-target="#offcanvasdiv1">',
  486. '<div id="offcanvasdiv1" class="offcanvas"></div>'
  487. ].join('')
  488. const target = fixtureEl.querySelector('input')
  489. const offCanvasEl = fixtureEl.querySelector('#offcanvasdiv1')
  490. offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
  491. expect(offCanvasEl).toHaveClass('show')
  492. expect(target.checked).toBeTrue()
  493. resolve()
  494. })
  495. target.click()
  496. })
  497. })
  498. it('should not call toggle on disabled elements', () => {
  499. fixtureEl.innerHTML = [
  500. '<a href="#" data-bs-toggle="offcanvas" data-bs-target="#offcanvasdiv1" class="disabled"></a>',
  501. '<div id="offcanvasdiv1" class="offcanvas"></div>'
  502. ].join('')
  503. const target = fixtureEl.querySelector('a')
  504. const spy = spyOn(Offcanvas.prototype, 'toggle')
  505. target.click()
  506. expect(spy).not.toHaveBeenCalled()
  507. })
  508. it('should call hide first, if another offcanvas is open', () => {
  509. return new Promise(resolve => {
  510. fixtureEl.innerHTML = [
  511. '<button id="btn2" data-bs-toggle="offcanvas" data-bs-target="#offcanvas2"></button>',
  512. '<div id="offcanvas1" class="offcanvas"></div>',
  513. '<div id="offcanvas2" class="offcanvas"></div>'
  514. ].join('')
  515. const trigger2 = fixtureEl.querySelector('#btn2')
  516. const offcanvasEl1 = document.querySelector('#offcanvas1')
  517. const offcanvasEl2 = document.querySelector('#offcanvas2')
  518. const offcanvas1 = new Offcanvas(offcanvasEl1)
  519. offcanvasEl1.addEventListener('shown.bs.offcanvas', () => {
  520. trigger2.click()
  521. })
  522. offcanvasEl1.addEventListener('hidden.bs.offcanvas', () => {
  523. expect(Offcanvas.getInstance(offcanvasEl2)).not.toBeNull()
  524. resolve()
  525. })
  526. offcanvas1.show()
  527. })
  528. })
  529. it('should focus on trigger element after closing offcanvas', () => {
  530. return new Promise(resolve => {
  531. fixtureEl.innerHTML = [
  532. '<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas"></button>',
  533. '<div id="offcanvas" class="offcanvas"></div>'
  534. ].join('')
  535. const trigger = fixtureEl.querySelector('#btn')
  536. const offcanvasEl = fixtureEl.querySelector('#offcanvas')
  537. const offcanvas = new Offcanvas(offcanvasEl)
  538. const spy = spyOn(trigger, 'focus')
  539. offcanvasEl.addEventListener('shown.bs.offcanvas', () => {
  540. offcanvas.hide()
  541. })
  542. offcanvasEl.addEventListener('hidden.bs.offcanvas', () => {
  543. setTimeout(() => {
  544. expect(spy).toHaveBeenCalled()
  545. resolve()
  546. }, 5)
  547. })
  548. trigger.click()
  549. })
  550. })
  551. it('should not focus on trigger element after closing offcanvas, if it is not visible', () => {
  552. return new Promise(resolve => {
  553. fixtureEl.innerHTML = [
  554. '<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas"></button>',
  555. '<div id="offcanvas" class="offcanvas"></div>'
  556. ].join('')
  557. const trigger = fixtureEl.querySelector('#btn')
  558. const offcanvasEl = fixtureEl.querySelector('#offcanvas')
  559. const offcanvas = new Offcanvas(offcanvasEl)
  560. const spy = spyOn(trigger, 'focus')
  561. offcanvasEl.addEventListener('shown.bs.offcanvas', () => {
  562. trigger.style.display = 'none'
  563. offcanvas.hide()
  564. })
  565. offcanvasEl.addEventListener('hidden.bs.offcanvas', () => {
  566. setTimeout(() => {
  567. expect(isVisible(trigger)).toBeFalse()
  568. expect(spy).not.toHaveBeenCalled()
  569. resolve()
  570. }, 5)
  571. })
  572. trigger.click()
  573. })
  574. })
  575. })
  576. describe('jQueryInterface', () => {
  577. it('should create an offcanvas', () => {
  578. fixtureEl.innerHTML = '<div></div>'
  579. const div = fixtureEl.querySelector('div')
  580. jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
  581. jQueryMock.elements = [div]
  582. jQueryMock.fn.offcanvas.call(jQueryMock)
  583. expect(Offcanvas.getInstance(div)).not.toBeNull()
  584. })
  585. it('should not re create an offcanvas', () => {
  586. fixtureEl.innerHTML = '<div></div>'
  587. const div = fixtureEl.querySelector('div')
  588. const offCanvas = new Offcanvas(div)
  589. jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
  590. jQueryMock.elements = [div]
  591. jQueryMock.fn.offcanvas.call(jQueryMock)
  592. expect(Offcanvas.getInstance(div)).toEqual(offCanvas)
  593. })
  594. it('should throw error on undefined method', () => {
  595. fixtureEl.innerHTML = '<div></div>'
  596. const div = fixtureEl.querySelector('div')
  597. const action = 'undefinedMethod'
  598. jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
  599. jQueryMock.elements = [div]
  600. expect(() => {
  601. jQueryMock.fn.offcanvas.call(jQueryMock, action)
  602. }).toThrowError(TypeError, `No method named "${action}"`)
  603. })
  604. it('should throw error on protected method', () => {
  605. fixtureEl.innerHTML = '<div></div>'
  606. const div = fixtureEl.querySelector('div')
  607. const action = '_getConfig'
  608. jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
  609. jQueryMock.elements = [div]
  610. expect(() => {
  611. jQueryMock.fn.offcanvas.call(jQueryMock, action)
  612. }).toThrowError(TypeError, `No method named "${action}"`)
  613. })
  614. it('should throw error if method "constructor" is being called', () => {
  615. fixtureEl.innerHTML = '<div></div>'
  616. const div = fixtureEl.querySelector('div')
  617. const action = 'constructor'
  618. jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
  619. jQueryMock.elements = [div]
  620. expect(() => {
  621. jQueryMock.fn.offcanvas.call(jQueryMock, action)
  622. }).toThrowError(TypeError, `No method named "${action}"`)
  623. })
  624. it('should call offcanvas method', () => {
  625. fixtureEl.innerHTML = '<div></div>'
  626. const div = fixtureEl.querySelector('div')
  627. const spy = spyOn(Offcanvas.prototype, 'show')
  628. jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
  629. jQueryMock.elements = [div]
  630. jQueryMock.fn.offcanvas.call(jQueryMock, 'show')
  631. expect(spy).toHaveBeenCalled()
  632. })
  633. it('should create a offcanvas with given config', () => {
  634. fixtureEl.innerHTML = '<div></div>'
  635. const div = fixtureEl.querySelector('div')
  636. jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
  637. jQueryMock.elements = [div]
  638. jQueryMock.fn.offcanvas.call(jQueryMock, { scroll: true })
  639. const offcanvas = Offcanvas.getInstance(div)
  640. expect(offcanvas).not.toBeNull()
  641. expect(offcanvas._config.scroll).toBeTrue()
  642. })
  643. })
  644. describe('getInstance', () => {
  645. it('should return offcanvas instance', () => {
  646. fixtureEl.innerHTML = '<div></div>'
  647. const div = fixtureEl.querySelector('div')
  648. const offCanvas = new Offcanvas(div)
  649. expect(Offcanvas.getInstance(div)).toEqual(offCanvas)
  650. expect(Offcanvas.getInstance(div)).toBeInstanceOf(Offcanvas)
  651. })
  652. it('should return null when there is no offcanvas instance', () => {
  653. fixtureEl.innerHTML = '<div></div>'
  654. const div = fixtureEl.querySelector('div')
  655. expect(Offcanvas.getInstance(div)).toBeNull()
  656. })
  657. })
  658. describe('getOrCreateInstance', () => {
  659. it('should return offcanvas instance', () => {
  660. fixtureEl.innerHTML = '<div></div>'
  661. const div = fixtureEl.querySelector('div')
  662. const offcanvas = new Offcanvas(div)
  663. expect(Offcanvas.getOrCreateInstance(div)).toEqual(offcanvas)
  664. expect(Offcanvas.getInstance(div)).toEqual(Offcanvas.getOrCreateInstance(div, {}))
  665. expect(Offcanvas.getOrCreateInstance(div)).toBeInstanceOf(Offcanvas)
  666. })
  667. it('should return new instance when there is no Offcanvas instance', () => {
  668. fixtureEl.innerHTML = '<div></div>'
  669. const div = fixtureEl.querySelector('div')
  670. expect(Offcanvas.getInstance(div)).toBeNull()
  671. expect(Offcanvas.getOrCreateInstance(div)).toBeInstanceOf(Offcanvas)
  672. })
  673. it('should return new instance when there is no offcanvas instance with given configuration', () => {
  674. fixtureEl.innerHTML = '<div></div>'
  675. const div = fixtureEl.querySelector('div')
  676. expect(Offcanvas.getInstance(div)).toBeNull()
  677. const offcanvas = Offcanvas.getOrCreateInstance(div, {
  678. scroll: true
  679. })
  680. expect(offcanvas).toBeInstanceOf(Offcanvas)
  681. expect(offcanvas._config.scroll).toBeTrue()
  682. })
  683. it('should return the instance when exists without given configuration', () => {
  684. fixtureEl.innerHTML = '<div></div>'
  685. const div = fixtureEl.querySelector('div')
  686. const offcanvas = new Offcanvas(div, {
  687. scroll: true
  688. })
  689. expect(Offcanvas.getInstance(div)).toEqual(offcanvas)
  690. const offcanvas2 = Offcanvas.getOrCreateInstance(div, {
  691. scroll: false
  692. })
  693. expect(offcanvas).toBeInstanceOf(Offcanvas)
  694. expect(offcanvas2).toEqual(offcanvas)
  695. expect(offcanvas2._config.scroll).toBeTrue()
  696. })
  697. })
  698. })