initStimulus.test.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { Application, Controller } from '@hotwired/stimulus';
  2. import { initStimulus } from './initStimulus';
  3. jest.useFakeTimers();
  4. /**
  5. * Example controller.
  6. */
  7. class WordCountController extends Controller {
  8. static values = { max: { default: 10, type: Number } };
  9. connect() {
  10. const output = document.createElement('output');
  11. output.setAttribute('name', 'word-count');
  12. output.setAttribute('for', this.element.id);
  13. output.style.float = 'inline-end';
  14. this.element.insertAdjacentElement('beforebegin', output);
  15. this.output = output;
  16. this.updateCount();
  17. }
  18. setupOutput() {
  19. if (this.output) return;
  20. const template = document.createElement('template');
  21. template.innerHTML = `<output name='word-count' for='${this.element.id}' class='output-label'></output>`;
  22. const output = template.content.firstChild;
  23. this.element.insertAdjacentElement('beforebegin', output);
  24. this.output = output;
  25. }
  26. updateCount(event) {
  27. const value = event ? event.target.value : this.element.value;
  28. const words = (value || '').split(' ');
  29. this.output.textContent = `${words.length} / ${this.maxValue} words`;
  30. }
  31. disconnect() {
  32. this.output && this.output.remove();
  33. }
  34. }
  35. describe('initStimulus', () => {
  36. const mockControllerConnected = jest.fn();
  37. class TestMockController extends Controller {
  38. static targets = ['item'];
  39. connect() {
  40. mockControllerConnected();
  41. this.itemTargets.forEach((item) => {
  42. item.setAttribute('hidden', '');
  43. });
  44. }
  45. }
  46. beforeAll(() => {
  47. document.body.innerHTML = `
  48. <main>
  49. <section data-controller="w-test-mock">
  50. <div id="item" data-w-test-mock-target="item"></div>
  51. </section>
  52. </main>`;
  53. });
  54. let application;
  55. it('should initialise a stimulus application', () => {
  56. const definitions = [
  57. { identifier: 'w-test-mock', controllerConstructor: TestMockController },
  58. ];
  59. expect(mockControllerConnected).not.toHaveBeenCalled();
  60. application = initStimulus({ debug: false, definitions });
  61. expect(application).toBeInstanceOf(Application);
  62. });
  63. it('should have set the debug value based on the option provided', () => {
  64. expect(application.debug).toEqual(false);
  65. });
  66. it('should have loaded the controller definitions supplied', () => {
  67. expect(mockControllerConnected).toHaveBeenCalled();
  68. expect(application.controllers).toHaveLength(1);
  69. expect(application.controllers[0]).toBeInstanceOf(TestMockController);
  70. });
  71. it('should support the documented approach for registering a controller via a class with register', async () => {
  72. const section = document.createElement('section');
  73. section.id = 'example-b';
  74. section.innerHTML = `<input value="some words" id="example-b-input" data-controller="example-b" data-action="change->example-b#updateCount" data-example-b-max-value="5" />`;
  75. // register a controller
  76. application.register('example-b', WordCountController);
  77. // before controller element added - should not include an `output` element
  78. expect(document.querySelector('#example-b > output')).toEqual(null);
  79. document.querySelector('section').after(section);
  80. await Promise.resolve();
  81. // after controller connected - should have an output element
  82. expect(document.querySelector('#example-b > output').innerHTML).toEqual(
  83. '2 / 5 words',
  84. );
  85. await Promise.resolve();
  86. // should respond to changes on the input
  87. const input = document.querySelector('#example-b > input');
  88. input.setAttribute('value', 'even more words');
  89. input.dispatchEvent(new Event('change'));
  90. expect(document.querySelector('#example-b > output').innerHTML).toEqual(
  91. '3 / 5 words',
  92. );
  93. // removal of the input should also remove the output (disconnect method)
  94. input.remove();
  95. await Promise.resolve();
  96. // should call the disconnect method (removal of the injected HTML)
  97. expect(document.querySelector('#example-b > output')).toEqual(null);
  98. // clean up
  99. section.remove();
  100. });
  101. });