Repository: exadel-inc/esl
Pull request #1853 (2023-08-08 20:49:05)

fix(esl-toggleable): fix activator when close-on inner trigger click handled (#1852)
Fix activator when close-on inner trigger click handled + test coverage for related functionality

Closes: #1852


Patch:
diff --git a/src/modules/esl-toggleable/core/esl-toggleable.ts b/src/modules/esl-toggleable/core/esl-toggleable.ts
index e98fd520a..c755c756a 100644
--- a/src/modules/esl-toggleable/core/esl-toggleable.ts
+++ b/src/modules/esl-toggleable/core/esl-toggleable.ts
@@ -9,6 +9,7 @@ import {DeviceDetector} from '../../esl-utils/environment/device-detector';
 import {DelayedTask} from '../../esl-utils/async/delayed-task';
 import {ESLBaseElement} from '../../esl-base-element/core';
 import {findParent, isMatches} from '../../esl-utils/dom/traversing';
+import type {DelegatedEvent} from '../../esl-event-listener/core/types';
 
 /** Default Toggleable action params type definition */
 export interface ESLToggleableActionParams {
@@ -304,12 +305,13 @@ export class ESLToggleable extends ESLBaseElement {
     return !(e instanceof KeyboardEvent && SYSTEM_KEYS.includes(e.key));
   }
 
-  @listen({
-    event: 'click',
-    selector: (el: ESLToggleable) => el.closeTrigger || ''
-  })
-  protected _onCloseClick(e: MouseEvent): void {
-    this.hide({initiator: 'close', activator: e.target as HTMLElement, event: e});
+  @listen({event: 'click', selector: (el: ESLToggleable) => el.closeTrigger || ''})
+  protected _onCloseClick(e: DelegatedEvent<MouseEvent>): void {
+    this.hide({
+      initiator: 'close',
+      activator: e.$delegate as HTMLElement,
+      event: e
+    });
   }
 
   @listen({


Test patch:
diff --git a/src/modules/esl-toggleable/test/toggleable.close-on.test.ts b/src/modules/esl-toggleable/test/toggleable.close-on.test.ts
new file mode 100644
index 000000000..d06ffb360
--- /dev/null
+++ b/src/modules/esl-toggleable/test/toggleable.close-on.test.ts
@@ -0,0 +1,102 @@
+import {ESLToggleable} from '../core/esl-toggleable';
+
+describe('ESLToggleable: close-on trigger handler', () => {
+  const $template = document.createElement('template');
+  $template.innerHTML = `
+    <esl-toggleable class='toggleable-close-on-test' close-on='.trigger-close' defaultParams='{delay: -1}'>
+      <button type='button' class='trigger trigger-close'><span>Close<span></button>
+      <button type='button' class='trigger trigger-additional'><span>Trigger<span></button>
+    </esl-toggleable>
+    <button type='button' class='trigger-close trigger-outside toggleable-close-on-test'>
+      <span>Close<span>
+    </button>
+  `;
+  const elements = {
+    get $el() {
+      return document.querySelector('esl-toggleable.toggleable-close-on-test') as ESLToggleable;
+    },
+    get $trigger() {
+      return document.querySelector('.trigger-close') as HTMLElement;
+    },
+    get $triggerOutside() {
+      return document.querySelector('.trigger-outside') as HTMLElement;
+    },
+    get $triggerAdditional() {
+      return document.querySelector('.trigger-additional') as HTMLElement;
+    }
+  };
+
+  beforeAll(() => ESLToggleable.register());
+  beforeEach(() => document.body.appendChild($template.content.cloneNode(true)));
+  afterEach(() => [...document.querySelectorAll('.toggleable-close-on-test')].forEach(($el) => $el.remove()));
+
+  test('Close-on trigger click leads to toggleable hide', () => {
+    const {$el, $trigger} = elements;
+
+    $el.show();
+    expect($el.open).toBe(true);
+
+    $trigger.click();
+    expect($el.open).toBe(false);
+  });
+
+  test('Click on inner trigger content leads to toggleable hide', () => {
+    const {$el, $trigger} = elements;
+
+    $el.show();
+    expect($el.open).toBe(true);
+
+    $trigger.querySelector('span')?.click();
+    expect($el.open).toBe(false);
+  });
+
+  test('Close-on trigger click outside toggleable does not leads to toggleable hide', () => {
+    const {$el, $triggerOutside} = elements;
+
+    $el.show();
+    expect($el.open).toBe(true);
+
+    $triggerOutside.click();
+    expect($el.open).toBe(true);
+  });
+
+  test('Close-on trigger click on additional trigger does not leads to toggleable hide', () => {
+    const {$el, $triggerAdditional} = elements;
+
+    $el.show();
+    expect($el.open).toBe(true);
+
+    $triggerAdditional.click();
+    expect($el.open).toBe(true);
+  });
+
+  test('Dynamic change of close-on attribute processed correctly', () => {
+    const {$el, $triggerAdditional} = elements;
+
+    $el.show();
+    $el.setAttribute('close-on', '.trigger');
+
+    $triggerAdditional.click();
+    expect($el.open).toBe(false);
+  });
+
+  test('ESLToggleableActionParams contains correct activator details', () => {
+    const {$el, $trigger} = elements;
+
+    const $fn = jest.fn((e) => console.log(e));
+
+    $el.show();
+    $el.addEventListener('esl:hide', $fn);
+    $trigger.querySelector('span')?.click();
+
+    expect($fn).lastCalledWith(expect.objectContaining({
+      type: 'esl:hide',
+      detail: expect.objectContaining({
+        params: expect.objectContaining({
+          initiator: 'close',
+          activator: $trigger
+        })
+      })
+    }));
+  });
+});