§
    ¢"¿j-;  ã                  ó6  — d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlmZ ddlZ ee¦  «                             ¦   «         j        j        j        Zdd	„Z G d
„ d¦  «        Z G d„ d¦  «        Z G d„ d¦  «        Z G d„ d¦  «        Z G d„ d¦  «        ZdS )zÇ
Unit Test Suite for Client WebApp UI Modules (Milestone 5)
Empirical verification of PartyUI, SocialUI, TeleportHUD, TownPortalController,
and 9-Language i18n Catalog Parity. Soft Cap <= 350 lines.
é    )ÚannotationsN)ÚPathÚscript_bodyÚstrÚreturnÚdictc                ó  — 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 )z-Runs a simulated DOM test harness in Node.js.aç	  
import fs from 'fs';

function setupDOM() {
  const elMap = {}, winListeners = {}, docListeners = {};
  class HTMLElement {}
  class Image { constructor() { this.src = ''; } }
  class CustomEvent { constructor(t, i = {}) { this.type = t; this.detail = i.detail || {}; } }
  function makeEl(id, tag = 'DIV') {
    const classes = new Set(), handlers = {}, children = [];
    let _html = '';
    return {
      id, tagName: tag, children, dataset: {}, style: { width: '0%' }, value: '',
      classList: { add: (c) => classes.add(c), remove: (c) => classes.delete(c), contains: (c) => classes.has(c) },
      get innerHTML() { return _html; }, set innerHTML(val) { _html = val; children.length = 0; },
      addEventListener: (t, fn) => { (handlers[t] = handlers[t] || []).push(fn); },
      removeEventListener: (t, fn) => { if (handlers[t]) handlers[t] = handlers[t].filter(f => f !== fn); },
      dispatchEvent: function(evt) { evt.target = evt.target || this; evt.currentTarget = this; (handlers[evt.type] || []).forEach(fn => fn(evt)); },
      querySelector: (sel) => makeEl(id + '_' + sel), querySelectorAll: (sel) => [makeEl(id + '_1'), makeEl(id + '_2')],
      appendChild: (c) => { children.push(c); return c; }
    };
  }
  const win = {
    addEventListener: (t, fn) => { (winListeners[t] = winListeners[t] || []).push(fn); },
    removeEventListener: (t, fn) => { if (winListeners[t]) winListeners[t] = winListeners[t].filter(f => f !== fn); },
    dispatchEvent: (evt) => { (winListeners[evt.type] || []).forEach(fn => fn(evt)); return true; },
    CustomEvent, HTMLElement, Image, FreeExileI18n: null
  };
  const doc = {
    getElementById: (id) => elMap[id] || (elMap[id] = makeEl(id)),
    querySelector: (sel) => elMap[sel.replace('#', '')] || makeEl(sel),
    querySelectorAll: () => [], createElement: (tag) => makeEl('auto_' + Math.random(), tag),
    addEventListener: (t, fn) => { (docListeners[t] = docListeners[t] || []).push(fn); },
    removeEventListener: (t, fn) => { if (docListeners[t]) docListeners[t] = docListeners[t].filter(f => f !== fn); },
    dispatchEvent: (evt) => { (docListeners[evt.type] || []).forEach(fn => fn(evt)); return true; },
    body: makeEl('body')
  };
  global.window = win; global.document = doc; global.CustomEvent = CustomEvent;
  global.HTMLElement = HTMLElement; global.Image = Image;
  global.requestAnimationFrame = (fn) => 1; global.cancelAnimationFrame = () => {};
  return { elMap, win, doc, winListeners };
}

(async () => {
  try {
    const env = setupDOM();
    zJ
  } catch (err) {
    console.error(err);
    process.exit(1);
  }
})();
Únodez--input-type=modulez-eT)Úcapture_outputÚtextÚcwdr   zNode execution failed:
ú
)Ú
subprocessÚrunr   ÚPROJECT_ROOTÚ
returncodeÚRuntimeErrorÚstderrÚstdoutÚjsonÚloadsÚstrip)r   Ú	bootstrapÚress      ú:C:\Projects\FreeExile\tests\unit\test_client_ui_modules.pyÚrun_node_harnessr      s¤   € ð4ð\ ð]4ð 4ð 4€Iõj Œ.Ø	Ð&¨¨iÐ8Ø $­CµÑ,=Ô,=ðñ ô €Cð „~˜ÒÐÝÐP°c´jÐPÐPÀCÄJÐPÐPÑQÔQÐQÝŒ:�c”j×&Ò&Ñ(Ô(Ñ)Ô)Ð)ó    c                  ó   — e Zd Zdd„Zdd„ZdS )ÚTestClientPartyUIr   ÚNonec                óp  — d}t          |¦  «        }|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d	         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d
         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         d         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         d         d         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}d S )Nuù  
        const { PartyUI } = await import('./client/webapp/js/ui/party_ui.js');
        const partyUI = new PartyUI({ currentUserId: 'p1' });
        partyUI.init();
        const partyState = {
          party_id: 'party_101', leader_id: 'p1', loot_allocation_mode: 'SHORT',
          members: [
            { player_id: 'p1', character_name: 'HeroOne', level: 50, hp: 100, max_hp: 100, mana: 50, max_mana: 50, role: 'LEADER', is_online: true },
            { player_id: 'p2', character_name: 'BuddyTwo', level: 48, hp: 80, max_hp: 100, mana: 40, max_mana: 50, role: 'MEMBER', is_online: true },
            { player_id: 'p3', character_name: 'MageThree', level: 45, hp: 60, max_hp: 100, mana: 50, max_mana: 50, role: 'MEMBER', is_online: false }
          ]
        };
        partyUI.updateParty(partyState);
        const isLeader = partyUI.isLeader();
        const html = partyUI.container.innerHTML;
        let actionDispatched = null;
        partyUI.onActionCallback = (action, payload) => { actionDispatched = { action, payload }; };
        partyUI.setLootMode('PERMANENT');
        const lootAction = actionDispatched;
        partyUI.kickMember('p3');
        const kickAction = actionDispatched;
        console.log(JSON.stringify({
          isLeader, hasPartyTitle: html.includes('ðŸ‘¥'), hasLootBadge: html.includes('party-loot-badge'),
          hasFramesList: html.includes('party-frames-list'), hasLeaderControls: html.includes('party-leader-controls'),
          lootAction, kickAction
        }));
        ÚisLeaderT©Úis©z%(py1)s is %(py4)s©Úpy1Úpy4úassert %(py6)sÚpy6ÚhasPartyTitleÚhasLootBadgeÚhasFramesListÚhasLeaderControlsÚ
lootActionÚactionÚset_loot_mode©ú==©z%(py1)s == %(py4)sÚ
kickActionÚpayloadÚ	member_idÚp3©r   Ú
@pytest_arÚ_call_reprcompareÚ	_safereprÚAssertionErrorÚ_format_explanation©ÚselfÚscriptÚoutÚ@py_assert0Ú@py_assert3Ú@py_assert2Ú@py_format5Ú@py_format7s           r   Ú+test_party_ui_lifecycle_and_leader_controlsz=TestClientPartyUI.test_party_ui_lifecycle_and_leader_controlsQ   sÉ  € ðˆõ6 ˜vÑ&Ô&ˆØ�:ŒÐ& $Ð&ˆ $Ð&Ð&Ð&Ð&Õ&Ô&Ð&Ð&Ð&Ð&ˆ $Ð&Ñ&Ô&Õ&Ô&ˆÑ&Ô&Õ&Ô& $Ñ&Ô&Ð&Ð&Ñ&Ð&Ð&Ð&Ð&Ð&Ñ&Ð&Õ&Õ&Ô&Ð&Ñ&Ô&Ñ&Ô&Ð&Ð&Ð&Ð&Ð&Ð&Ð&Ø�?Ô#Ð+ tÐ+Ð# tÐ+Ð+Ð+Ð+Õ+Ô+Ð+Ð+Ð+Ð+Ð# tÐ+Ñ+Ô+Õ+Ô+Ð#Ñ+Ô+Õ+Ô+ tÑ+Ô+Ð+Ð+Ñ+Ð+Ð+Ð+Ð+Ð+Ñ+Ð+Õ+Õ+Ô+Ð+Ñ+Ô+Ñ+Ô+Ð+Ð+Ð+Ð+Ð+Ð+Ð+Ø�>Ô"Ð* dÐ*Ð" dÐ*Ð*Ð*Ð*Õ*Ô*Ð*Ð*Ð*Ð*Ð" dÐ*Ñ*Ô*Õ*Ô*Ð"Ñ*Ô*Õ*Ô* dÑ*Ô*Ð*Ð*Ñ*Ð*Ð*Ð*Ð*Ð*Ñ*Ð*Õ*Õ*Ô*Ð*Ñ*Ô*Ñ*Ô*Ð*Ð*Ð*Ð*Ð*Ð*Ð*Ø�?Ô#Ð+ tÐ+Ð# tÐ+Ð+Ð+Ð+Õ+Ô+Ð+Ð+Ð+Ð+Ð# tÐ+Ñ+Ô+Õ+Ô+Ð#Ñ+Ô+Õ+Ô+ tÑ+Ô+Ð+Ð+Ñ+Ð+Ð+Ð+Ð+Ð+Ñ+Ð+Õ+Õ+Ô+Ð+Ñ+Ô+Ñ+Ô+Ð+Ð+Ð+Ð+Ð+Ð+Ð+ØÐ&Ô'Ð/¨4Ð/Ð'¨4Ð/Ð/Ð/Ð/Õ/Ô/Ð/Ð/Ð/Ð/Ð'¨4Ð/Ñ/Ô/Õ/Ô/Ð'Ñ/Ô/Õ/Ô/¨4Ñ/Ô/Ð/Ð/Ñ/Ð/Ð/Ð/Ð/Ð/Ñ/Ð/Õ/Õ/Ô/Ð/Ñ/Ô/Ñ/Ô/Ð/Ð/Ð/Ð/Ð/Ð/Ð/Ø�<Ô  Ô*Ð=¨oÐ=Ð*¨oÒ=Ð=Ð=Ð=Õ=Ô=Ð=Ð=Ð=Ð=Ð*¨oÐ=Ñ=Ô=Õ=Ô=Ð*Ñ=Ô=Õ=Ô=¨oÑ=Ô=Ð=Ð=Ñ=Ð=Ð=Ð=Ð=Ð=Ñ=Ð=Õ=Õ=Ô=Ð=Ñ=Ô=Ñ=Ô=Ð=Ð=Ð=Ð=Ð=Ð=Ð=Ø�<Ô  Ô+¨KÔ8Ð@¸DÐ@Ð8¸DÒ@Ð@Ð@Ð@Õ@Ô@Ð@Ð@Ð@Ð@Ð8¸DÐ@Ñ@Ô@Õ@Ô@Ð8Ñ@Ô@Õ@Ô@¸DÑ@Ô@Ð@Ð@Ñ@Ð@Ð@Ð@Ð@Ð@Ñ@Ð@Õ@Õ@Ô@Ð@Ñ@Ô@Ñ@Ô@Ð@Ð@Ð@Ð@Ð@Ð@Ð@Ð@Ð@r   c                ó4  — d}t          |¦  «        }|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d	         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d
         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}d S )Na   
        const { PartyUI } = await import('./client/webapp/js/ui/party_ui.js');
        const partyUI = new PartyUI({ currentUserId: 'p2' });
        partyUI.init();
        const partyState = {
          party_id: 'party_102', leader_id: 'p1', loot_allocation_mode: 'FREE_FOR_ALL',
          members: [
            { player_id: 'p1', character_name: 'Leader', role: 'LEADER' },
            { player_id: 'p2', character_name: 'MemberMe', role: 'MEMBER' }
          ]
        };
        partyUI.updateParty(partyState);
        console.log(JSON.stringify({
          isLeader: partyUI.isLeader(),
          hasLeaderControls: partyUI.container.innerHTML.includes('party-leader-controls'),
          hasMemberControls: partyUI.container.innerHTML.includes('party-member-controls')
        }));
        r"   Fr#   r%   r&   r)   r*   r.   ÚhasMemberControlsTr9   r?   s           r   Ú/test_party_ui_member_view_lacks_leader_controlszATestClientPartyUI.test_party_ui_member_view_lacks_leader_controlsv   s  € ðˆõ$ ˜vÑ&Ô&ˆØ�:ŒÐ' %Ð'ˆ %Ð'Ð'Ð'Ð'Õ'Ô'Ð'Ð'Ð'Ð'ˆ %Ð'Ñ'Ô'Õ'Ô'ˆÑ'Ô'Õ'Ô' %Ñ'Ô'Ð'Ð'Ñ'Ð'Ð'Ð'Ð'Ð'Ñ'Ð'Õ'Õ'Ô'Ð'Ñ'Ô'Ñ'Ô'Ð'Ð'Ð'Ð'Ð'Ð'Ð'ØÐ&Ô'Ð0¨5Ð0Ð'¨5Ð0Ð0Ð0Ð0Õ0Ô0Ð0Ð0Ð0Ð0Ð'¨5Ð0Ñ0Ô0Õ0Ô0Ð'Ñ0Ô0Õ0Ô0¨5Ñ0Ô0Ð0Ð0Ñ0Ð0Ð0Ð0Ð0Ð0Ñ0Ð0Õ0Õ0Ô0Ð0Ñ0Ô0Ñ0Ô0Ð0Ð0Ð0Ð0Ð0Ð0Ð0ØÐ&Ô'Ð/¨4Ð/Ð'¨4Ð/Ð/Ð/Ð/Õ/Ô/Ð/Ð/Ð/Ð/Ð'¨4Ð/Ñ/Ô/Õ/Ô/Ð'Ñ/Ô/Õ/Ô/¨4Ñ/Ô/Ð/Ð/Ñ/Ð/Ð/Ð/Ð/Ð/Ñ/Ð/Õ/Õ/Ô/Ð/Ñ/Ô/Ñ/Ô/Ð/Ð/Ð/Ð/Ð/Ð/Ð/Ð/Ð/r   N©r   r    )Ú__name__Ú
__module__Ú__qualname__rH   rK   © r   r   r   r   P   sA   € € € € € ð#Að #Að #Að #AðJ0ð 0ð 0ð 0ð 0ð 0r   r   c                  ó   — e Zd Zdd„Zdd„ZdS )ÚTestClientSocialUIr   r    c                ó@  — d}t          |¦  «        }|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d	         }d
}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}d S )Nu™  
        const { SocialUI } = await import('./client/webapp/js/ui/social_ui.js');
        const socialUI = new SocialUI({ currentUserId: 'p1' });
        socialUI.init();
        socialUI.open();
        const friends = [
          { player_id: 'f1', character_name: 'Alice', level: 60, is_online: true, zone_name: 'TÃ ng Kiáº¿m Nhai', note: 'Best Archer' },
          { player_id: 'f2', character_name: 'Bob', level: 30, is_online: false, zone_name: 'Doanh Tráº¡i' }
        ];
        socialUI.updatePresence(friends);
        const friendsHtml = socialUI.container.innerHTML;
        socialUI.switchTab('guild');
        const guildMembers = [
          { player_id: 'g1', character_name: 'GuildMaster', rank: 'LEADER', level: 85, is_online: true, zone_name: 'SÃ o Huyá»‡t' }
        ];
        socialUI.updateGuildRoster(guildMembers);
        const guildHtml = socialUI.container.innerHTML;
        console.log(JSON.stringify({
          isOpen: socialUI.isOpen, activeTab: socialUI.activeTab,
          hasAlice: friendsHtml.includes('Alice'), hasNote: friendsHtml.includes('Best Archer'),
          hasGuildMaster: guildHtml.includes('GuildMaster')
        }));
        ÚisOpenTr#   r%   r&   r)   r*   Ú	activeTabÚguildr2   r4   ÚhasAliceÚhasNoteÚhasGuildMasterr9   r?   s           r   Ú)test_social_ui_tabs_and_presence_trackingz<TestClientSocialUI.test_social_ui_tabs_and_presence_tracking�   s`  € ðˆõ. ˜vÑ&Ô&ˆØ�8Œ}Ð$ Ð$ˆ} Ð$Ð$Ð$Ð$Õ$Ô$Ð$Ð$Ð$Ð$ˆ} Ð$Ñ$Ô$Õ$Ô$ˆ}Ñ$Ô$Õ$Ô$ Ñ$Ô$Ð$Ð$Ñ$Ð$Ð$Ð$Ð$Ð$Ñ$Ð$Õ$Õ$Ô$Ð$Ñ$Ô$Ñ$Ô$Ð$Ð$Ð$Ð$Ð$Ð$Ð$Ø�;ÔÐ* 7Ð*Ð 7Ò*Ð*Ð*Ð*Õ*Ô*Ð*Ð*Ð*Ð*Ð 7Ð*Ñ*Ô*Õ*Ô*ÐÑ*Ô*Õ*Ô* 7Ñ*Ô*Ð*Ð*Ñ*Ð*Ð*Ð*Ð*Ð*Ñ*Ð*Õ*Õ*Ô*Ð*Ñ*Ô*Ñ*Ô*Ð*Ð*Ð*Ð*Ð*Ð*Ð*Ø�:ŒÐ& $Ð&ˆ $Ð&Ð&Ð&Ð&Õ&Ô&Ð&Ð&Ð&Ð&ˆ $Ð&Ñ&Ô&Õ&Ô&ˆÑ&Ô&Õ&Ô& $Ñ&Ô&Ð&Ð&Ñ&Ð&Ð&Ð&Ð&Ð&Ñ&Ð&Õ&Õ&Ô&Ð&Ñ&Ô&Ñ&Ô&Ð&Ð&Ð&Ð&Ð&Ð&Ð&Ø�9Œ~Ð% Ð%ˆ~ Ð%Ð%Ð%Ð%Õ%Ô%Ð%Ð%Ð%Ð%ˆ~ Ð%Ñ%Ô%Õ%Ô%ˆ~Ñ%Ô%Õ%Ô% Ñ%Ô%Ð%Ð%Ñ%Ð%Ð%Ð%Ð%Ð%Ñ%Ð%Õ%Õ%Ô%Ð%Ñ%Ô%Ñ%Ô%Ð%Ð%Ð%Ð%Ð%Ð%Ð%ØÐ#Ô$Ð,¨Ð,Ð$¨Ð,Ð,Ð,Ð,Õ,Ô,Ð,Ð,Ð,Ð,Ð$¨Ð,Ñ,Ô,Õ,Ô,Ð$Ñ,Ô,Õ,Ô,¨Ñ,Ô,Ð,Ð,Ñ,Ð,Ð,Ð,Ð,Ð,Ñ,Ð,Õ,Õ,Ô,Ð,Ñ,Ô,Ñ,Ô,Ð,Ð,Ð,Ð,Ð,Ð,Ð,Ð,Ð,r   c                óX  — d}t          |¦  «        }|d         d         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd	|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         d
         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd	|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd	|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}d S )Na¸  
        const { SocialUI } = await import('./client/webapp/js/ui/social_ui.js');
        const socialUI = new SocialUI({ currentUserId: 'p1' });
        socialUI.init();
        let tpDetail = null;
        window.addEventListener('freeexile:teleportRequest', (e) => { tpDetail = e.detail; });
        socialUI.updatePresence([{ player_id: 'f1', character_name: 'Alice', level: 60, is_online: true }]);
        socialUI.teleportTo('f1', 'FRIEND');
        socialUI.sendFriendRequest('charlie');
        socialUI.saveFriendNote('f1', 'Updated Note');
        const f1Note = socialUI.friends.find(f => f.player_id === 'f1').note;
        console.log(JSON.stringify({ tpDetail, f1Note }));
        ÚtpDetailÚtargetIdÚf1r2   r4   r&   r)   r*   Ú
targetTypeÚFRIENDÚf1NotezUpdated Noter9   r?   s           r   Ú3test_social_ui_friend_actions_and_teleport_dispatchzFTestClientSocialUI.test_social_ui_friend_actions_and_teleport_dispatch¯   s  € ðˆõ ˜vÑ&Ô&ˆØ�:Œ˜zÔ*Ð2¨dÐ2Ð*¨dÒ2Ð2Ð2Ð2Õ2Ô2Ð2Ð2Ð2Ð2Ð*¨dÐ2Ñ2Ô2Õ2Ô2Ð*Ñ2Ô2Õ2Ô2¨dÑ2Ô2Ð2Ð2Ñ2Ð2Ð2Ð2Ð2Ð2Ñ2Ð2Õ2Õ2Ô2Ð2Ñ2Ô2Ñ2Ô2Ð2Ð2Ð2Ð2Ð2Ð2Ð2Ø�:Œ˜|Ô,Ð8°Ð8Ð,°Ò8Ð8Ð8Ð8Õ8Ô8Ð8Ð8Ð8Ð8Ð,°Ð8Ñ8Ô8Õ8Ô8Ð,Ñ8Ô8Õ8Ô8°Ñ8Ô8Ð8Ð8Ñ8Ð8Ð8Ð8Ð8Ð8Ñ8Ð8Õ8Õ8Ô8Ð8Ñ8Ô8Ñ8Ô8Ð8Ð8Ð8Ð8Ð8Ð8Ð8Ø�8Œ}Ð. Ð.ˆ} Ò.Ð.Ð.Ð.Õ.Ô.Ð.Ð.Ð.Ð.ˆ} Ð.Ñ.Ô.Õ.Ô.ˆ}Ñ.Ô.Õ.Ô. Ñ.Ô.Ð.Ð.Ñ.Ð.Ð.Ð.Ð.Ð.Ñ.Ð.Õ.Õ.Ô.Ð.Ñ.Ô.Ñ.Ô.Ð.Ð.Ð.Ð.Ð.Ð.Ð.Ð.Ð.r   NrL   )rM   rN   rO   rZ   rb   rP   r   r   rR   rR   �   s<   € € € € € ð-ð -ð -ð -ð>/ð /ð /ð /ð /ð /r   rR   c                  ó   — e Zd Zdd„Zdd„ZdS )ÚTestClientTeleportHUDr   r    c                ó@  — d}t          |¦  «        }|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d	         }d
}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d
}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}d S )Nag  
        const { TeleportHUD } = await import('./client/webapp/js/ui/teleport_hud.js');
        const hud = new TeleportHUD({ durationMs: 3500, movementThreshold: 0.05 });
        hud.init();
        hud.startChannel({ targetId: 'p2', targetName: 'Buddy' });
        const startActive = hud.isChanneling;
        // Sub-threshold movement (0.04 <= 0.05) should NOT cancel
        const moveIgnored = hud.onPlayerMoved(0.04);
        const stillChanneling = hud.isChanneling;
        // Movement over threshold (0.06 > 0.05) cancels immediately
        const moveCancelled = hud.onPlayerMoved(0.06);
        const cancelledState = hud.isChanneling;
        const msg = hud.statusMessage;
        console.log(JSON.stringify({
          startActive, moveIgnored, stillChanneling, moveCancelled, cancelledState,
          hasInterruptMsg: msg.length > 0
        }));
        ÚstartActiveTr#   r%   r&   r)   r*   ÚmoveIgnoredFÚstillChannelingÚmoveCancelledÚcancelledStateÚhasInterruptMsgr9   r?   s           r   Ú0test_teleport_channel_and_subthreshold_tolerancezFTestClientTeleportHUD.test_teleport_channel_and_subthreshold_toleranceÄ   s  € ðˆõ$ ˜vÑ&Ô&ˆØ�=Ô!Ð) TÐ)Ð! TÐ)Ð)Ð)Ð)Õ)Ô)Ð)Ð)Ð)Ð)Ð! TÐ)Ñ)Ô)Õ)Ô)Ð!Ñ)Ô)Õ)Ô) TÑ)Ô)Ð)Ð)Ñ)Ð)Ð)Ð)Ð)Ð)Ñ)Ð)Õ)Õ)Ô)Ð)Ñ)Ô)Ñ)Ô)Ð)Ð)Ð)Ð)Ð)Ð)Ð)Ø�=Ô!Ð* UÐ*Ð! UÐ*Ð*Ð*Ð*Õ*Ô*Ð*Ð*Ð*Ð*Ð! UÐ*Ñ*Ô*Õ*Ô*Ð!Ñ*Ô*Õ*Ô* UÑ*Ô*Ð*Ð*Ñ*Ð*Ð*Ð*Ð*Ð*Ñ*Ð*Õ*Õ*Ô*Ð*Ñ*Ô*Ñ*Ô*Ð*Ð*Ð*Ð*Ð*Ð*Ð*ØÐ$Ô%Ð-¨Ð-Ð%¨Ð-Ð-Ð-Ð-Õ-Ô-Ð-Ð-Ð-Ð-Ð%¨Ð-Ñ-Ô-Õ-Ô-Ð%Ñ-Ô-Õ-Ô-¨Ñ-Ô-Ð-Ð-Ñ-Ð-Ð-Ð-Ð-Ð-Ñ-Ð-Õ-Õ-Ô-Ð-Ñ-Ô-Ñ-Ô-Ð-Ð-Ð-Ð-Ð-Ð-Ð-Ø�?Ô#Ð+ tÐ+Ð# tÐ+Ð+Ð+Ð+Õ+Ô+Ð+Ð+Ð+Ð+Ð# tÐ+Ñ+Ô+Õ+Ô+Ð#Ñ+Ô+Õ+Ô+ tÑ+Ô+Ð+Ð+Ñ+Ð+Ð+Ð+Ð+Ð+Ñ+Ð+Õ+Õ+Ô+Ð+Ñ+Ô+Ñ+Ô+Ð+Ð+Ð+Ð+Ð+Ð+Ð+ØÐ#Ô$Ð-¨Ð-Ð$¨Ð-Ð-Ð-Ð-Õ-Ô-Ð-Ð-Ð-Ð-Ð$¨Ð-Ñ-Ô-Õ-Ô-Ð$Ñ-Ô-Õ-Ô-¨Ñ-Ô-Ð-Ð-Ñ-Ð-Ð-Ð-Ð-Ð-Ñ-Ð-Õ-Õ-Ô-Ð-Ñ-Ô-Ñ-Ô-Ð-Ð-Ð-Ð-Ð-Ð-Ð-ØÐ$Ô%Ð-¨Ð-Ð%¨Ð-Ð-Ð-Ð-Õ-Ô-Ð-Ð-Ð-Ð-Ð%¨Ð-Ñ-Ô-Õ-Ô-Ð%Ñ-Ô-Õ-Ô-¨Ñ-Ô-Ð-Ð-Ñ-Ð-Ð-Ð-Ð-Ð-Ñ-Ð-Õ-Õ-Ô-Ð-Ñ-Ô-Ñ-Ô-Ð-Ð-Ð-Ð-Ð-Ð-Ð-Ð-Ð-r   c                ó0  — d}t          |¦  «        }|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d	         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}d S )
Naö  
        const { TeleportHUD } = await import('./client/webapp/js/ui/teleport_hud.js');
        const hud = new TeleportHUD({ durationMs: 3500 });
        hud.init();
        hud.startChannel({ targetName: 'Hideout' });
        hud.onPlayerDamaged(25);
        const damagedState = hud.isChanneling;
        hud.startChannel({ targetName: 'Friend' });
        hud.cancelManual();
        const manualState = hud.isChanneling;
        console.log(JSON.stringify({ damagedState, manualState }));
        ÚdamagedStateFr#   r%   r&   r)   r*   ÚmanualStater9   r?   s           r   Ú&test_teleport_damage_and_manual_cancelz<TestClientTeleportHUD.test_teleport_damage_and_manual_cancelß   sp  € ðˆõ ˜vÑ&Ô&ˆØ�>Ô"Ð+ eÐ+Ð" eÐ+Ð+Ð+Ð+Õ+Ô+Ð+Ð+Ð+Ð+Ð" eÐ+Ñ+Ô+Õ+Ô+Ð"Ñ+Ô+Õ+Ô+ eÑ+Ô+Ð+Ð+Ñ+Ð+Ð+Ð+Ð+Ð+Ñ+Ð+Õ+Õ+Ô+Ð+Ñ+Ô+Ñ+Ô+Ð+Ð+Ð+Ð+Ð+Ð+Ð+Ø�=Ô!Ð* UÐ*Ð! UÐ*Ð*Ð*Ð*Õ*Ô*Ð*Ð*Ð*Ð*Ð! UÐ*Ñ*Ô*Õ*Ô*Ð!Ñ*Ô*Õ*Ô* UÑ*Ô*Ð*Ð*Ñ*Ð*Ð*Ð*Ð*Ð*Ñ*Ð*Õ*Õ*Ô*Ð*Ñ*Ô*Ñ*Ô*Ð*Ð*Ð*Ð*Ð*Ð*Ð*Ð*Ð*r   NrL   )rM   rN   rO   rl   rp   rP   r   r   rd   rd   Ã   s<   € € € € € ð.ð .ð .ð .ð6+ð +ð +ð +ð +ð +r   rd   c                  ó   — e Zd Zdd„Zdd„ZdS )ÚTestClientTownPortalControllerr   r    c                óT  — d}t          |¦  «        }|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d	         }d
}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d
}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}d S )Na}  
        const { TownPortalController } = await import('./client/webapp/js/engine/town_portal_controller.js');
        const controller = new TownPortalController();
        controller.init();
        const createRes = controller.requestTownPortal('hero', 'zone_tang_kiem_nhai', 'inst_1', 120.5, 340.5);
        const pId = createRes.portalId;
        const portal = controller.portals.get(pId);
        const trav1 = controller.traversePortal(pId, { id: 'hero', zoneId: 'zone_tang_kiem_nhai' });
        const trav2 = controller.traversePortal(pId, { id: 'hero', zoneId: 'zone_player_hideout' });
        console.log(JSON.stringify({
          success: createRes.success, originZone: portal.originZone, originX: portal.originX,
          hubZone: portal.hubZone, trav1TargetZone: trav1.targetZone,
          trav2TargetZone: trav2.targetZone, trav2TargetX: trav2.targetX
        }));
        ÚsuccessTr#   r%   r&   r)   r*   Ú
originZoneÚzone_tang_kiem_nhair2   r4   ÚoriginXg      ^@Útrav1TargetZoneÚzone_player_hideoutÚtrav2TargetZoneÚtrav2TargetXr9   r?   s           r   Ú'test_portal_2way_creation_and_traversalzFTestClientTownPortalController.test_portal_2way_creation_and_traversalò   s  € ðˆõ ˜vÑ&Ô&ˆØ�9Œ~Ð% Ð%ˆ~ Ð%Ð%Ð%Ð%Õ%Ô%Ð%Ð%Ð%Ð%ˆ~ Ð%Ñ%Ô%Õ%Ô%ˆ~Ñ%Ô%Õ%Ô% Ñ%Ô%Ð%Ð%Ñ%Ð%Ð%Ð%Ð%Ð%Ñ%Ð%Õ%Õ%Ô%Ð%Ñ%Ô%Ñ%Ô%Ð%Ð%Ð%Ð%Ð%Ð%Ð%Ø�<Ô Ð9Ð$9Ð9Ð Ð$9Ò9Ð9Ð9Ð9Õ9Ô9Ð9Ð9Ð9Ð9Ð Ð$9Ð9Ñ9Ô9Õ9Ô9Ð Ñ9Ô9Õ9Ô9Ð$9Ñ9Ô9Ð9Ð9Ñ9Ð9Ð9Ð9Ð9Ð9Ñ9Ð9Õ9Õ9Ô9Ð9Ñ9Ô9Ñ9Ô9Ð9Ð9Ð9Ð9Ð9Ð9Ð9Ø�9Œ~Ð& Ð&ˆ~ Ò&Ð&Ð&Ð&Õ&Ô&Ð&Ð&Ð&Ð&ˆ~ Ð&Ñ&Ô&Õ&Ô&ˆ~Ñ&Ô&Õ&Ô& Ñ&Ô&Ð&Ð&Ñ&Ð&Ð&Ð&Ð&Ð&Ñ&Ð&Õ&Õ&Ô&Ð&Ñ&Ô&Ñ&Ô&Ð&Ð&Ð&Ð&Ð&Ð&Ð&ØÐ$Ô%Ð>Ð)>Ð>Ð%Ð)>Ò>Ð>Ð>Ð>Õ>Ô>Ð>Ð>Ð>Ð>Ð%Ð)>Ð>Ñ>Ô>Õ>Ô>Ð%Ñ>Ô>Õ>Ô>Ð)>Ñ>Ô>Ð>Ð>Ñ>Ð>Ð>Ð>Ð>Ð>Ñ>Ð>Õ>Õ>Ô>Ð>Ñ>Ô>Ñ>Ô>Ð>Ð>Ð>Ð>Ð>Ð>Ð>ØÐ$Ô%Ð>Ð)>Ð>Ð%Ð)>Ò>Ð>Ð>Ð>Õ>Ô>Ð>Ð>Ð>Ð>Ð%Ð)>Ð>Ñ>Ô>Õ>Ô>Ð%Ñ>Ô>Õ>Ô>Ð)>Ñ>Ô>Ð>Ð>Ñ>Ð>Ð>Ð>Ð>Ð>Ñ>Ð>Õ>Õ>Ô>Ð>Ñ>Ô>Ñ>Ô>Ð>Ð>Ð>Ð>Ð>Ð>Ð>Ø�>Ô"Ð+ eÐ+Ð" eÒ+Ð+Ð+Ð+Õ+Ô+Ð+Ð+Ð+Ð+Ð" eÐ+Ñ+Ô+Õ+Ô+Ð"Ñ+Ô+Õ+Ô+ eÑ+Ô+Ð+Ð+Ñ+Ð+Ð+Ð+Ð+Ð+Ñ+Ð+Õ+Õ+Ô+Ð+Ñ+Ô+Ñ+Ô+Ð+Ð+Ð+Ð+Ð+Ð+Ð+Ð+Ð+r   c                óL  — d}t          |¦  «        }|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d	         }d
}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}|d         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d x}x}}d S )Naµ  
        const { TownPortalController } = await import('./client/webapp/js/engine/town_portal_controller.js');
        const controller = new TownPortalController();
        controller.init();
        controller.setQuarantineState('zone_boss', 'inst_b1', { bossActive: true });
        const bossRes = controller.requestTownPortal('hero', 'zone_boss', 'inst_b1');
        controller.setQuarantineState('zone_puzzle', 'inst_s1', { secretActive: true });
        const secretRes = controller.requestTownPortal('hero', 'zone_puzzle', 'inst_s1');
        controller.registerMapDevice('map_empty', 0);
        const mapRes = controller.requestTownPortal('hero', 'zone_wild', 'map_empty');
        console.log(JSON.stringify({
          bossAllowed: bossRes.success, bossReason: bossRes.reason,
          secretAllowed: secretRes.success, secretReason: secretRes.reason,
          mapAllowed: mapRes.success, mapReason: mapRes.reason
        }));
        ÚbossAllowedFr#   r%   r&   r)   r*   Ú
bossReasonÚQUARANTINE_BOSS_ENGAGEDr2   r4   ÚsecretAllowedÚsecretReasonÚQUARANTINE_SECRET_CHAMBERÚ
mapAllowedÚ	mapReasonÚ
MAP_SEALEDr9   r?   s           r   Ú,test_portal_anti_exploit_quarantine_lockoutszKTestClientTownPortalController.test_portal_anti_exploit_quarantine_lockouts
  s  € ðˆõ  ˜vÑ&Ô&ˆØ�=Ô!Ð* UÐ*Ð! UÐ*Ð*Ð*Ð*Õ*Ô*Ð*Ð*Ð*Ð*Ð! UÐ*Ñ*Ô*Õ*Ô*Ð!Ñ*Ô*Õ*Ô* UÑ*Ô*Ð*Ð*Ñ*Ð*Ð*Ð*Ð*Ð*Ñ*Ð*Õ*Õ*Ô*Ð*Ñ*Ô*Ñ*Ô*Ð*Ð*Ð*Ð*Ð*Ð*Ð*Ø�<Ô Ð=Ð$=Ð=Ð Ð$=Ò=Ð=Ð=Ð=Õ=Ô=Ð=Ð=Ð=Ð=Ð Ð$=Ð=Ñ=Ô=Õ=Ô=Ð Ñ=Ô=Õ=Ô=Ð$=Ñ=Ô=Ð=Ð=Ñ=Ð=Ð=Ð=Ð=Ð=Ñ=Ð=Õ=Õ=Ô=Ð=Ñ=Ô=Ñ=Ô=Ð=Ð=Ð=Ð=Ð=Ð=Ð=Ø�?Ô#Ð, uÐ,Ð# uÐ,Ð,Ð,Ð,Õ,Ô,Ð,Ð,Ð,Ð,Ð# uÐ,Ñ,Ô,Õ,Ô,Ð#Ñ,Ô,Õ,Ô, uÑ,Ô,Ð,Ð,Ñ,Ð,Ð,Ð,Ð,Ð,Ñ,Ð,Õ,Õ,Ô,Ð,Ñ,Ô,Ñ,Ô,Ð,Ð,Ð,Ð,Ð,Ð,Ð,Ø�>Ô"ÐAÐ&AÐAÐ"Ð&AÒAÐAÐAÐAÕAÔAÐAÐAÐAÐAÐ"Ð&AÐAÑAÔAÕAÔAÐ"ÑAÔAÕAÔAÐ&AÑAÔAÐAÐAÑAÐAÐAÐAÐAÐAÑAÐAÕAÕAÔAÐAÑAÔAÑAÔAÐAÐAÐAÐAÐAÐAÐAØ�<Ô Ð) EÐ)Ð  EÐ)Ð)Ð)Ð)Õ)Ô)Ð)Ð)Ð)Ð)Ð  EÐ)Ñ)Ô)Õ)Ô)Ð Ñ)Ô)Õ)Ô) EÑ)Ô)Ð)Ð)Ñ)Ð)Ð)Ð)Ð)Ð)Ñ)Ð)Õ)Õ)Ô)Ð)Ñ)Ô)Ñ)Ô)Ð)Ð)Ð)Ð)Ð)Ð)Ð)Ø�;ÔÐ/ <Ð/Ð <Ò/Ð/Ð/Ð/Õ/Ô/Ð/Ð/Ð/Ð/Ð <Ð/Ñ/Ô/Õ/Ô/ÐÑ/Ô/Õ/Ô/ <Ñ/Ô/Ð/Ð/Ñ/Ð/Ð/Ð/Ð/Ð/Ñ/Ð/Õ/Õ/Ô/Ð/Ñ/Ô/Ñ/Ô/Ð/Ð/Ð/Ð/Ð/Ð/Ð/Ð/Ð/r   NrL   )rM   rN   rO   r|   r‡   rP   r   r   rr   rr   ñ   s<   € € € € € ð,ð ,ð ,ð ,ð00ð 0ð 0ð 0ð 0ð 0r   rr   c                  ó   — e Zd Zdd„ZdS )ÚTestClientI18nCatalogParityr   r    c                ó’  — d}t          |¦  «        }|d         }d}||k    }|s—t          j        d|fd||f¦  «        dt          j        ¦   «         v st          j        |¦  «        rt          j        |¦  «        ndt          j        |¦  «        dœz  }dd	|iz  }t          t          j        |¦  «        ¦  «        ‚d x}}|d
          	                    ¦   «         D �]ì\  }}	|	d         }
|
|k    }|s°t          j        d|fd|
|f¦  «        t          j        |
¦  «        dt          j        ¦   «         v st          j        |¦  «        rt          j        |¦  «        nddœz  }t          j
        d|› d�¦  «        dz   d	|iz  }t          t          j        |¦  «        ¦  «        ‚d x}
}|	d         }t          |¦  «        }d}||k    }|sèt          j        d|fd||f¦  «        dt          j        ¦   «         v st          j        t          ¦  «        rt          j        t          ¦  «        ndt          j        |¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }t          j
        d|› d|	d         › �¦  «        dz   d|iz  }t          t          j        |¦  «        ¦  «        ‚d x}x}x}}|	d         }t          |¦  «        }d}||k    }|sèt          j        d|fd||f¦  «        dt          j        ¦   «         v st          j        t          ¦  «        rt          j        t          ¦  «        ndt          j        |¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }t          j
        d|› d|	d         › �¦  «        dz   d|iz  }t          t          j        |¦  «        ¦  «        ‚d x}x}x}}�Œîd S )Na½  
        const { PARTY_SOCIAL_I18N_CATALOG } = await import('./client/webapp/js/data/i18n.js');
        const locales = ['vi', 'en', 'zh', 'ja', 'ko', 'th', 'de', 'ru', 'es'];
        const viKeys = Object.keys(PARTY_SOCIAL_I18N_CATALOG.vi || {}).sort();
        const parity = {};
        for (const loc of locales) {
          const locKeys = Object.keys(PARTY_SOCIAL_I18N_CATALOG[loc] || {}).sort();
          const missing = viKeys.filter(k => !locKeys.includes(k));
          const extra = locKeys.filter(k => !viKeys.includes(k));
          parity[loc] = { keyCount: locKeys.length, missing, extra };
        }
        console.log(JSON.stringify({ viKeyCount: viKeys.length, parity }));
        Ú
viKeyCounté(   )ú>=)z%(py0)s >= %(py3)sÚ
total_keys)Úpy0Úpy3zassert %(py5)sÚpy5ÚparityÚkeyCountr2   )z%(py1)s == %(py3)s)r'   r�   zLocale z key count mismatchz
>assert %(py5)sÚmissingr   )z0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py7)sÚlen)r�   Úpy2r(   Úpy7z missing keys: z
>assert %(py9)sÚpy9Úextraz extra keys: )r   r:   r;   Ú@py_builtinsÚlocalsÚ_should_repr_global_namer<   r=   r>   ÚitemsÚ_format_assertmsgr•   )r@   rA   rB   rŽ   rE   Ú@py_assert1Ú@py_format4Ú@py_format6ÚlocÚinforC   rD   Ú@py_assert6Ú@py_assert5Ú@py_format8Ú@py_format10s                   r   Ú&test_9_language_parity_across_all_keyszBTestClientI18nCatalogParity.test_9_language_parity_across_all_keys%  sR  € ðˆõ ˜vÑ&Ô&ˆØ˜Ô&ˆ
ØÐˆz˜RÒÐÐÐÕÔÐÐÐÐˆz˜RÐÑÔÐÕÔÑÔÐÐÕÔˆzÑÔÐÕÔˆzÑÔÐÐÕÔ˜RÑÔÐÐÑÐÐÐÐÐÑÐÕÕÔÐÑÔÑÔÐÐÐÐÐØ˜Xœ×,Ò,Ñ.Ô.ð 	Xñ 	X‰IˆC�Ø˜
Ô#ÐUÐ# zÒ1ÐUÐUÐUÕUÔUÐUÐUÐUÐUÐ# zÐUÑUÔUÕUÔUÐ#ÑUÔUÐUÕUÔUÑUÔUÐUÐUÕUÔU zÑUÔUÐUÕUÔU zÑUÔUÐUÐUÐUÐUÑUÐUÕUÔUÐ3U¸SÐ3UÐ3UÐ3UÑUÔUÐUÑUÐUÐUÐUÑUÐUÕUÕUÔUÐUÑUÔUÑUÔUÐUÐUÐUÐUÐUØ˜I”Ð]•3�Ñ'Ô'Ð]¨1Ð]Ð'¨1Ò,Ð]Ð]Ð]Õ]Ô]Ð]Ð]Ð]Ð]Ð'¨1Ð]Ñ]Ô]Ð]Õ]Ô]Ñ]Ô]Ð]Ð]Õ]Ô]•3Ñ]Ô]Ð]Õ]Ô]•3Ñ]Ô]Ð]Ð]Õ]Ô]�Ñ]Ô]Õ]Ô]Ð'Ñ]Ô]Õ]Ô]¨1Ñ]Ô]Ð]Ð]Ñ]Ð]Õ]Ô]Ð.]¸Ð.]Ð.]ÈDÐQZÌOÐ.]Ð.]Ñ]Ô]Ð]Ñ]Ð]Ð]Ð]Ñ]Ð]Õ]Õ]Ô]Ð]Ñ]Ô]Ñ]Ô]Ð]Ð]Ð]Ð]Ð]Ð]Ð]Ð]Ð]Ø˜G”}ÐW•3�}Ñ%Ô%ÐW¨ÐWÐ%¨Ò*ÐWÐWÐWÕWÔWÐWÐWÐWÐWÐ%¨ÐWÑWÔWÐWÕWÔWÑWÔWÐWÐWÕWÔW•3ÑWÔWÐWÕWÔW•3ÑWÔWÐWÐWÕWÔW�}ÑWÔWÕWÔWÐ%ÑWÔWÕWÔW¨ÑWÔWÐWÐWÑWÐWÕWÔWÐ,W°cÐ,WÐ,WÈÈWÌÐ,WÐ,WÑWÔWÐWÑWÐWÐWÐWÑWÐWÕWÕWÔWÐWÑWÔWÑWÔWÐWÐWÐWÐWÐWÐWÐWÐWÐWÑWð	Xð 	Xr   NrL   )rM   rN   rO   r¨   rP   r   r   r‰   r‰   $  s.   € € € € € ðXð Xð Xð Xð Xð Xr   r‰   )r   r   r   r   )Ú__doc__Ú
__future__r   Úbuiltinsrš   Ú_pytest.assertion.rewriteÚ	assertionÚrewriter:   r   r   Úpathlibr   ÚpytestÚ__file__ÚresolveÚparentr   r   r   rR   rd   rr   r‰   rP   r   r   ú<module>r´      s„  ððð ð #Ð "Ð "Ð "Ð "Ð "Ø  € € € € € € € € € € € € €€€€Ø Ð Ð Ð Ø Ð Ð Ð Ð Ð Ø €€€àˆt�H‰~Œ~×%Ò%Ñ'Ô'Ô.Ô5Ô<€ð=*ð =*ð =*ð =*ð@<0ð <0ð <0ð <0ð <0ñ <0ô <0ð <0ð~1/ð 1/ð 1/ð 1/ð 1/ñ 1/ô 1/ð 1/ðh++ð ++ð ++ð ++ð ++ñ ++ô ++ð ++ð\00ð 00ð 00ð 00ð 00ñ 00ô 00ð 00ðfXð Xð Xð Xð Xñ Xô Xð Xð Xð Xr   