§
    ‘Ç½jˆ0  ã                   ó&  — d Z ddlZddlmc mZ ddlZddlZddl	Z	ddl
mZ  ee¦  «                             ¦   «         j        j        j        Zedz  dz  dz  dz  dz  Zd	ed
efd„Z G d„ de	j        ¦  «        Zedk    r e	j        ¦   «          dS dS )aJ  
Adversarial Challenger Test Suite: Dual-Platform Controls & Listener Deduplication (Milestone 1)
Empirical verification of:
1. Listener deduplication: single click on action slots invokes exactly one action execution
2. Child DOM click bubbling: clicks on icons or overlays do not duplicate actions
3. Full PC control scheme: Q, 2 (WASD-safe), E, R, Space, LMB, Potion (4)
4. Input element isolation: typing in input/textarea/select does not trigger skills
5. Re-registration listener behavior on multi-charge skills
6. Mobile touch cluster ergonomics: touch target sizing and spacing
é    N)ÚPathÚclientÚwebappÚjsÚuizskill_bar_controller.jsÚscript_bodyÚreturnc                 ó  — d| › d�}t          j        ddd|gddt          t          ¦  «        ¬¦  «        }|j        dk    rt          d	|j        › d
|j        › �¦  «        ‚t          j	        |j         
                    ¦   «         ¦  «        S )zGExecutes an empirical JavaScript test harness in Node.js with mock DOM.aC
  
import fs from 'fs';

function setupTestEnvironment() {
  const elMap = {};
  const windowListeners = {};
  const docListeners = {};

  function makeEl(id, tagName = 'BUTTON') {
    const list = new Set();
    const handlers = {};
    const dataset = {};
    const styles = {};
    const children = [];
    const el = {
      id, tagName, dataset, children,
      classList: {
        add: (c) => list.add(c),
        remove: (c) => list.delete(c),
        contains: (c) => list.has(c),
        toggle: (c, f) => { f = f === undefined ? !list.has(c) : f; f ? list.add(c) : list.delete(c); return f; }
      },
      style: {
        setProperty: (k, v) => { styles[k] = v; },
        getProperty: (k) => styles[k]
      },
      addEventListener: (type, fn) => {
        (handlers[type] = handlers[type] || []).push(fn);
      },
      dispatchEvent: function(evt) {
        evt.target = evt.target || this;
        evt.currentTarget = this;
        const list = handlers[evt.type] || [];
        list.forEach(fn => fn(evt));
      },
      querySelector: function(sel) {
        const child = makeEl(id + '_' + sel, 'DIV');
        children.push(child);
        return child;
      },
      offsetWidth: 44, offsetHeight: 44
    };
    return el;
  }

  const defaultIds = [
    'hud-skill-bar', 'skill-potion', 'skill-fire', 'skill-slot-w',
    'skill-thunder', 'skill-frost', 'skill-primary', 'skill-dodge'
  ];
  defaultIds.forEach(id => { elMap[id] = makeEl(id); });

  const win = {
    addEventListener: (t, fn) => { (windowListeners[t] = windowListeners[t] || []).push(fn); },
    dispatchEvent: (evt) => { (windowListeners[evt.type] || []).forEach(fn => fn(evt)); },
    performance: { now: () => Date.now() },
    setTimeout: (fn) => { fn(); },
    clearTimeout: () => {}
  };

  const doc = {
    getElementById: (id) => elMap[id] || (elMap[id] = makeEl(id)),
    querySelector: (sel) => elMap[sel.replace('#', '')] || makeEl(sel),
    activeElement: { tagName: 'BODY' },
    readyState: 'complete',
    addEventListener: (t, fn) => { (docListeners[t] = docListeners[t] || []).push(fn); },
    dispatchEvent: (evt) => { (docListeners[evt.type] || []).forEach(fn => fn(evt)); }
  };

  global.window = win;
  global.document = doc;
  global.performance = win.performance;
  global.requestAnimationFrame = (fn) => 1;
  global.cancelAnimationFrame = () => {};

  return { elMap, win, doc, windowListeners, docListeners };
}

(async () => {
  try {
    const env = setupTestEnvironment();
    const mod = await import('./client/webapp/js/ui/skill_bar_controller.js');
    const controller = mod.skillBarController;
    zJ
  } catch (err) {
    console.error(err);
    process.exit(1);
  }
})();
Únodez--input-type=modulez-eT)Úcapture_outputÚtextÚcwdr   zNode execution failed:
ú
)Ú
subprocessÚrunÚstrÚPROJECT_ROOTÚ
returncodeÚRuntimeErrorÚstderrÚstdoutÚjsonÚloadsÚstrip)r   Ú	bootstrapÚress      úMC:\Projects\FreeExile\tests\unit\test_challenger_m1_dual_platform_controls.pyÚrun_node_skill_harnessr      s¨   € ðXðd ðeXð Xð X€Iõr Œ.Ø	Ð&¨¨iÐ8Ø $­CµÑ,=Ô,=ðñ ô €Cð „~˜ÒÐÝÐP°c´jÐPÐPÀCÄJÐPÐPÑQÔQÐQÝŒ:�c”j×&Ò&Ñ(Ô(Ñ)Ô)Ð)ó    c                   ó6   — e Zd ZdZd„ Zd„ Zd„ Zd„ Zd„ Zd„ Z	dS )	Ú(TestDualPlatformControlsAndDeduplicationzWEmpirical adversarial test suite for dual-platform controls and listener deduplication.c                 ó  — d}t          |¦  «        }dD ]u}|                      ||         d         dd|› d�¦  «         |                      ||         d         d|› d�¦  «         |                      ||         d	         d|› d
�¦  «         ŒvdS )zMVerifies that each slot invokes its respective action exactly once per click.aë  
        const counts = { fire: 0, thunder: 0, frost: 0, primary: 0, dodge: 0, potion: 0 };
        global.window.doFire = () => { counts.fire++; };
        global.window.doThunder = () => { counts.thunder++; };
        global.window.doFrost = () => { counts.frost++; };
        global.window.doPrimaryAttack = () => { counts.primary++; };
        global.window.doDodge = () => { counts.dodge++; };
        global.window.drinkHealthPotion = () => { counts.potion++; };

        const slots = [
          { id: 'skill-fire', key: 'fire' },
          { id: 'skill-thunder', key: 'thunder' },
          { id: 'skill-frost', key: 'frost' },
          { id: 'skill-primary', key: 'primary' },
          { id: 'skill-dodge', key: 'dodge' },
          { id: 'skill-potion', key: 'potion' }
        ];

        const clicks = {};
        slots.forEach(s => {
          const el = env.elMap[s.id];
          let prevented = false, stopped = false;
          el.dispatchEvent({
            type: 'click',
            preventDefault: () => { prevented = true; },
            stopPropagation: () => { stopped = true; }
          });
          clicks[s.key] = { count: counts[s.key], prevented, stopped };
        });

        console.log(JSON.stringify(clicks));
        )ÚfireÚthunderÚfrostÚprimaryÚdodgeÚpotionÚcounté   zSlot z' must execute exactly 1 action on clickÚ	preventedz must preventDefaultÚstoppedz must stopPropagationN©r   ÚassertEqualÚ
assertTrue)Úselfr   ÚrÚkeys       r   Ú*test_single_click_deduplication_clean_initzSTestDualPlatformControlsAndDeduplication.test_single_click_deduplication_clean_init|   s·   € ðˆõ@ # 2Ñ&Ô&ˆØMð 	Sð 	SˆCØ×Ò˜Q˜sœV Gœ_¨aÐ1e¸Ð1eÐ1eÐ1eÑfÔfÐfØ�OŠO˜A˜cœF ;Ô/Ð1R¸Ð1RÐ1RÐ1RÑSÔSÐSØ�OŠO˜A˜cœF 9Ô-Ð/Q°sÐ/QÐ/QÐ/QÑRÔRÐRÐRð	Sð 	Sr   c                 ób   — d}t          |¦  «        }|                      |d         dd¦  «         dS )zVClicks dispatched to child icon/overlay elements bubble cleanly without double action.ay  
        let fireCount = 0;
        global.window.doFire = () => { fireCount++; };

        const fireEl = env.elMap['skill-fire'];
        // Simulate clicking on child cooldown overlay
        const overlay = fireEl.querySelector('.cooldown-overlay');
        overlay.dispatchEvent({
          type: 'click',
          preventDefault: () => {},
          stopPropagation: () => {}
        });

        // Bubble to parent element
        fireEl.dispatchEvent({
          type: 'click',
          preventDefault: () => {},
          stopPropagation: () => {}
        });

        console.log(JSON.stringify({ fireCount }));
        Ú	fireCountr*   z0Child bubbling must not trigger multiple actionsN©r   r.   ©r0   r   r1   s      r   Ú/test_child_element_click_bubbling_deduplicationzXTestDualPlatformControlsAndDeduplication.test_child_element_click_bubbling_deduplication¤   s;   € ðˆõ, # 2Ñ&Ô&ˆà×Ò˜˜;œ¨Ð,^Ñ_Ô_Ð_Ð_Ð_r   c                 óŒ  — d}t          |¦  «        }|                      d|d         ¦  «         |                      dd„ |d         D ¦   «         ¦  «         |                      d|d         ¦  «         |                      d|d         ¦  «         |                      d|d         ¦  «         |                      d	|d         ¦  «         d
S )zNVerifies PC keybindings: Q, 2, E, R, Space, Potion (4), and WASD 'W' immunity.a˜  
        const log = [];
        global.window.doFire = () => { log.push('Q'); };
        global.window.doThunder = () => { log.push('2'); };
        global.window.doFrost = () => { log.push('R'); };
        global.window.doDodge = () => { log.push('SPACE'); };
        global.window.drinkHealthPotion = () => { log.push('POTION'); };

        // 1. Press 'q'
        env.win.dispatchEvent({ type: 'keydown', key: 'q', code: 'KeyQ', preventDefault: () => {} });
        // 2. Press 'W' (MUST NOT trigger slot 2!)
        env.win.dispatchEvent({ type: 'keydown', key: 'w', code: 'KeyW', preventDefault: () => {} });
        env.win.dispatchEvent({ type: 'keydown', key: 'W', code: 'KeyW', preventDefault: () => {} });
        // 3. Press '2' (MUST trigger slot 2!)
        env.win.dispatchEvent({ type: 'keydown', key: '2', code: 'Digit2', preventDefault: () => {} });
        // 4. Press 'e'
        env.win.dispatchEvent({ type: 'keydown', key: 'e', code: 'KeyE', preventDefault: () => {} });
        // 5. Press 'r'
        env.win.dispatchEvent({ type: 'keydown', key: 'r', code: 'KeyR', preventDefault: () => {} });
        // 6. Press Space
        env.win.dispatchEvent({ type: 'keydown', key: ' ', code: 'Space', preventDefault: () => {} });
        // 7. Press '4'
        env.win.dispatchEvent({ type: 'keydown', key: '4', code: 'Digit4', preventDefault: () => {} });

        console.log(JSON.stringify({ log }));
        ÚQÚlogÚwc                 óB   — g | ]}|d k    ¯|                      ¦   «         ‘ŒS )r<   )Úlower)Ú.0Úxs     r   ú
<listcomp>zgTestDualPlatformControlsAndDeduplication.test_keyboard_pc_bindings_qwer_space_mouse.<locals>.<listcomp>Þ   s%   € ÐGÐGÐG¨Q¸aÀ3ºh¸h˜qŸwšw™yœy¸h¸h¸hr   Ú2ÚRÚSPACEÚPOTIONN)r   ÚassertInÚassertNotInr7   s      r   Ú*test_keyboard_pc_bindings_qwer_space_mousezSTestDualPlatformControlsAndDeduplication.test_keyboard_pc_bindings_qwer_space_mouseÀ   s¿   € ðˆõ4 # 2Ñ&Ô&ˆØ�Š�c˜1˜Uœ8Ñ$Ô$Ð$Ø×Ò˜ÐGÐG°!°E´(ÐGÑGÔGÑHÔHÐHØ�Š�c˜1˜Uœ8Ñ$Ô$Ð$Ø�Š�c˜1˜Uœ8Ñ$Ô$Ð$Ø�Š�g˜q œxÑ(Ô(Ð(Ø�Š�h  %¤Ñ)Ô)Ð)Ð)Ð)r   c                 ób   — d}t          |¦  «        }|                      |d         dd¦  «         dS )zLVerifies keydown events inside input/textarea/select are completely ignored.a#  
        let actionCount = 0;
        global.window.doFire = () => { actionCount++; };
        global.window.doDodge = () => { actionCount++; };

        env.doc.activeElement = { tagName: 'INPUT' };

        ['q', 'w', 'e', 'r', ' ', '1', '2', '3', '4'].forEach(key => {
          env.win.dispatchEvent({
            type: 'keydown',
            key,
            code: key === ' ' ? 'Space' : 'Key' + key.toUpperCase(),
            preventDefault: () => {}
          });
        });

        console.log(JSON.stringify({ actionCount }));
        ÚactionCountr   z1Input focus must block all skill keydown triggersNr6   r7   s      r   Ú+test_input_element_focus_keyboard_isolationzTTestDualPlatformControlsAndDeduplication.test_input_element_focus_keyboard_isolationä   s<   € ðˆõ$ # 2Ñ&Ô&ˆØ×Ò˜˜=Ô)¨1Ð.aÑbÔbÐbÐbÐbr   c                 ó
  — d}t          |¦  «        }|                      |d         d¦  «         |                      |d         dd¦  «         |                      |d         d¦  «         |                      |d         d	¦  «         d
S )z[Asserts clicking during active cooldown does not execute action and triggers blocked state.a  
        let fireCount = 0;
        global.window.doFire = () => { fireCount++; };

        const fireEl = env.elMap['skill-fire'];
        // First click: triggers action and starts 1.5s cooldown
        fireEl.dispatchEvent({ type: 'click', preventDefault: () => {}, stopPropagation: () => {} });
        const countAfterFirst = fireCount;
        const onCd = controller.isSlotOnCooldown('1');

        // Second click while on cooldown: must NOT call doFire
        fireEl.dispatchEvent({ type: 'click', preventDefault: () => {}, stopPropagation: () => {} });
        const countAfterSecond = fireCount;
        const isBlocked = fireEl.classList.contains('is-cooldown-blocked');

        console.log(JSON.stringify({ countAfterFirst, countAfterSecond, onCd, isBlocked }));
        ÚcountAfterFirstr*   ÚcountAfterSecondz*Blocked click must not invoke skill actionÚonCdzSlot must be on cooldownÚ	isBlockedz&Slot must show blocked visual feedbackNr-   r7   s      r   Ú3test_cooldown_blocked_click_feedback_without_actionz\TestDualPlatformControlsAndDeduplication.test_cooldown_blocked_click_feedback_without_actionû   s‹   € ðˆõ" # 2Ñ&Ô&ˆØ×Ò˜Ð,Ô-¨qÑ1Ô1Ð1Ø×Ò˜Ð-Ô.°Ð3_Ñ`Ô`Ð`Ø�Š˜˜&œ	Ð#=Ñ>Ô>Ð>Ø�Š˜˜+œÐ(PÑQÔQÐQÐQÐQr   c                 óœ   — d}t          |¦  «        }|                      |d         dd¦  «         |                      |d         dd¦  «         dS )	zcAdversarial stress-test: registering slot twice creates duplicate listeners on multi-charge skills.aœ  
        let dodgeCount = 0;
        global.window.doDodge = () => { dodgeCount++; };

        const dodgeEl = env.elMap['skill-dodge'];
        // Re-register dodge slot (simulating dynamic slot update)
        controller.registerSlot('dodge', {
          element: dodgeEl, key: 'SPACE', charges: 3, cooldownTotal: 3.0,
          onActivate: () => { global.window.doDodge(); }
        });

        // Click once
        dodgeEl.dispatchEvent({ type: 'click', preventDefault: () => {}, stopPropagation: () => {} });
        const chargesRemaining = controller.slots.get('dodge').charges;

        console.log(JSON.stringify({ dodgeCount, chargesRemaining }));
        Ú
dodgeCounté   z4Confirms duplicate click listener on re-registrationÚchargesRemainingr*   z)Dodge charges decreased by 2 instead of 1Nr6   r7   s      r   Ú/test_re_registration_duplicate_listener_anomalyzXTestDualPlatformControlsAndDeduplication.test_re_registration_duplicate_listener_anomaly  sZ   € ðˆõ" # 2Ñ&Ô&ˆà×Ò˜˜<œ¨!Ð-cÑdÔdÐdØ×Ò˜Ð-Ô.°Ð3^Ñ_Ô_Ð_Ð_Ð_r   N)
Ú__name__Ú
__module__Ú__qualname__Ú__doc__r3   r8   rH   rK   rQ   rV   © r   r   r!   r!   y   s‡   € € € € € ØaÐað&Sð &Sð &SðP`ð `ð `ð8"*ð "*ð "*ðHcð cð cð.Rð Rð Rð2`ð `ð `ð `ð `r   r!   Ú__main__)rZ   ÚbuiltinsÚ@py_builtinsÚ_pytest.assertion.rewriteÚ	assertionÚrewriteÚ
@pytest_arr   r   ÚunittestÚpathlibr   Ú__file__ÚresolveÚparentr   ÚSKILL_BAR_JSr   Údictr   ÚTestCaser!   rW   Úmainr[   r   r   ú<module>rl      s)  ðð	ð 	ð € € € € € € € € € € € € €€€€Ø Ð Ð Ð Ø €€€Ø Ð Ð Ð Ð Ð àˆt�H‰~Œ~×%Ò%Ñ'Ô'Ô.Ô5Ô<€Ø˜hÑ&¨Ñ1°DÑ8¸4Ñ?ÐB[Ñ[€ða*¨ð a*°ð a*ð a*ð a*ð a*ðHq`ð q`ð q`ð q`ð q`¨xÔ/@ñ q`ô q`ð q`ðh ˆzÒÐØ€H„M�O„O€O€O€Oð Ðr   