§
    V¿j’F  ã                  óæ  — 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ZddlZddlmZ ddlmZmZ  ee¦  «                             ¦   «         j        j        j        Zedz  dz  Zedz  d	z  d
z  Zedz  d	z  dz  Zedz  dz  dz  Zdd d„Z G d„ dej        ¦  «        Z G d„ dej        ¦  «        Z G d„ dej        ¦  «        Z G d„ dej        ¦  «        Ze dk    r ej!        ¦   «          dS dS )!a.  
Empirical Challenger Test Suite for Milestone M1:
Adversarial Verification of Catalog Dictionary Parity & Parameter Interpolation.

Covers:
1. 100% key parity & parameter placeholder parity across all 9 languages in chat_i18n_catalog.js.
2. Microcopy length bounds & HUD ergonomics across channels, badges, buttons, rarities, elements.
3. Item link tooltip localization for rarities, elements, HMAC badges, and dynamic in-place re-translation.
4. Exception resilience: FreeExileI18n.t() missing/null keys, adversarial params, subscriber errors, SSR safety.
é    )ÚannotationsN)ÚPath)ÚAnyÚDictÚclientÚwebappÚjsÚdatazchat_i18n_catalog.jszi18n.jsÚuiz
chat_ui.jsç      .@ÚcodeÚstrÚtimeout_secÚfloatÚreturnr   c           	     óš  — d| › d�}t          j        ddd|gdddt          t          ¦  «        |¬¦  «        }|j        d	k    rt          d
|j        › d|j        › �¦  «        ‚t          |j         	                    ¦   «          
                    d¦  «        ¦  «        D ]°}| 	                    ¦   «         }|                     d¦  «        r|                     d¦  «        s*|                     d¦  «        r+|                     d¦  «        rt          j        |¦  «        c S |                     ¦   «         dv r|                     ¦   «         dk    c S Œ±dS )zIHelper to execute ES module snippets in Node.js and return parsed output.z(async () => { try { zN process.exit(0); } catch (err) { console.error(err); process.exit(1); } })();Únodez--input-type=modulez-eTzutf-8)Úcapture_outputÚtextÚencodingÚcwdÚtimeoutr   zNode execution failed (code z):
ú
Ú{Ú}ú[ú])ÚtrueÚfalser   N)Ú
subprocessÚrunr   ÚPROJECT_ROOTÚ
returncodeÚRuntimeErrorÚstderrÚreversedÚstdoutÚstripÚsplitÚ
startswithÚendswithÚjsonÚloadsÚlower)r   r   ÚwrappedÚresÚlines        úBC:\Projects\FreeExile\tests\unit\test_challenger_i18n_m1_parity.pyÚrun_noder3      se  € ð A¨ð  Að  Að  A€GÝ
Œ.Ø	Ð&¨¨gÐ6Ø $°½cÅ,Ñ>OÔ>OÐYdðñ ô €Cð „~˜ÒÐÝÐZ¸#¼.ÐZÐZÈcÌjÐZÐZÑ[Ô[Ð[Ý˜œ×)Ò)Ñ+Ô+×1Ò1°$Ñ7Ô7Ñ8Ô8ð *ð *ˆØ�zŠz‰|Œ|ˆØ�OŠO˜CÑ Ô ð 	$ T§]¢]°3Ñ%7Ô%7ð 	$¸T¿_º_ÈSÑ=QÔ=Qð 	$ÐVZ×VcÒVcÐdgÑVhÔVhð 	$Ý”:˜dÑ#Ô#Ð#Ð#Ð#Ø�:Š:‰<Œ<Ð,Ð,Ð,Ø—:’:‘<”< 6Ò)Ð)Ð)Ð)ð -àˆ4ó    c                  óR   — e Zd ZdZedd„¦   «         Zdd„Zdd„Zdd„Zdd„Z	dd	„Z
d
S )Ú(TestChatCatalogKeyParityAndInterpolationzbVerifies 100% dictionary parity, non-empty values, and parameter interpolation across 9 languages.r   ÚNonec                óÈ   — t                                ¦   «                              ¦   «         }t          d|› d�¦  «        }|d         | _        |d         | _        g d¢| _        d S )Núconst m = await import('zc'); console.log(JSON.stringify({ chatCatalog: m.CHAT_I18N_CATALOG, channelI18n: m.CHANNEL_I18N }));ÚchatCatalogÚchannelI18n©	ÚviÚenÚzhÚjaÚkoÚthÚdeÚruÚes)ÚCHAT_CATALOG_JSÚresolveÚas_urir3   Úchat_catalogÚchannel_i18nÚlocales)ÚclsÚchat_urlr
   s      r2   Ú
setUpClassz3TestChatCatalogKeyParityAndInterpolation.setUpClass1   su   € å"×*Ò*Ñ,Ô,×3Ò3Ñ5Ô5ˆÝð  c°8ð  cð  cð  cñ  dô  dˆØ6:¸=Ô6IˆÔØ6:¸=Ô6IˆÔØLÐLÐLˆŒˆˆr4   c                óš   — | j         D ]B}|                      || j        d|› d�¦  «         |                      || j        d|› d�¦  «         ŒCdS )zNBoth CHAT_I18N_CATALOG and CHANNEL_I18N must define all 9 canonical languages.úLocale 'z ' missing from CHAT_I18N_CATALOGz' missing from CHANNEL_I18NN)rK   ÚassertInrI   rJ   )ÚselfÚlocs     r2   Ú.test_01_all_nine_languages_present_in_catalogszWTestChatCatalogKeyParityAndInterpolation.test_01_all_nine_languages_present_in_catalogs9   sn   € à”<ð 	_ð 	_ˆCØ�MŠM˜#˜tÔ0Ð2b¸SÐ2bÐ2bÐ2bÑcÔcÐcØ�MŠM˜#˜tÔ0Ð2]¸SÐ2]Ð2]Ð2]Ñ^Ô^Ð^Ð^ð	_ð 	_r4   c           
     óî  — t          | j        d                              ¦   «         ¦  «        }|                      t	          |¦  «        ddt	          |¦  «        › �¦  «         | j        D ]Œ}t          | j        |                              ¦   «         ¦  «        }|                      ||z
  t          ¦   «         d|› d||z
  › �¦  «         |                      ||z
  t          ¦   «         d|› d||z
  › �¦  «         Œ�dS )z^Every locale must contain exactly the same set of 77 keys as the master Vietnamese dictionary.r=   éM   z%Master catalog expected 77 keys, got rP   z' is missing keys: z' has extra keys: N)ÚsetrI   ÚkeysÚassertEqualÚlenrK   )rR   Úmaster_keysrS   Úloc_keyss       r2   Ú-test_02_strict_one_hundred_percent_key_parityzVTestChatCatalogKeyParityAndInterpolation.test_02_strict_one_hundred_percent_key_parity?   s  € å˜$Ô+¨DÔ1×6Ò6Ñ8Ô8Ñ9Ô9ˆØ×Ò�˜[Ñ)Ô)¨2Ð/iÕWZÐ[fÑWgÔWgÐ/iÐ/iÑjÔjÐjØ”<ð 	xð 	xˆCÝ˜4Ô,¨SÔ1×6Ò6Ñ8Ô8Ñ9Ô9ˆHØ×Ò˜[¨8Ñ3µS±U´UÐ<wÀsÐ<wÐ<wÐ_jÐmuÑ_uÐ<wÐ<wÑxÔxÐxØ×Ò˜X¨Ñ3µS±U´UÐ<vÀsÐ<vÐ<vÐ^fÐitÑ^tÐ<vÐ<vÑwÔwÐwÐwð	xð 	xr4   c                ó*  — | j         D ]Š}| j        |                              ¦   «         D ]h\  }}|                      |t          d|› d|› d�¦  «         |                      t          |                     ¦   «         ¦  «        dk    d|› d|› d�¦  «         ŒiŒ‹dS )zJEvery translation value across all 9 languages must be a non-empty string.zKey 'ú' in [z] must be a stringr   z] cannot be emptyN)rK   rI   ÚitemsÚassertIsInstancer   Ú
assertTruerZ   r(   )rR   rS   ÚkeyÚvals       r2   Ú*test_03_non_empty_trimmed_strings_all_keyszSTestChatCatalogKeyParityAndInterpolation.test_03_non_empty_trimmed_strings_all_keysH   s¼   € à”<ð 	að 	aˆCØ Ô-¨cÔ2×8Ò8Ñ:Ô:ð að a‘��SØ×%Ò% c­3Ð0Z¸Ð0ZÐ0ZÀ3Ð0ZÐ0ZÐ0ZÑ[Ô[Ð[Ø—’¥ C§I¢I¡K¤KÑ 0Ô 0°1Ò 4Ð6_¸cÐ6_Ð6_ÈÐ6_Ð6_Ð6_Ñ`Ô`Ð`Ð`ðað	að 	ar4   c                óx  — t          j        d¦  «        }| j        d                              ¦   «         D ]…\  }}t	          |                     |¦  «        ¦  «        }|sŒ*| j        D ]S}t	          |                     | j        |         |         ¦  «        ¦  «        }|                      ||d|› d|› d�¦  «         ŒTŒ†dS )zhInterpolation placeholders like {sec}, {channel}, {level} must match identically across all 9 languages.z\{[a-zA-Z0-9_]+\}r=   zParam mismatch for 'r_   r   N)ÚreÚcompilerI   r`   ÚsortedÚfindallrK   rY   )rR   Úplaceholder_regexrc   Úvi_valÚvi_placeholdersrS   Úloc_placeholderss          r2   Ú*test_04_parameter_placeholder_exact_parityzSTestChatCatalogKeyParityAndInterpolation.test_04_parameter_placeholder_exact_parityO   sæ   € åœJÐ';Ñ<Ô<ÐØÔ,¨TÔ2×8Ò8Ñ:Ô:ð 	nð 	n‰KˆC�Ý$Ð%6×%>Ò%>¸vÑ%FÔ%FÑGÔGˆOØ"ð ØØ”|ð nð n�Ý#)Ð*;×*CÒ*CÀDÔDUÐVYÔDZÐ[^ÔD_Ñ*`Ô*`Ñ#aÔ#aÐ Ø× Ò  Ð2BÐDlÐ[^ÐDlÐDlÐfiÐDlÐDlÐDlÑmÔmÐmÐmðnð		nð 	nr4   c                ó¬   — h d£}| j         D ]G}|                      t          | j        |                              ¦   «         ¦  «        |d|› d�¦  «         ŒHdS )zhCHANNEL_I18N must define all 8 channels (world, zone, guild, party, whisper, system, recruit, feedback).>   ÚzoneÚguildÚpartyÚworldÚsystemÚrecruitÚwhisperÚfeedbackzCHANNEL_I18N[z
] mismatchN)rK   rY   rW   rJ   rX   )rR   ÚexpectedrS   s      r2   Ú*test_05_channel_i18n_eight_channels_parityzSTestChatCatalogKeyParityAndInterpolation.test_05_channel_i18n_eight_channels_parityZ   sp   € àbÐbÐbˆØ”<ð 	lð 	lˆCØ×Ò�S Ô!2°3Ô!7×!<Ò!<Ñ!>Ô!>Ñ?Ô?ÀÐKjÐ[^ÐKjÐKjÐKjÑkÔkÐkÐkð	lð 	lr4   N©r   r7   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__ÚclassmethodrN   rT   r]   re   ro   rz   © r4   r2   r6   r6   .   s¶   € € € € € ØlÐlàðMð Mð Mñ „[ðMð_ð _ð _ð _ðxð xð xð xðað að að að	nð 	nð 	nð 	nðlð lð lð lð lð lr4   r6   c                  óJ   — e Zd ZdZed
d„¦   «         Zd
d„Zd
d„Zd
d„Zd
d„Z	d	S )Ú)TestMicrocopyLengthLimitsAndHUDErgonomicsz^Enforces strict length bounds on compact UI labels to prevent HUD overflow on mobile displays.r   r7   c                óž   — t                                ¦   «                              ¦   «         }t          d|› d�¦  «        | _        g d¢| _        d S )Nr9   z5'); console.log(JSON.stringify(m.CHAT_I18N_CATALOG));r<   )rF   rG   rH   r3   rI   rK   )rL   rM   s     r2   rN   z4TestMicrocopyLengthLimitsAndHUDErgonomics.setUpClassd   sc   € å"×*Ò*Ñ,Ô,×3Ò3Ñ5Ô5ˆÝ6>ð  @ZÐZbð  @Zð  @Zð  @Zñ  7[ô  7[ˆÔØLÐLÐLˆŒˆˆr4   c                ó´   — d„ dD ¦   «         }| j         D ]C}|D ]>}|                      t          | j        |         |         ¦  «        dd|› d|› d�¦  «         Œ?ŒDdS )zbChannel tab labels must not exceed 15 characters to avoid horizontal overflow on mobile viewports.c                ó   — g | ]}d |› �‘ŒS )Úchannel_r�   )Ú.0Úcs     r2   ú
<listcomp>zlTestMicrocopyLengthLimitsAndHUDErgonomics.test_06_channel_tab_lengths_under_mobile_limit.<locals>.<listcomp>l   s0   € ð  Að  Að  A¨1˜ 1˜˜ð  Að  Að  Ar4   )rt   rq   rr   rs   rw   ru   rv   rx   é   zTab 'r_   ú] exceeds 15 charsN)rK   ÚassertLessEqualrZ   rI   )rR   Úchannel_keysrS   Úks       r2   Ú.test_06_channel_tab_lengths_under_mobile_limitzXTestMicrocopyLengthLimitsAndHUDErgonomics.test_06_channel_tab_lengths_under_mobile_limitj   s¡   € ð Að  AÐ0ð  Añ  Aô  AˆØ”<ð 	sð 	sˆCØ!ð sð s�Ø×$Ò$¥S¨Ô):¸3Ô)?ÀÔ)BÑ%CÔ%CÀRÐIqÐQRÐIqÐIqÐZ]ÐIqÐIqÐIqÑrÔrÐrÐrðsð	sð 	sr4   c                ó¤  — | j         D ]Ç}| j        |         }|                      t          |d         ¦  «        dd|› d�¦  «         |                      t          |d         ¦  «        dd|› d�¦  «         |                      t          |d         ¦  «        d	d
|› d�¦  «         |                      t          |d         ¦  «        d	d|› d�¦  «         ŒÈdS )zbBadges and buttons must remain concise (locked <= 15 chars, send <= 12 chars, badges <= 25 chars).Úchat_badge_lockedr‹   zLock badge in [z
] too longÚchat_btn_sendé   zSend button in [Úchat_item_badge_verifiedé   zVerified badge in [Úchat_item_badge_unverifiedzUnverified badge in [N)rK   rI   r�   rZ   )rR   rS   Úds      r2   Ú+test_07_badges_and_buttons_microcopy_boundszUTestMicrocopyLengthLimitsAndHUDErgonomics.test_07_badges_and_buttons_microcopy_boundsq   sü   € à”<ð 	tð 	tˆCØÔ! #Ô&ˆAØ× Ò ¥ QÐ':Ô%;Ñ!<Ô!<¸bÐBcÐTWÐBcÐBcÐBcÑdÔdÐdØ× Ò ¥ Q Ô%7Ñ!8Ô!8¸"Ð>`ÐQTÐ>`Ð>`Ð>`ÑaÔaÐaØ× Ò ¥ QÐ'AÔ%BÑ!CÔ!CÀRÐInÐ_bÐInÐInÐInÑoÔoÐoØ× Ò ¥ QÐ'CÔ%DÑ!EÔ!EÀrÐKrÐcfÐKrÐKrÐKrÑsÔsÐsÐsð	tð 	tr4   c                ó4  — | j         D ]�}| j        |         }t          dd¦  «        D ]6}|                      t	          |d|› �         ¦  «        dd|› d|› d�¦  «         Œ7dD ]6}|                      t	          |d	|› �         ¦  «        d
d|› d|› d�¦  «         Œ7Œ�dS )zVItem rarities (<= 15 chars) and elements (<= 10 chars) must satisfy compact HUD specs.é   é   Úrarity_r‹   zRarity 'r_   rŒ   )ÚmetalÚwoodÚwaterÚfireÚearthÚelement_é
   z	Element 'z] exceeds 10 charsN)rK   rI   Úranger�   rZ   )rR   rS   r˜   ÚrÚelems        r2   Ú,test_08_rarity_and_element_microcopy_brevityzVTestMicrocopyLengthLimitsAndHUDErgonomics.test_08_rarity_and_element_microcopy_brevityz   sè   € à”<ð 	uð 	uˆCØÔ! #Ô&ˆAÝ˜1˜a‘[”[ð mð m�Ø×$Ò$¥S¨¨=°Q¨=¨=Ô)9Ñ%:Ô%:¸BÐ@kÈ1Ð@kÐ@kÐTWÐ@kÐ@kÐ@kÑlÔlÐlÐlØCð uð u�Ø×$Ò$¥S¨Ð+<°dÐ+<Ð+<Ô)=Ñ%>Ô%>ÀÐDsÐPTÐDsÐDsÐ\_ÐDsÐDsÐDsÑtÔtÐtÐtðuð		uð 	ur4   c                óÊ   — g d¢}| j         D ]V}|D ]Q}| j        |         |         }|                      d|d|› d|› d�¦  «         |                      d|d|› d|› d�¦  «         ŒRŒWdS )	zoTabs, buttons, rarities, elements, and badges must never contain bilingual parenthetical formatting '(' or ')'.)r’   r“   r•   r—   Úrarity_1Úrarity_2Úrarity_3Úrarity_4Úrarity_5Úelement_metalÚelement_woodÚelement_waterÚelement_fireÚelement_earthú(zForbidden paren '(' in [z] key 'ú'ú)zForbidden paren ')' in [N)rK   rI   ÚassertNotIn)rR   Úcompact_keysrS   r�   rd   s        r2   Ú4test_09_zero_bilingual_parentheses_in_compact_labelsz^TestMicrocopyLengthLimitsAndHUDErgonomics.test_09_zero_bilingual_parentheses_in_compact_labelsƒ   sº   € ð
ð 
ð 
ˆð
 ”<ð 	Xð 	XˆCØ!ð Xð X�ØÔ'¨Ô,¨QÔ/�Ø× Ò   cÐ+VÀcÐ+VÐ+VÐRSÐ+VÐ+VÐ+VÑWÔWÐWØ× Ò   cÐ+VÀcÐ+VÐ+VÐRSÐ+VÐ+VÐ+VÑWÔWÐWÐWðXð	Xð 	Xr4   Nr{   )
r|   r}   r~   r   r€   rN   r�   r™   r¨   r¹   r�   r4   r2   rƒ   rƒ   a   sž   € € € € € ØhÐhàðMð Mð Mñ „[ðMð
sð sð sð sðtð tð tð tðuð uð uð uðXð Xð Xð Xð Xð Xr4   rƒ   c                  ó*   — e Zd ZdZd	d„Zd
d„Zd
d„ZdS )Ú/TestItemTooltipLocalizationAndReactiveSwitchingz_Empirically tests tooltip rendering and dynamic in-place re-translation across all 9 languages.r   úDict[str, Any]c                ó4  — t                                ¦   «                              ¦   «         }t                               ¦   «                              ¦   «         }t	          t
          ¦  «                             dd¦  «        }d|› d|› d|› d�}t          |¦  «        S )Nú\ú/aû  
        const fs = await import('node:fs');
        const elements = {};
        const createEl = (id) => (elements[id] = {
            id, className: '', textContent: '', innerHTML: '', dataset: {}, children: [],
            classList: { _c: new Set(), add(...c) { c.forEach(x => this._c.add(x)); }, remove(...c) { c.forEach(x => this._c.delete(x)); }, contains(c) { return this._c.has(c); } },
            appendChild(c) { this.children.push(c); return c; }, addEventListener: () => {}, querySelectorAll: () => []
        });
        ['modal-item-link-tooltip', 'item-tooltip-name', 'item-tooltip-sub', 'item-tooltip-icon', 'item-tooltip-crafter', 'item-tooltip-affixes', 'item-tooltip-hmac-badge', 'chat-messages-container', 'chat-input', 'chat-send-btn', 'chat-cooldown-badge', 'chat-ticker-channel', 'chat-ticker-text'].forEach(createEl);
        global.window = { dispatchEvent: () => {}, localStorage: { getItem: () => null, setItem: () => {} }, addEventListener: () => {} };
        global.document = { getElementById: (id) => elements[id] || null, querySelectorAll: () => [], addEventListener: () => {}, createElement: () => createEl('c_' + Math.random()) };
        global.Image = class { constructor() { this.src = ''; } };

        const chatCat = await import('zŒ');
        window.CHAT_I18N_CATALOG = chatCat.CHAT_I18N_CATALOG; window.CHANNEL_I18N = chatCat.CHANNEL_I18N;
        eval(fs.readFileSync('z3', 'utf-8'));
        const chatUi = await import('uè  ');

        const verifiedSnap = { itemUuid: 'v-1', signature: 's1', itemName: 'ThÃ¡i Cá»• ThÆ°Æ¡ng', rarity: 5, element: 3, quality: 20, itemLevel: 95, crafterName: 'Ä�á»™c CÃ´', affixes: [{ statName: 'Báº¡o KÃ­ch', statValue: 40, isImplicit: true }], isVerified: true };
        const unverifiedSnap = { itemUuid: 'uv-1', signature: 's2', itemName: 'PhÃ´i Kiáº¿m', rarity: 1, element: 1, isVerified: false };
        const results = {};

        for (const loc of ['vi', 'en', 'zh', 'ja', 'ko', 'th', 'de', 'ru', 'es']) {
            window.FreeExileI18n.setLocale(loc); chatUi.updateLanguage(loc);
            elements['item-tooltip-affixes'].children.length = 0; chatUi.openItemTooltip(verifiedSnap);
            const vData = { sub: elements['item-tooltip-sub'].textContent, badge: elements['item-tooltip-hmac-badge'].textContent, crafter: elements['item-tooltip-crafter'].textContent };
            elements['item-tooltip-affixes'].children.length = 0; chatUi.openItemTooltip(unverifiedSnap);
            const uvData = { sub: elements['item-tooltip-sub'].textContent, badge: elements['item-tooltip-hmac-badge'].textContent, affixes: elements['item-tooltip-affixes'].children.map(c => c.textContent) };
            results[loc] = { verified: vData, unverified: uvData };
        }
        chatUi.openItemTooltip(verifiedSnap); window.FreeExileI18n.setLocale('ja'); chatUi.updateLanguage('ja');
        console.log(JSON.stringify({ results, dynamicJa: elements['item-tooltip-sub'].textContent }));
        )rF   rG   rH   Ú
CHAT_UI_JSr   ÚI18N_JSÚreplacer3   )rR   rM   Úui_urlÚ	i18n_pathr   s        r2   Ú_eval_tooltip_in_nodezETestItemTooltipLocalizationAndReactiveSwitching._eval_tooltip_in_node”   sŸ   € Ý"×*Ò*Ñ,Ô,×3Ò3Ñ5Ô5ˆÝ×#Ò#Ñ%Ô%×,Ò,Ñ.Ô.ˆÝ�‘L”L×(Ò(¨¨sÑ3Ô3ˆ	ð ð (0ð ð  ð  )ð ð  ð  '-ð! ð  ð  ˆõB ˜‰~Œ~Ðr4   r7   c           
     ó8  — |                       ¦   «         d         }ddddddddd	d
œ	}dddddddddd
œ	}dddddddddd
œ	}ddddd d!d"d#d$d
œ	}|                     ¦   «         D �]9\  }}|                      ||         |d%         d&         d'|› d(�¦  «         |                      ||         |d%         d&         d'|› d)�¦  «         |                      |d%         d*         ||         ¦  «         |                      |d%         d+         d,¦  «         |                      |d-         d*         ||         ¦  «         |                      t	          |d-         d.         ¦  «        d/¦  «         |                      t	          |d-         d.         d0         ¦  «        d0k    ¦  «         �Œ;d1S )2zjItem tooltip sub-header, verified/unverified badges, and affixes notices must localize in all 9 languages.Úresultsu
   ThÃ¡i Cá»•ÚPrimalõ   å¤ªå�¤u   íƒœê³ u   à¹‚à¸šà¸£à¸²à¸“à¸�à¸²à¸¥ÚUraltu   Ð”Ñ€ÐµÐ²Ð½Ð¸Ð¹Ú
Primigenior<   u   Thá»§yÚWateru   æ°´u   ìˆ˜u	   à¸™à¹‰à¸³ÚWasseru   Ð’Ð¾Ð´Ð°ÚAguau   âœ“ HMAC XÃ¡c Thá»±cu   âœ“ HMAC Verifiedu   âœ“ HMAC å·²éªŒè¯�u   âœ“ HMAC èª�è¨¼æ¸ˆu   âœ“ HMAC ê²€ì¦�ë�¨u+   âœ“ à¸•à¸£à¸§à¸ˆà¸ªà¸­à¸š HMAC à¹�à¸¥à¹‰à¸§u   âœ“ HMAC Verifiziertu   âœ“ HMAC ÐŸÐ¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½u   âœ“ HMAC Verificadou   âš  ChÆ°a XÃ¡c Thá»±cu   âš  Unverifiedu   âš  æœªéªŒè¯�u   âš  æœªèª�è¨¼u   âš  ë¯¸ê²€ì¦�u(   âš  à¸¢à¸±à¸‡à¹„à¸¡à¹ˆà¸¢à¸·à¸™à¸¢à¸±à¸™u   âš  Nicht verifiziertu!   âš  Ð�Ðµ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¾u   âš  Sin verificarÚverifiedÚsubzSub header in [z] missing rarity_5z] missing element_waterÚbadgeÚcrafteru
   Ä�á»™c CÃ´Ú
unverifiedÚaffixesr›   r   N)rÅ   r`   rQ   rY   rZ   rb   )rR   r0   Úexpected_rarity_5Úexpected_element_waterÚexpected_badge_verifiedÚexpected_badge_unverifiedrS   Úvs           r2   Ú:test_10_item_tooltip_translation_parity_all_nine_languageszjTestItemTooltipLocalizationAndReactiveSwitching.test_10_item_tooltip_translation_parity_all_nine_languages»   sV  € à×(Ò(Ñ*Ô*¨9Ô5ˆØ#/°xÀxÐW_Ðgoð  xRð  Zað  iyð  AMð  Nð  NÐØ(0¸ÀuÐTYÐafÐnyð  BJð  R\ð  djð  "kð  "kÐØ)?ÐGZÐbvð  Sð  [oð  wdð  lBð  Jkð  sHð  #Ið  #IÐØ+BÐJZÐbqð  zIð  Q`ð  hRð  Zqð  y\ð  dwð  %xð  %xÐ!à—i’i‘k”kð 	Dñ 	D‰FˆC�Ø�MŠMÐ+¨CÔ0°!°J´-ÀÔ2FÐHqÐZ]ÐHqÐHqÐHqÑrÔrÐrØ�MŠMÐ0°Ô5°q¸´}ÀUÔ7KÐM{Ð_bÐM{ÐM{ÐM{Ñ|Ô|Ð|Ø×Ò˜Q˜zœ]¨7Ô3Ð5LÈSÔ5QÑRÔRÐRØ×Ò˜Q˜zœ]¨9Ô5°|ÑDÔDÐDØ×Ò˜Q˜|œ_¨WÔ5Ð7PÐQTÔ7UÑVÔVÐVØ×Ò�S  <¤°Ô!;Ñ<Ô<¸aÑ@Ô@Ð@Ø�OŠO�C  ,¤°	Ô :¸1Ô =Ñ>Ô>ÀÒBÑCÔCÐCÑCð	Dð 	Dr4   c                óž   — |                       ¦   «         }|                      d|d         ¦  «         |                      d|d         ¦  «         dS )zgCalling updateLanguage while an item tooltip is opened must dynamically re-render it in the new locale.rÉ   Ú	dynamicJazLv. 95N)rÅ   rQ   )rR   r
   s     r2   Ú9test_11_dynamic_language_switch_retranslates_open_tooltipziTestItemTooltipLocalizationAndReactiveSwitching.test_11_dynamic_language_switch_retranslates_open_tooltipÌ   sK   € à×)Ò)Ñ+Ô+ˆØ�Š�h  [Ô 1Ñ2Ô2Ð2Ø�Š�h  [Ô 1Ñ2Ô2Ð2Ð2Ð2r4   N)r   r¼   r{   )r|   r}   r~   r   rÅ   rÚ   rÝ   r�   r4   r2   r»   r»   ‘   s[   € € € € € ØiÐið%ð %ð %ð %ðNDð Dð Dð Dð"3ð 3ð 3ð 3ð 3ð 3r4   r»   c                  ó*   — e Zd ZdZdd„Zdd„Zdd„ZdS )	Ú+TestExceptionResilienceAndDefensiveBehaviorzYStress-tests exception handling, null parameters, missing keys, and subscriber isolation.r   r7   c                óÐ  — t          t          ¦  «                             dd¦  «        }d|› d�}t          |¦  «        }|                      |d         d¦  «         |                      |d         d¦  «         |                      |d	         d
¦  «         |                      |d         d¦  «         |                      |d         d¦  «         |                      |d         d¦  «         dS )zSFreeExileI18n.t() must never throw on missing, null, undefined, or non-string keys.r¾   r¿   áo  
        const fs = await import('node:fs');
        global.window = { localStorage: { getItem: () => null, setItem: () => {} }, dispatchEvent: () => {} };
        global.document = { getElementById: () => null, querySelectorAll: () => [], addEventListener: () => {} };
        global.Image = class { constructor() { this.src = ''; } };
        eval(fs.readFileSync('a   ', 'utf-8'));
        const i18n = window.FreeExileI18n;
        console.log(JSON.stringify({
            mFb: i18n.t('missing_xyz', {}, 'MY_FALLBACK'),
            mNoFb: i18n.t('missing_xyz'),
            nullFb: i18n.t(null, {}, 'FALLBACK_NULL'),
            nullNoFb: i18n.t(null),
            undefFb: i18n.t(undefined, {}, 'FALLBACK_UNDEF'),
            numKey: i18n.t(404, {}, 'NOT_FOUND'),
            nullParams: i18n.t('char_name', null, 'Exile'),
            undefParams: i18n.t('char_name', undefined, 'Exile')
        }));
        ÚmFbÚMY_FALLBACKÚmNoFbÚmissing_xyzÚnullFbÚFALLBACK_NULLÚnullNoFbÚ ÚundefFbÚFALLBACK_UNDEFÚnumKeyÚ	NOT_FOUNDN)r   rÁ   rÂ   r3   rY   ©rR   rÄ   r   r˜   s       r2   Ú9test_12_freeexile_i18n_t_missing_and_null_keys_resiliencezeTestExceptionResilienceAndDefensiveBehavior.test_12_freeexile_i18n_t_missing_and_null_keys_resilienceÖ   sè   € å�‘L”L×(Ò(¨¨sÑ3Ô3ˆ	ðð
  )ðð ð ˆõ$ �T‰NŒNˆØ×Ò˜˜5œ =Ñ1Ô1Ð1Ø×Ò˜˜7œ ]Ñ3Ô3Ð3Ø×Ò˜˜8œ oÑ6Ô6Ð6Ø×Ò˜˜:œ¨Ñ+Ô+Ð+Ø×Ò˜˜9œÐ'7Ñ8Ô8Ð8Ø×Ò˜˜8œ kÑ2Ô2Ð2Ð2Ð2r4   c                óZ  — t          t          ¦  «                             dd¦  «        }d|› d�}t          |¦  «        }|                      |d         ¦  «         |                      |d         ¦  «         |                      |d         ¦  «         |                      |d         d	¦  «         d
S )ziExceptions thrown inside subscribers must be isolated and not prevent subsequent subscribers from firing.r¾   r¿   rá   a  ', 'utf-8'));
        const i18n = window.FreeExileI18n;
        let sub1Fired = false, sub2Fired = false, sub3Fired = false;
        i18n.subscribe(() => { sub1Fired = true; });
        i18n.subscribe(() => { throw new Error('Explosive subscriber error'); });
        i18n.subscribe((newLoc, prevLoc) => { sub2Fired = true; if (newLoc === 'ru' && prevLoc === 'vi') sub3Fired = true; });
        i18n.setLocale('ru');
        console.log(JSON.stringify({ sub1Fired, sub2Fired, sub3Fired, loc: i18n.getLocale() }));
        Ú	sub1FiredÚ	sub2FiredÚ	sub3FiredrS   rD   N)r   rÁ   rÂ   r3   rb   rY   rî   s       r2   Ú3test_13_subscriber_exception_isolation_in_event_busz_TestExceptionResilienceAndDefensiveBehavior.test_13_subscriber_exception_isolation_in_event_busó   s§   € å�‘L”L×(Ò(¨¨sÑ3Ô3ˆ	ðð
  )ðð ð ˆõ �T‰NŒNˆØ�Š˜˜+œÑ'Ô'Ð'Ø�Š˜˜+œÑ'Ô'Ð'Ø�Š˜˜+œÑ'Ô'Ð'Ø×Ò˜˜5œ 4Ñ(Ô(Ð(Ð(Ð(r4   c                óÔ   — t                                ¦   «                              ¦   «         }d|› d�}|                      t	          |¦  «                             dd¦  «        ¦  «         dS )z`chat_ui.js functions must execute safely without throwing when DOM elements are null or minimal.a#  
        global.document = { getElementById: () => null, querySelectorAll: () => [], addEventListener: () => {} };
        global.window = { dispatchEvent: () => {}, localStorage: { getItem: () => null, setItem: () => {} }, addEventListener: () => {} };
        const chatUi = await import('zû');
        chatUi.updateLanguage('de'); chatUi.renderChatLog(); chatUi.updateCooldownUI(); chatUi.updateInputPlaceholder(); chatUi.openItemTooltip(null); chatUi.closeItemTooltip();
        console.log(JSON.stringify({ headlessSafe: true }));
        ÚheadlessSafeFN)rÀ   rG   rH   rb   r3   Úget)rR   rÃ   r   s      r2   Ú.test_14_chat_ui_headless_and_null_dom_immunityzZTestExceptionResilienceAndDefensiveBehavior.test_14_chat_ui_headless_and_null_dom_immunity
  sj   € å×#Ò#Ñ%Ô%×,Ò,Ñ.Ô.ˆðð '-ðð ð ˆð 	�Š� ™œ×*Ò*¨>¸5ÑAÔAÑBÔBÐBÐBÐBr4   Nr{   )r|   r}   r~   r   rï   rô   rø   r�   r4   r2   rß   rß   Ó   s\   € € € € € ØcÐcð3ð 3ð 3ð 3ð:)ð )ð )ð )ð.
Cð 
Cð 
Cð 
Cð 
Cð 
Cr4   rß   Ú__main__)r   )r   r   r   r   r   r   )"r   Ú
__future__r   ÚbuiltinsÚ@py_builtinsÚ_pytest.assertion.rewriteÚ	assertionÚrewriteÚ
@pytest_arr,   rg   r    ÚunittestÚpathlibr   Útypingr   r   Ú__file__rG   Úparentr"   Ú
WEBAPP_DIRrF   rÁ   rÀ   r3   ÚTestCaser6   rƒ   r»   rß   r|   Úmainr�   r4   r2   ú<module>r	     s  ðð	ð 	ð #Ð "Ð "Ð "Ð "Ð "à  € € € € € € € € € € € € €€€€Ø 	€	€	€	Ø Ð Ð Ð Ø €€€Ø Ð Ð Ð Ð Ð Ø Ð Ð Ð Ð Ð Ð Ð àˆt�H‰~Œ~×%Ò%Ñ'Ô'Ô.Ô5Ô<€Ø˜HÑ$ xÑ/€
Ø˜tÑ# fÑ,Ð/EÑE€Ø
�tÑ
˜fÑ
$ yÑ
0€Ø˜$Ñ Ñ%¨Ñ4€
ðð ð ð ð ð$0lð 0lð 0lð 0lð 0l¨xÔ/@ñ 0lô 0lð 0lðf-Xð -Xð -Xð -Xð -X°Ô0Añ -Xô -Xð -Xð`?3ð ?3ð ?3ð ?3ð ?3°hÔ6Gñ ?3ô ?3ð ?3ðDACð ACð ACð ACð AC°(Ô2Cñ ACô ACð ACðH ˆzÒÐØ€H„M�O„O€O€O€Oð Ðr4   