§
    ÿ¿jB;  ã                  óü  — 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ZddlmZ ddlmZmZmZ  eej        d¦  «        rej                             d¬¦  «          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edz  dz  dz  Zg d¢Z dd„Z!dd„Z" G d„ dej#        ¦  «        Z$e%dk    r ej&        ¦   «          dS dS )aõ  
Comprehensive Unit Test Suite for FreeExile Reactive i18n Event Bus & Chat UI (Milestone 2).
Enforces 23 Test Cases across 5 Groups:
- Group 1: Observer / Event Bus mechanics (Tests 1-5)
- Group 2: Fallback chaining and parameter interpolation (Tests 6-10)
- Group 3: 9-language dictionary parity & key presence (Tests 11-15)
- Group 4: Chat UI reactive DOM integration without page reload (Tests 16-20)
- Group 5: Edge cases, null/empty keys, invalid locales, localStorage persistence (Tests 21-23)
é    )ÚannotationsN)ÚPath)ÚAnyÚDictÚSetÚreconfigureúutf-8©ÚencodingÚclientÚwebappÚjsÚdatazi18n.jszi18n_catalog.jszchat_i18n_catalog.jsÚuiz
chat_ui.js)	ÚviÚenÚzhÚjaÚkoÚthÚdeÚruÚesÚpathr   ÚreturnúDict[str, Set[str]]c                ó   — |                       d¬¦  «        }i }t          D �],}t          j        d|› d�|¦  «        }|sŒ|                     ¦   «         d|                     ¦   «         }}}|t          |¦  «        k     rG|dk    rA||         dk    r|dz  }n||         dk    r|dz  }|dz  }|t          |¦  «        k     r|dk    °A||dz
  |…         }t          ¦   «         }	t          j        d	¦  «        }
|
                     |¦  «        D ]?}|	 	                    | 
                    d¦  «        p| 
                    d
¦  «        ¦  «         Œ@|	||<   �Œ.|S )zBExtracts keys for all 9 locales using syntax-aware brace matching.r	   r
   z\bz	\s*:\s*\{é   r   Ú{Ú}z?(?:[{,]\s*)\s*(?:["\']([a-zA-Z0-9_]+)["\']|([a-zA-Z0-9_]+))\s*:é   )Ú	read_textÚALL_9_LOCALESÚreÚsearchÚendÚlenÚsetÚcompileÚfinditerÚaddÚgroup)r   ÚtextÚresÚlocÚmÚstartÚbracesÚcurrÚblockÚkeysÚpatÚkms               ú7C:\Projects\FreeExile\tests\unit\test_i18n_event_bus.pyÚparse_catalog_filer9   "   sd  € à�>Š> 7ˆ>Ñ+Ô+€DØ!€CÝð ñ ˆÝŒIÐ+˜CÐ+Ð+Ð+¨TÑ2Ô2ˆØð 	ØØŸeše™gœg q¨!¯%ª%©'¬'�tˆvˆØ•S˜‘Y”YÒÐ 6¨A¢: :Ø�DŒz˜SÒ Ð Ø˜!‘��Ø�d”˜sÒ"Ð"Ø˜!‘�Ø�A‰IˆDð •S˜‘Y”YÒÐ 6¨A¢: :ð �U˜Q‘Y Ð%Ô&ˆÝ™œˆÝŒjÐ[Ñ\Ô\ˆØ—,’,˜uÑ%Ô%ð 	1ð 	1ˆBØ�HŠH�R—X’X˜a‘[”[Ð/ B§H¢H¨Q¡K¤KÑ0Ô0Ð0Ð0ØˆˆC‰‰Ø€Jó    úDict[str, Any]c            	     óV  — t          t          ¦  «                             dd¦  «        } t          t          ¦  «                             dd¦  «        }t          t          ¦  «                             dd¦  «        }t          t
          ¦  «                             dd¦  «        }d|› d|› d| › d|› d�	}t          j        dd	d
|gdddt          t          ¦  «        ¬¦  «        }|j	        dk    r't          d|j	        › d|j        › d|j        › �¦  «        ‚|j                             ¦   «                              d¦  «        D ]V}|                     ¦   «         }|                     d¦  «        r+|                     d¦  «        rt#          j        |¦  «        c S ŒWt'          d|j        › �¦  «        ‚)zIRuns full in-memory Node.js simulation of FreeExile i18n bus and Chat UI.ú\ú/aÚ  
import fs from 'fs';
import vm from 'vm';

global.Image = class Image { constructor() { this.src = ''; } };
const storage = {};
global.localStorage = {
  getItem: (k) => storage[k] || null,
  setItem: (k, v) => { storage[k] = String(v); },
  removeItem: (k) => { delete storage[k]; }
};

const windowListeners = {};
global.CustomEvent = class CustomEvent {
  constructor(t, i = {}) { this.type = t; this.detail = i.detail || {}; }
};
global.window = {
  addEventListener: (n, cb) => { windowListeners[n] = windowListeners[n] || []; windowListeners[n].push(cb); },
  removeEventListener: (n, cb) => { if (windowListeners[n]) windowListeners[n] = windowListeners[n].filter(f => f !== cb); },
  dispatchEvent: (e) => { (windowListeners[e.type] || []).forEach(f => f(e)); },
  localStorage: global.localStorage
};

const createMockEl = (id = '') => ({
  id, dataset: {}, textContent: '', innerText: '', value: '', placeholder: '', disabled: false,
  classList: { add: () => {}, remove: () => {}, contains: () => false },
  style: { setProperty: () => {}, getProperty: () => '' },
  appendChild: (c) => c, querySelectorAll: () => [], querySelector: () => null, addEventListener: () => {}
});

const elements = new Map();
const getEl = (id) => { if (!elements.has(id)) elements.set(id, createMockEl(id)); return elements.get(id); };
const tabs = Array.from({ length: 8 }, (_, i) => {
  const t = createMockEl('chat-tab-' + (i + 1));
  t.dataset.channelId = String(i + 1);
  return t;
});

global.document = {
  readyState: 'complete',
  getElementById: (id) => getEl(id),
  createElement: (tag) => createMockEl('', tag),
  querySelectorAll: (sel) => (sel === '.chat-channel-tab' ? tabs : []),
  addEventListener: () => {}
};

const catCode = fs.readFileSync('zÅ', 'utf-8');
const sb = { window: global.window };
vm.createContext(sb);
vm.runInContext(catCode, sb);
global.I18N_CATALOG = global.window.I18N_CATALOG;
const chatCatModule = await import('file:///zV');
global.CHAT_I18N_CATALOG = chatCatModule.CHAT_I18N_CATALOG;
await import('file:///zS');
const i18n = global.window.FreeExileI18n;
const chatUI = await import('file:///u’  ');
chatUI.initChatUI();
const out = {};

// Group 1: Observer / Event Bus
let g1_received = null;
const unsub1 = i18n.onLocaleChanged((loc, prev) => { g1_received = { loc, prev }; });
i18n.setLocale('en');
out.t01 = { received: g1_received, ok: g1_received?.loc === 'en' };
unsub1();
const seq = [];
const uA = i18n.subscribe(() => seq.push('A')), uB = i18n.subscribe(() => seq.push('B')), uC = i18n.subscribe(() => seq.push('C'));
i18n.setLocale('zh');
out.t02 = { seq, ok: seq.join('') === 'ABC' };
uA(); uB(); uC();
let bExecuted = false;
const errSub = i18n.subscribe(() => { throw new Error('sub_error'); });
const safeSub = i18n.subscribe(() => { bExecuted = true; });
i18n.setLocale('ja');
out.t03 = { bExecuted, ok: bExecuted === true };
errSub(); safeSub();
let unsubCalled = false;
const testUnsub = i18n.subscribe(() => { unsubCalled = true; });
testUnsub();
unsubCalled = false;
i18n.setLocale('ko');
out.t04 = { unsubCalled, ok: unsubCalled === false };
let customEvtDetail = null;
window.addEventListener('freeexile:localeChanged', (e) => { customEvtDetail = e.detail; });
i18n.setLocale('th');
out.t05 = { detailLocale: customEvtDetail?.locale, ok: customEvtDetail?.locale === 'th' };

// Group 2: Fallback Chaining & Interpolation
i18n.setLocale('en');
out.t06 = { btn_send: i18n.t('chat_btn_send'), ok: i18n.t('chat_btn_send') === 'Send' };
i18n.registerCatalog({ ja: {}, en: { test_fallback_key: 'EnVal' }, vi: { test_fallback_key: 'ViVal' } });
i18n.setLocale('ja');
out.t07 = { val: i18n.t('test_fallback_key'), ok: i18n.t('test_fallback_key') === 'EnVal' };
i18n.registerCatalog({ ja: {}, en: {}, vi: { test_vi_only: 'ViOnlyVal' } });
out.t08 = { val: i18n.t('test_vi_only'), ok: i18n.t('test_vi_only') === 'ViOnlyVal' };
out.t09 = { ok: i18n.t('nonexistent_key_123') === 'nonexistent_key_123' && i18n.t('nonexistent_key_123', {}, 'CustomFallback') === 'CustomFallback' };
i18n.setLocale('en');
const pEn = i18n.t('chat_cooldown_wait', { sec: 5 });
i18n.setLocale('vi');
const pVi = i18n.t('chat_cooldown_wait', { sec: 5 });
out.t10 = { pEn, pVi, ok: pEn === 'Wait 5s' && pVi === 'Chá»� 5s' };

// Group 4: Chat UI Reactive DOM
out.t16 = { hasListeners: i18n.listeners.length > 0, ok: i18n.listeners.length > 0 };
i18n.setLocale('en');
out.t17 = { tab1: tabs[0].textContent, tab2: tabs[1].textContent, tab3: tabs[2].textContent, ok: tabs[0].textContent === 'World' && tabs[1].textContent === 'Zone' && tabs[2].textContent === 'Guild' };
out.t18 = { placeholder: getEl('chat-input').placeholder, ok: getEl('chat-input').placeholder.includes('World') };
out.t19 = { btnSend: getEl('chat-send-btn').textContent, ok: getEl('chat-send-btn').textContent === 'Send' };
chatUI.addChatMessage({ channel: 1, senderName: 'Hero', displayContent: 'Msg 1' });
chatUI.addChatMessage({ channel: 1, senderName: 'Hero', displayContent: 'Msg 2' });
const histBefore = chatUI.getChannelHistory(1).length;
i18n.setLocale('zh'); i18n.setLocale('ja'); i18n.setLocale('vi');
const histAfter = chatUI.getChannelHistory(1).length;
out.t20 = { ok: histBefore === 2 && histAfter === 2 };

// Group 5: Edge Cases
const curBefore = i18n.getLocale();
i18n.setLocale(null); i18n.setLocale('');
out.t21 = { ok: i18n.getLocale() === curBefore };
i18n.setLocale('ja');
out.t22 = { stored: storage['freeexile_locale'], ok: storage['freeexile_locale'] === 'ja' };
out.t23 = { ok: i18n.t('') === '' && i18n.t(null, {}, 'FallbackVal') === 'FallbackVal' };

console.log(JSON.stringify(out));
Únodez--input-type=modulez-eTr	   )Úcapture_outputr-   r   Úcwdr   zNode execution failed (code z):
ú
r   r    z'Could not parse JSON output from Node:
)ÚstrÚI18N_JSÚreplaceÚ
CHAT_UI_JSÚCHAT_CATALOG_JSÚI18N_CATALOG_JSÚ
subprocessÚrunÚPROJECT_ROOTÚ
returncodeÚRuntimeErrorÚstderrÚstdoutÚstripÚsplitÚ
startswithÚendswithÚjsonÚloadsÚ
ValueError)Úi18n_pÚ	chat_ui_pÚ
chat_cat_pÚ
i18n_cat_pÚnode_scriptr.   Úlines          r8   Úexecute_node_test_harnessr]   :   sÓ  € å•‰\Œ\×!Ò! $¨Ñ,Ô,€FÝ•J‘”×'Ò'¨¨cÑ2Ô2€IÝ•_Ñ%Ô%×-Ò-¨d°CÑ8Ô8€JÝ•_Ñ%Ô%×-Ò-¨d°CÑ8Ô8€Jð|ð\ #-ð]|ð |ðf .8ðg|ð |ðj ðk|ð |ðn '0ðo|ð |ð |€Kõ| Œ.Ø	Ð&¨¨kÐ:ØØØÝ•ÑÔðñ ô €Cð „~˜ÒÐÝÐh¸#¼.ÐhÐhÈcÌjÐhÐhÐ\_Ô\fÐhÐhÑiÔiÐiØ”
× Ò Ñ"Ô"×(Ò(¨Ñ.Ô.ð $ð $ˆØ�zŠz‰|Œ|ˆØ�?Š?˜3ÑÔð 	$ D§M¢M°#Ñ$6Ô$6ð 	$Ý”:˜dÑ#Ô#Ð#Ð#Ð#øÝ
ÐLÀÄ
ÐLÐLÑ
MÔ
MÐMr:   c                  ó  — e Zd ZU dZi Zded<   i Zded<   i Zded<   ed"d	„¦   «         Z	d"d
„Z
d"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d„Zd"d „Z d!S )#ÚTestI18nEventBusAndReactivityzS23 comprehensive tests verifying the i18n event bus, catalogs, and chat reactivity.r;   Úresultsr   Ú	chat_keysÚ	i18n_keysr   ÚNonec                ó�   — t          ¦   «         | _        t          t          ¦  «        | _        t          t
          ¦  «        | _        d S )N)r]   r`   r9   rG   ra   rH   rb   )Úclss    r8   Ú
setUpClassz(TestI18nEventBusAndReactivity.setUpClassÖ   s1   € å/Ñ1Ô1ˆŒÝ*­?Ñ;Ô;ˆŒÝ*­?Ñ;Ô;ˆŒˆˆr:   c                ó®   — |                       | j        d         d         d¦  «         |                      | j        d         d         d         d¦  «         d S )NÚt01Úokz0onLocaleChanged callback must receive new localeÚreceivedr/   r   ©Ú
assertTruer`   ÚassertEqual©Úselfs    r8   Ú*test_01_listener_registration_and_callbackzHTestI18nEventBusAndReactivity.test_01_listener_registration_and_callbackÝ   sP   € Ø�Š˜œ UÔ+¨DÔ1Ð3eÑfÔfÐfØ×Ò˜œ eÔ,¨ZÔ8¸Ô?ÀÑFÔFÐFÐFÐFr:   c                ó¦   — |                       | j        d         d         d¦  «         |                      | j        d         d         g d¢¦  «         d S )NÚt02ri   z-Multiple subscribers must execute in sequenceÚseq)ÚAÚBÚCrk   rn   s    r8   Ú#test_02_multiple_listeners_isolatedzATestI18nEventBusAndReactivity.test_02_multiple_listeners_isolatedá   sO   € Ø�Š˜œ UÔ+¨DÔ1Ð3bÑcÔcÐcØ×Ò˜œ eÔ,¨UÔ3°_°_°_ÑEÔEÐEÐEÐEr:   c                óT   — |                       | j        d         d         d¦  «         d S )NÚt03ri   z1Subsequent subscribers must run if earlier throws©rl   r`   rn   s    r8   Ú%test_03_listener_exception_resiliencezCTestI18nEventBusAndReactivity.test_03_listener_exception_resilienceå   s)   € Ø�Š˜œ UÔ+¨DÔ1Ð3fÑgÔgÐgÐgÐgr:   c                óT   — |                       | j        d         d         d¦  «         d S )NÚt04ri   z:Unsubscribed callback must not be invoked on locale changerz   rn   s    r8   Útest_04_listener_unsubscriptionz=TestI18nEventBusAndReactivity.test_04_listener_unsubscriptionè   s)   € Ø�Š˜œ UÔ+¨DÔ1Ð3oÑpÔpÐpÐpÐpr:   c                ó¢   — |                       | j        d         d         d¦  «         |                      | j        d         d         d¦  «         d S )NÚt05ri   z4window freeexile:localeChanged CustomEvent must fireÚdetailLocaler   rk   rn   s    r8   Ú$test_05_custom_event_window_dispatchzBTestI18nEventBusAndReactivity.test_05_custom_event_window_dispatchë   sK   € Ø�Š˜œ UÔ+¨DÔ1Ð3iÑjÔjÐjØ×Ò˜œ eÔ,¨^Ô<¸dÑCÔCÐCÐCÐCr:   c                ó¢   — |                       | j        d         d         d¦  «         |                      | j        d         d         d¦  «         d S )NÚt06ri   z7Exact lookup for chat_btn_send in EN must return 'Send'Úbtn_sendÚSendrk   rn   s    r8   Útest_06_exact_locale_lookupz9TestI18nEventBusAndReactivity.test_06_exact_locale_lookupð   sK   € Ø�Š˜œ UÔ+¨DÔ1Ð3lÑmÔmÐmØ×Ò˜œ eÔ,¨ZÔ8¸&ÑAÔAÐAÐAÐAr:   c                ó¢   — |                       | j        d         d         d¦  «         |                      | j        d         d         d¦  «         d S )NÚt07ri   z&Missing key in JA must fall back to ENÚvalÚEnValrk   rn   s    r8   Ú$test_07_fallback_chain_current_to_enzBTestI18nEventBusAndReactivity.test_07_fallback_chain_current_to_enô   sK   € Ø�Š˜œ UÔ+¨DÔ1Ð3[Ñ\Ô\Ð\Ø×Ò˜œ eÔ,¨UÔ3°WÑ=Ô=Ð=Ð=Ð=r:   c                ó¢   — |                       | j        d         d         d¦  «         |                      | j        d         d         d¦  «         d S )NÚt08ri   z-Missing key in JA and EN must fall back to VIrŠ   Ú	ViOnlyValrk   rn   s    r8   Útest_08_fallback_chain_en_to_viz=TestI18nEventBusAndReactivity.test_08_fallback_chain_en_to_viø   sK   € Ø�Š˜œ UÔ+¨DÔ1Ð3bÑcÔcÐcØ×Ò˜œ eÔ,¨UÔ3°[ÑAÔAÐAÐAÐAr:   c                óT   — |                       | j        d         d         d¦  «         d S )NÚt09ri   z3Non-existent key must return key or fallback stringrz   rn   s    r8   Útest_09_fallback_to_raw_keyz9TestI18nEventBusAndReactivity.test_09_fallback_to_raw_keyü   s)   € Ø�Š˜œ UÔ+¨DÔ1Ð3hÑiÔiÐiÐiÐir:   c                óð   — |                       | j        d         d         d¦  «         |                      | j        d         d         d¦  «         |                      | j        d         d         d¦  «         d S )NÚt10ri   z0Parameters like {sec} must interpolate correctlyÚpEnzWait 5sÚpViu   Chá»� 5srk   rn   s    r8   Útest_10_parameter_substitutionz<TestI18nEventBusAndReactivity.test_10_parameter_substitutionÿ   sm   € Ø�Š˜œ UÔ+¨DÔ1Ð3eÑfÔfÐfØ×Ò˜œ eÔ,¨UÔ3°YÑ?Ô?Ð?Ø×Ò˜œ eÔ,¨UÔ3°ZÑ@Ô@Ð@Ð@Ð@r:   c                ó–   — t           D ]@}|                      || j        d|› �¦  «         |                      || j        d|› �¦  «         ŒAd S )Nz!chat_i18n_catalog missing locale zi18n_catalog missing locale )r#   ÚassertInra   rb   )ro   r/   s     r8   Útest_11_all_9_locales_presentz;TestI18nEventBusAndReactivity.test_11_all_9_locales_present  se   € Ý ð 	Uð 	UˆCØ�MŠM˜#˜tœ~Ð/XÐSVÐ/XÐ/XÑYÔYÐYØ�MŠM˜#˜tœ~Ð/SÈcÐ/SÐ/SÑTÔTÐTÐTð	Uð 	Ur:   c                óº   — |                       | j        d         | j        d         d¦  «         |                       | j        d         | j        d         d¦  «         d S )Nr   r   z"Chat catalog VI-EN keys must matchz"i18n catalog VI-EN keys must match)rm   ra   rb   rn   s    r8   Útest_12_vi_en_dictionary_parityz=TestI18nEventBusAndReactivity.test_12_vi_en_dictionary_parity
  sV   € Ø×Ò˜œ¨Ô-¨t¬~¸dÔ/CÐEiÑjÔjÐjØ×Ò˜œ¨Ô-¨t¬~¸dÔ/CÐEiÑjÔjÐjÐjÐjr:   c           
     óš   — t          dd¦  «        D ]9}d|› �}t          D ]*}|                      || j        |         d|› d|› �¦  «         Œ+Œ:d S )Nr   é	   Úchat_channel_zMissing z in locale ©Úranger#   rš   ra   )ro   ÚiÚkr/   s       r8   Ú"test_13_all_8_channel_keys_presentz@TestI18nEventBusAndReactivity.test_13_all_8_channel_keys_present  s|   € Ý�q˜!‘”ð 	Vð 	VˆAØ# Ð#Ð#ˆAÝ$ð Vð V�Ø—’˜a ¤°Ô!4Ð6TÀÐ6TÐ6TÈsÐ6TÐ6TÑUÔUÐUÐUðVð	Vð 	Vr:   c           
     ó|   — g d¢}|D ]4}t           D ]*}|                      || j        |         d|› d|› �¦  «         Œ+Œ5d S )N)Úchat_perm_system_onlyÚchat_perm_guild_requiredÚchat_perm_party_requiredÚchat_perm_level_worldÚchat_perm_level_recruitÚchat_perm_mutedzPermission key z missing in locale )r#   rš   ra   )ro   Úrequired_perm_keysr¤   r/   s       r8   Ú$test_14_chat_permission_keys_presentzBTestI18nEventBusAndReactivity.test_14_chat_permission_keys_present  s|   € ð
ð 
ð 
Ðð
 $ð 	eð 	eˆAÝ$ð eð e�Ø—’˜a ¤°Ô!4Ð6cÈÐ6cÐ6cÐ^aÐ6cÐ6cÑdÔdÐdÐdðeð	eð 	er:   c                óî   — t          dd¦  «        D ]0}t          D ]&}|                      d|› �| j        |         ¦  «         Œ'Œ1dD ]0}t          D ]&}|                      d|› �| j        |         ¦  «         Œ'Œ1d S )Nr   é   Úrarity_)ÚmetalÚwoodÚwaterÚfireÚearthÚelement_r¡   )ro   Úrr/   Úelems       r8   Ú,test_15_chat_rarity_and_element_keys_presentzJTestI18nEventBusAndReactivity.test_15_chat_rarity_and_element_keys_present  s±   € Ý�q˜!‘”ð 	Bð 	BˆAÝ$ð Bð B�Ø—’˜m¨˜m˜m¨T¬^¸CÔ-@ÑAÔAÐAÐAðBà?ð 	Fð 	FˆDÝ$ð Fð F�Ø—’Ð/¨Ð/Ð/°´ÀÔ1DÑEÔEÐEÐEðFð	Fð 	Fr:   c                óT   — |                       | j        d         d         d¦  «         d S )NÚt16ri   z.Chat UI must attach listener to i18n event busrz   rn   s    r8   Ú&test_16_chat_ui_subscribes_to_i18n_buszDTestI18nEventBusAndReactivity.test_16_chat_ui_subscribes_to_i18n_bus'  s)   € Ø�Š˜œ UÔ+¨DÔ1Ð3cÑdÔdÐdÐdÐdr:   c                ó>  — |                       | j        d         d         d¦  «         |                      | j        d         d         d¦  «         |                      | j        d         d         d¦  «         |                      | j        d         d         d	¦  «         d S )
NÚt17ri   z3Channel tabs must dynamically update without reloadÚtab1ÚWorldÚtab2ÚZoneÚtab3ÚGuildrk   rn   s    r8   Ú0test_17_language_change_updates_all_channel_tabszNTestI18nEventBusAndReactivity.test_17_language_change_updates_all_channel_tabs*  s�   € Ø�Š˜œ UÔ+¨DÔ1Ð3hÑiÔiÐiØ×Ò˜œ eÔ,¨VÔ4°gÑ>Ô>Ð>Ø×Ò˜œ eÔ,¨VÔ4°fÑ=Ô=Ð=Ø×Ò˜œ eÔ,¨VÔ4°gÑ>Ô>Ð>Ð>Ð>r:   c                óT   — |                       | j        d         d         d¦  «         d S )NÚt18ri   z4Placeholder must update dynamically on locale changerz   rn   s    r8   Ú8test_18_language_change_updates_placeholder_and_cooldownzVTestI18nEventBusAndReactivity.test_18_language_change_updates_placeholder_and_cooldown0  s)   € Ø�Š˜œ UÔ+¨DÔ1Ð3iÑjÔjÐjÐjÐjr:   c                ó¢   — |                       | j        d         d         d¦  «         |                      | j        d         d         d¦  «         d S )NÚt19ri   z0Send button must update text content dynamicallyÚbtnSendr†   rk   rn   s    r8   Ú+test_19_language_change_updates_send_buttonzITestI18nEventBusAndReactivity.test_19_language_change_updates_send_button3  sK   € Ø�Š˜œ UÔ+¨DÔ1Ð3eÑfÔfÐfØ×Ò˜œ eÔ,¨YÔ7¸Ñ@Ô@Ð@Ð@Ð@r:   c                óT   — |                       | j        d         d         d¦  «         d S )NÚt20ri   z+Chat ring buffer history must remain intactrz   rn   s    r8   Ú.test_20_language_change_preserves_chat_historyzLTestI18nEventBusAndReactivity.test_20_language_change_preserves_chat_history7  s)   € Ø�Š˜œ UÔ+¨DÔ1Ð3`ÑaÔaÐaÐaÐar:   c                óT   — |                       | j        d         d         d¦  «         d S )NÚt21ri   z/Null or empty locale must be handled gracefullyrz   rn   s    r8   Ú)test_21_invalid_locale_handled_gracefullyzGTestI18nEventBusAndReactivity.test_21_invalid_locale_handled_gracefully;  s)   € Ø�Š˜œ UÔ+¨DÔ1Ð3dÑeÔeÐeÐeÐer:   c                ó¢   — |                       | j        d         d         d¦  «         |                      | j        d         d         d¦  «         d S )NÚt22ri   z*Locale change must persist to localStorageÚstoredr   rk   rn   s    r8   Ú test_22_localstorage_persistencez>TestI18nEventBusAndReactivity.test_22_localstorage_persistence>  sK   € Ø�Š˜œ UÔ+¨DÔ1Ð3_Ñ`Ô`Ð`Ø×Ò˜œ eÔ,¨XÔ6¸Ñ=Ô=Ð=Ð=Ð=r:   c                óT   — |                       | j        d         d         d¦  «         d S )NÚt23ri   z8Empty or null keys must return fallback without throwingrz   rn   s    r8   Ú$test_23_null_or_empty_key_resiliencezBTestI18nEventBusAndReactivity.test_23_null_or_empty_key_resilienceB  s)   € Ø�Š˜œ UÔ+¨DÔ1Ð3mÑnÔnÐnÐnÐnr:   N)r   rc   )!Ú__name__Ú
__module__Ú__qualname__Ú__doc__r`   Ú__annotations__ra   rb   Úclassmethodrf   rp   rw   r{   r~   r‚   r‡   rŒ   r�   r“   r˜   r›   r�   r¥   r®   rº   r½   rÆ   rÉ   rÍ   rÐ   rÓ   r×   rÚ   © r:   r8   r_   r_   Ï   s‹  € € € € € € Ø]Ð]à €GÐ Ð Ð Ñ Ø%'€IÐ'Ð'Ð'Ñ'Ø%'€IÐ'Ð'Ð'Ñ'àð<ð <ð <ñ „[ð<ðGð Gð Gð GðFð Fð Fð Fðhð hð hð hðqð qð qð qðDð Dð Dð Dð
Bð Bð Bð Bð>ð >ð >ð >ðBð Bð Bð Bðjð jð jð jðAð Að Að AðUð Uð Uð Uð
kð kð kð kðVð Vð Vð Vðeð eð eð eðFð Fð Fð Fðeð eð eð eð?ð ?ð ?ð ?ðkð kð kð kðAð Að Að Aðbð bð bð bðfð fð fð fð>ð >ð >ð >ðoð oð oð oð oð or:   r_   Ú__main__)r   r   r   r   )r   r;   )'rÞ   Ú
__future__r   ÚbuiltinsÚ@py_builtinsÚ_pytest.assertion.rewriteÚ	assertionÚrewriteÚ
@pytest_arrT   r$   rI   ÚsysÚunittestÚpathlibr   Útypingr   r   r   ÚhasattrrO   r   Ú__file__ÚresolveÚparentrK   Ú
CLIENT_DIRrD   rH   rG   rF   r#   r9   r]   ÚTestCaser_   rÛ   Úmainrá   r:   r8   ú<module>rõ      sï  ððð ð #Ð "Ð "Ð "Ð "Ð "à  € € € € € € € € € € € € €€€€Ø 	€	€	€	Ø Ð Ð Ð Ø 
€
€
€
Ø €€€Ø Ð Ð Ð Ð Ð Ø !Ð !Ð !Ð !Ð !Ð !Ð !Ð !Ð !Ð !à
€7ˆ3Œ:�}Ñ%Ô%ð -Ø„J×Ò GÐÑ,Ô,Ð,àˆt�H‰~Œ~×%Ò%Ñ'Ô'Ô.Ô5Ô<€Ø˜HÑ$ xÑ/€
Ø
�tÑ
˜fÑ
$ yÑ
0€Ø˜tÑ# fÑ,Ð/@Ñ@€Ø˜tÑ# fÑ,Ð/EÑE€Ø˜$Ñ Ñ%¨Ñ4€
àFÐFÐF€ðð ð ð ð0RNð RNð RNð RNðjtoð toð toð toð to HÔ$5ñ toô toð toðn ˆzÒÐØ€H„M�O„O€O€O€Oð Ðr:   