§
    &¿j>  ã                  óø  — d Z ddlm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edz  dz  dz                       ¦   «         Zddd„Z G d„ dej        ¦  «        Zedk    r ej        ¦   «          dS dS )aû  
Empirical Challenger Test Suite: Adversarial Verification of Reactive Language Switching in Chat UI.
Tests:
1. Live programmatic switching across 9 languages without page reload (tabs, placeholder, button, badge).
2. Dynamic channel switching and permission placeholder localization.
3. Cooldown badge states localization (locked, waiting, ready).
4. In-place reactive re-render of open rich item tooltips.
5. In-place reactive re-render of historical chat log messages and channel tags.
6. Stress test: 1,000 rapid sequential locale switches (SLA < 500ms, zero listener leak).
7. Invalid, unknown, and falsy locale fallbacks ('xx', null, undefined, '').
8. Listener error isolation and subscription cleanup.
9. Cross-tab storage event reactive synchronization.
é    )ÚannotationsN)ÚPath)ÚAnyÚDictÚclientÚwebappÚjsÚdatazi18n_catalog.jszi18n.jszchat_i18n_catalog.jsÚuiz
chat_ui.jsç      .@Úscript_bodyÚstrÚtimeout_secÚfloatÚreturnúDict[str, Any]c                óz  — dt           › dt          › dt          › dt          › 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 Œ�i S )zEExecute ES module code in Node.js with simulated browser environment.a«	  
import fs from 'fs';
global.Image = class { constructor() { this.src = ''; } };
const elements = new Map();
const createMockEl = (tag, id = '') => {
  let _html = '', _text = '';
  const children = [], classSet = new Set();
  const el = {
    tagName: tag.toUpperCase(), id, dataset: {}, className: '', value: '', disabled: false, placeholder: '',
    get innerHTML() { return _html; }, set innerHTML(v) { _html = v; if (!v) children.length = 0; },
    get textContent() { return _text; }, set textContent(v) { _text = v; },
    classList: {
      add(...c) { c.forEach(x => classSet.add(x)); el.className = Array.from(classSet).join(' '); },
      remove(...c) { c.forEach(x => classSet.delete(x)); el.className = Array.from(classSet).join(' '); },
      contains(c) { return classSet.has(c); },
      toggle(c, f) { if (f ?? !classSet.has(c)) classSet.add(c); else classSet.delete(c); el.className = Array.from(classSet).join(' '); }
    },
    children, appendChild(c) { children.push(c); return c; }, querySelector: () => null,
    querySelectorAll: (s) => (s === '.chat-channel-tab') ? Array.from(elements.values()).filter(e => e.classList.contains('chat-channel-tab')) : [],
    addEventListener: () => {}, focus: () => {}
  };
  return el;
};
const getOrCreate = (id, tag = 'div') => {
  if (!elements.has(id)) elements.set(id, createMockEl(tag, id));
  return elements.get(id);
};
const win = new EventTarget();
win.localStorage = { _s: {}, getItem(k) { return this._s[k] || null; }, setItem(k, v) { this._s[k] = String(v); } };
global.window = win; global.document = {
  getElementById: (id) => getOrCreate(id), createElement: (t) => createMockEl(t), querySelector: () => null,
  querySelectorAll: (s) => (s === '.chat-channel-tab') ? Array.from(elements.values()).filter(e => e.classList.contains('chat-channel-tab')) : [],
  addEventListener: () => {}, readyState: 'complete'
};
global.localStorage = win.localStorage;
for (let i = 1; i <= 8; i++) {
  const tab = createMockEl('button', 'tab-' + i);
  tab.classList.add('chat-channel-tab'); tab.dataset.channelId = String(i); elements.set('tab-' + i, tab);
}
['chat-input', 'chat-send-btn', 'chat-cooldown-badge', 'chat-ticker-channel', 'chat-ticker-text',
 'chat-messages-container', 'modal-item-link-tooltip', 'item-tooltip-name', 'item-tooltip-sub',
 'item-tooltip-icon', 'item-tooltip-crafter', 'item-tooltip-affixes', 'item-tooltip-hmac-badge'].forEach(id => getOrCreate(id));

new Function(fs.readFileSync('z.', 'utf-8'))();
new Function(fs.readFileSync('z&', 'utf-8'))();
await import('file:///z)');
const chatUI = await import('file:///z');
chatUI.initChatUI();
ú
Únodez--input-type=modulez-eTzutf-8)Úcapture_outputÚtextÚencodingÚcwdÚtimeoutr   zNode execution failed (code z):
Ú{Ú}ú[ú])ÚI18N_CATALOG_JSÚI18N_JSÚCHAT_CATALOG_JSÚ
CHAT_UI_JSÚ
subprocessÚrunr   ÚPROJECT_ROOTÚ
returncodeÚRuntimeErrorÚstderrÚreversedÚstdoutÚstripÚsplitÚ
startswithÚendswithÚjsonÚloads)r   r   Ú	bootstrapÚresÚlines        úFC:\Projects\FreeExile\tests\unit\test_challenger_i18n_reactive_chat.pyÚrun_node_evalr5      s|  € ð1õV  /ðW1ð 1õX  'ðY1ð 1õZ 'ð[1ð 1õ\ '1ð]1ð 1ð` ða1ð 1ð 1€Iõd Œ.Ø	Ð&¨¨iÐ8Ø $°½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Ñ#Ô#Ð#Ð#Ð#øØ€Ió    c                  óZ   — e Zd Z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 )ÚTestChallengerI18nReactiveChatzFEmpirical adversarial test suite for live reactive language switching.r   ÚNonec                óX  — t          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         d
         d¦  «         |                      |d         d         d         d¦  «         |                      |d         d         d         d¦  «         |                      |d         d         d         d¦  «         |                      |d         d         d         d¦  «         |                      |d          d         d         d!¦  «         d"S )#zRCalling setLocale across 9 languages updates tabs, button, placeholder reactively.aF  
        const locales = ['en', 'zh', 'ja', 'vi', 'ko', 'th', 'de', 'ru', 'es'];
        const res = {};
        for (const loc of locales) {
          window.FreeExileI18n.setLocale(loc);
          const tabs = Array.from(elements.values()).filter(e => e.classList.contains('chat-channel-tab')).map(t => t.textContent);
          res[loc] = { tabs, sendBtn: getOrCreate('chat-send-btn').textContent,
            placeholder: getOrCreate('chat-input').placeholder, badge: getOrCreate('chat-cooldown-badge').textContent };
        }
        console.log(JSON.stringify(res));
        ÚenÚtabsr   ÚWorldÚsendBtnÚSendzRequires Level 20ÚplaceholderÚbadgeÚLockedÚzhu   ä¸–ç•Œu   å�‘é€�u   20çº§Újaõ   ãƒ¯ãƒ¼ãƒ«ãƒ‰u   é€�ä¿¡u   ãƒ¯ãƒ¼ãƒ«ãƒ‰ãƒ�ãƒ£ãƒƒãƒˆÚviõ   Tháº¿ Giá»›iu   Gá»­iõ   KhÃ³aÚkou   ì›”ë“œÚthu	   à¹‚à¸¥à¸�ÚdeÚWeltÚruu   ÐœÐ¸Ñ€ÚesÚMundoN©r5   ÚassertEqualÚassertIn©Úselfr
   s     r4   Ú6test_01_live_programmatic_locale_switching_9_languageszUTestChallengerI18nReactiveChat.test_01_live_programmatic_locale_switching_9_languagesc   su  € åð 
ñ 
ô 
ˆð 	×Ò˜˜dœ FÔ+¨AÔ.°Ñ8Ô8Ð8Ø×Ò˜˜dœ IÔ.°Ñ7Ô7Ð7Ø�ŠÐ)¨4°¬:°mÔ+DÑEÔEÐEØ×Ò˜˜dœ GÔ,¨hÑ7Ô7Ð7à×Ò˜˜dœ FÔ+¨AÔ.°Ñ9Ô9Ð9Ø×Ò˜˜dœ IÔ.°Ñ9Ô9Ð9Ø�Š�g˜t Dœz¨-Ô8Ñ9Ô9Ð9à×Ò˜˜dœ FÔ+¨AÔ.°Ñ?Ô?Ð?Ø×Ò˜˜dœ IÔ.°Ñ9Ô9Ð9Ø�ŠÐ0°$°t´*¸]Ô2KÑLÔLÐLà×Ò˜˜dœ FÔ+¨AÔ.°Ñ?Ô?Ð?Ø×Ò˜˜dœ IÔ.°Ñ8Ô8Ð8Ø×Ò˜˜dœ GÔ,¨gÑ6Ô6Ð6à×Ò˜˜dœ FÔ+¨AÔ.°Ñ9Ô9Ð9Ø×Ò˜˜dœ FÔ+¨AÔ.°Ñ<Ô<Ð<Ø×Ò˜˜dœ FÔ+¨AÔ.°Ñ7Ô7Ð7Ø×Ò˜˜dœ FÔ+¨AÔ.°Ñ9Ô9Ð9Ø×Ò˜˜dœ FÔ+¨AÔ.°Ñ8Ô8Ð8Ð8Ð8r6   c                ó  — t          d¦  «        }|                      |d         d¦  «         |                      |d         d¦  «         |                      d|d         ¦  «         |                      d|d	         ¦  «         d
S )zISwitching channels produces localized permissions and input placeholders.aˆ  
        window.FreeExileI18n.setLocale('en'); chatUI.switchChannel(2);
        const zonePlaceholderEn = getOrCreate('chat-input').placeholder;
        window.FreeExileI18n.setLocale('vi');
        const zonePlaceholderVi = getOrCreate('chat-input').placeholder;
        chatUI.switchChannel(6); window.FreeExileI18n.setLocale('en');
        const sysPlaceholderEn = getOrCreate('chat-input').placeholder;
        window.FreeExileI18n.setLocale('zh');
        const sysPlaceholderZh = getOrCreate('chat-input').placeholder;
        console.log(JSON.stringify({ zonePlaceholderEn, zonePlaceholderVi, sysPlaceholderEn, sysPlaceholderZh }));
        ÚzonePlaceholderEnzEnter message [Zone]...ÚzonePlaceholderViu    Nháº­p tin nháº¯n [Khu Vá»±c]...zSystem channel is reservedÚsysPlaceholderEnu'   ç³»ç»Ÿé¢‘é�“ä»…ä¾›æœ�åŠ¡å™¨å…¬å‘Šä½¿ç”¨ÚsysPlaceholderZhNrP   rS   s     r4   Ú-test_02_dynamic_channel_switching_placeholderzLTestChallengerI18nReactiveChat.test_02_dynamic_channel_switching_placeholder‡   sŽ   € åð 
ñ 
ô 
ˆð 	×Ò˜Ð1Ô2Ð4MÑNÔNÐNØ×Ò˜Ð1Ô2Ð4VÑWÔWÐWØ�ŠÐ2°DÐ9KÔ4LÑMÔMÐMØ�ŠÐ?ÀÐFXÔAYÑZÔZÐZÐZÐZr6   c                óÌ   — t          d¦  «        }|                      |d         d¦  «         |                      |d         d¦  «         |                      |d         d¦  «         dS )	zJCooldown badges translate reactively for both locked and countdown states.aÎ  
        chatUI.switchChannel(6); window.FreeExileI18n.setLocale('en');
        const lockedEn = getOrCreate('chat-cooldown-badge').textContent;
        window.FreeExileI18n.setLocale('vi');
        const lockedVi = getOrCreate('chat-cooldown-badge').textContent;
        window.FreeExileI18n.setLocale('ja');
        const lockedJa = getOrCreate('chat-cooldown-badge').textContent;
        console.log(JSON.stringify({ lockedEn, lockedVi, lockedJa }));
        ÚlockedEnrB   ÚlockedVirH   ÚlockedJau	   ãƒ­ãƒƒã‚¯N©r5   rQ   rS   s     r4   Ú*test_03_cooldown_badge_states_localizationzITestChallengerI18nReactiveChat.test_03_cooldown_badge_states_localization™   so   € åð ñ ô ˆð 	×Ò˜˜jÔ)¨8Ñ4Ô4Ð4Ø×Ò˜˜jÔ)¨7Ñ3Ô3Ð3Ø×Ò˜˜jÔ)¨;Ñ7Ô7Ð7Ð7Ð7r6   c                ót  — t          d¦  «        }|                      d|d         ¦  «         |                      |d         d¦  «         |                      d|d         ¦  «         |                      |d         d	¦  «         |                      d
|d         ¦  «         |                      d|d         ¦  «         dS )zFOpen item tooltip dynamically re-renders in-place when locale changes.u”  
        chatUI.openItemTooltip({ itemUuid: 'uuid_sword_1', itemName: 'Báº¡ch Há»• Tháº§n Ä�ao', rarity: 4, element: 1,
          quality: 20, itemLevel: 85, crafterName: 'ThiÃªn CÃ´ng',
          affixes: [{ statName: 'SÃ¡t ThÆ°Æ¡ng', statValue: 100, isImplicit: true }], isVerified: true });
        window.FreeExileI18n.setLocale('vi');
        const viSub = getOrCreate('item-tooltip-sub').textContent, viBadge = getOrCreate('item-tooltip-hmac-badge').textContent;
        window.FreeExileI18n.setLocale('en');
        const enSub = getOrCreate('item-tooltip-sub').textContent, enBadge = getOrCreate('item-tooltip-hmac-badge').textContent;
        window.FreeExileI18n.setLocale('ja');
        const jaSub = getOrCreate('item-tooltip-sub').textContent, jaBadge = getOrCreate('item-tooltip-hmac-badge').textContent;
        console.log(JSON.stringify({ viSub, viBadge, enSub, enBadge, jaSub, jaBadge }));
        u   Tháº§n Pháº©mÚviSubÚviBadgeu   âœ“ HMAC XÃ¡c Thá»±cÚDivineÚenSubÚenBadgeu   âœ“ HMAC Verifiedu   ç¥žå“�ÚjaSubÚHMACÚjaBadgeN)r5   rR   rQ   rS   s     r4   Ú2test_04_open_item_tooltip_in_place_reactive_updatezQTestChallengerI18nReactiveChat.test_04_open_item_tooltip_in_place_reactive_update¨   s´   € åð ñ ô ˆð 	�Š�o t¨G¤}Ñ5Ô5Ð5Ø×Ò˜˜iœÐ*@ÑAÔAÐAØ�Š�h  W¤Ñ.Ô.Ð.Ø×Ò˜˜iœÐ*=Ñ>Ô>Ð>Ø�Š�h  W¤Ñ.Ô.Ð.Ø�Š�f˜d 9œoÑ.Ô.Ð.Ð.Ð.r6   c                óÆ   — t          d¦  «        }|                      |d         ¦  «         |                      |d         ¦  «         |                      |d         ¦  «         dS )zLHistorical chat messages re-render channel tags reactively on locale switch.u  
        chatUI.addChatMessage({ messageId: 'msg_h1', senderName: 'DaoKhach', channel: 1,
          displayContent: 'Reactive chat check', linkedItems: [], timestampMs: 1700000000000 });
        const container = getOrCreate('chat-messages-container');
        window.FreeExileI18n.setLocale('vi'); const viHtml = container.children.map(c => c.innerHTML).join('');
        window.FreeExileI18n.setLocale('en'); const enHtml = container.children.map(c => c.innerHTML).join('');
        window.FreeExileI18n.setLocale('zh'); const zhHtml = container.children.map(c => c.innerHTML).join('');
        console.log(JSON.stringify({ hasTheGioi: viHtml.includes('[Tháº¿ Giá»›i]'),
          hasWorld: enHtml.includes('[World]'), hasShijie: zhHtml.includes('[ä¸–ç•Œ]') }));
        Ú
hasTheGioiÚhasWorldÚ	hasShijieN)r5   Ú
assertTruerS   s     r4   Ú,test_05_chat_log_messages_reactive_re_renderzKTestChallengerI18nReactiveChat.test_05_chat_log_messages_reactive_re_render½   sc   € åð 	ñ 	ô 	ˆð 	�Š˜˜\Ô*Ñ+Ô+Ð+Ø�Š˜˜ZÔ(Ñ)Ô)Ð)Ø�Š˜˜[Ô)Ñ*Ô*Ð*Ð*Ð*r6   c                óN  — t          d¦  «        }|                      |d         dd¦  «         |                      |d         |d         d¦  «         |                      |d         d	d
¦  «         |                      |d         d¦  «         |                      |d         d¦  «         dS )zLStress testing 1,000 rapid sequential locale switches satisfies < 500ms SLA.a2  
        const initialListeners = window.FreeExileI18n.listeners.length;
        let eventCount = 0;
        window.addEventListener('freeexile:localeChanged', () => { eventCount++; });
        const locales = ['en', 'zh', 'ja', 'vi', 'ko', 'th', 'de', 'ru', 'es'];
        const t0 = performance.now();
        for (let i = 0; i < 1000; i++) window.FreeExileI18n.setLocale(locales[i % locales.length]);
        const elapsedMs = performance.now() - t0, finalListeners = window.FreeExileI18n.listeners.length;
        const finalLocale = window.FreeExileI18n.getLocale();
        const tab1 = Array.from(elements.values()).filter(e => e.classList.contains('chat-channel-tab'))[0].textContent;
        console.log(JSON.stringify({ elapsedMs, initialListeners, finalListeners, eventCount, finalLocale, tab1 }));
        Ú	elapsedMsg     @@z(1,000 switches must execute within 500msÚinitialListenersÚfinalListenerszZero listener leak expectedÚ
eventCountiè  z Must dispatch 1,000 CustomEventsÚfinalLocaler;   Útab1r=   N)r5   Ú
assertLessrQ   rS   s     r4   Ú1test_06_rapid_sequential_switching_stress_harnesszPTestChallengerI18nReactiveChat.test_06_rapid_sequential_switching_stress_harnessÍ   s²   € åð ñ ô ˆð 	�Š˜˜[Ô)¨5Ð2\Ñ]Ô]Ð]Ø×Ò˜Ð0Ô1°4Ð8HÔ3IÐKhÑiÔiÐiØ×Ò˜˜lÔ+¨TÐ3UÑVÔVÐVØ×Ò˜˜mÔ,¨dÑ3Ô3Ð3Ø×Ò˜˜fœ wÑ/Ô/Ð/Ð/Ð/r6   c                ó  — t          d¦  «        }|                      |d         d¦  «         |                      |d         d¦  «         |                      |d         |d         ¦  «         |                      |d         d	¦  «         d
S )zLUnknown and falsy locales fall back gracefully without unhandled exceptions.aS  
        window.FreeExileI18n.setLocale('en'); window.FreeExileI18n.setLocale('xx');
        const tabsUnknown = Array.from(elements.values()).filter(e => e.classList.contains('chat-channel-tab')).map(t => t.textContent);
        const sendBtnUnknown = getOrCreate('chat-send-btn').textContent, locBeforeFalsy = window.FreeExileI18n.getLocale();
        window.FreeExileI18n.setLocale(null); window.FreeExileI18n.setLocale(undefined); window.FreeExileI18n.setLocale('');
        const locAfterFalsy = window.FreeExileI18n.getLocale();
        window.FreeExileI18n.setLocale('vi');
        const tab1Recovered = Array.from(elements.values()).filter(e => e.classList.contains('chat-channel-tab'))[0].textContent;
        console.log(JSON.stringify({ tab0Unknown: tabsUnknown[0], sendBtnUnknown, locBeforeFalsy, locAfterFalsy, tab1Recovered }));
        Útab0Unknownr=   ÚsendBtnUnknownr?   ÚlocBeforeFalsyÚlocAfterFalsyÚtab1RecoveredrG   Nr`   rS   s     r4   Ú,test_07_invalid_and_unknown_locale_fallbackszKTestChallengerI18nReactiveChat.test_07_invalid_and_unknown_locale_fallbacksá   s‘   € åð 	ñ 	ô 	ˆð 	×Ò˜˜mÔ,¨gÑ6Ô6Ð6Ø×Ò˜Ð.Ô/°Ñ8Ô8Ð8Ø×Ò˜Ð.Ô/°°oÔ1FÑGÔGÐGØ×Ò˜˜oÔ.°Ñ?Ô?Ð?Ð?Ð?r6   c                óÎ   — t          d¦  «        }|                      |d         d¦  «         |                      |d         d¦  «         |                      |d         dd¦  «         dS )	zVSubscriber throwing an exception does not halt other listeners, and unsubscribe works.aB  
        let secondRan = false;
        window.FreeExileI18n.subscribe(() => { throw new Error('Faulty listener error'); });
        window.FreeExileI18n.subscribe(() => { secondRan = true; });
        let unsubCounter = 0;
        const unsub = window.FreeExileI18n.subscribe(() => { unsubCounter++; });
        window.FreeExileI18n.setLocale('en'); const countBeforeUnsub = unsubCounter;
        unsub(); window.FreeExileI18n.setLocale('zh'); const countAfterUnsub = unsubCounter;
        console.log(JSON.stringify({ secondRan, countBeforeUnsub, countAfterUnsub }));
        Ú	secondRanz4Subsequent listener must execute despite prior throwÚcountBeforeUnsubé   ÚcountAfterUnsubz(Unsubscribed listener must not be calledN)r5   rp   rQ   rS   s     r4   Ú.test_08_listener_resilience_and_unsubscriptionzMTestChallengerI18nReactiveChat.test_08_listener_resilience_and_unsubscriptionò   ss   € åð 	ñ 	ô 	ˆð 	�Š˜˜[Ô)Ð+aÑbÔbÐbØ×Ò˜Ð0Ô1°1Ñ5Ô5Ð5Ø×Ò˜Ð/Ô0°!Ð5_Ñ`Ô`Ð`Ð`Ð`r6   c                ó”   — t          d¦  «        }|                      |d         d¦  «         |                      |d         d¦  «         dS )zDStorage event from peer browser tab triggers locale synchronization.aÎ  
        window.FreeExileI18n.setLocale('vi');
        if (window.dispatchEvent) {
          const ev = new Event('storage');
          ev.key = 'freeexile_locale'; ev.newValue = 'ja'; window.dispatchEvent(ev);
        }
        const locale = window.FreeExileI18n.getLocale();
        const tab1 = Array.from(elements.values()).filter(e => e.classList.contains('chat-channel-tab'))[0].textContent;
        console.log(JSON.stringify({ locale, tab1 }));
        ÚlocalerD   rx   rE   Nr`   rS   s     r4   Ú$test_09_storage_event_cross_tab_synczCTestChallengerI18nReactiveChat.test_09_storage_event_cross_tab_sync  sR   € åð 	ñ 	ô 	ˆð 	×Ò˜˜hœ¨Ñ.Ô.Ð.Ø×Ò˜˜fœ ~Ñ6Ô6Ð6Ð6Ð6r6   N)r   r9   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__rU   r[   ra   rk   rq   rz   r�   r‡   rŠ   © r6   r4   r8   r8   `   sÛ   € € € € € ØPÐPð"9ð "9ð "9ð "9ðH[ð [ð [ð [ð$8ð 8ð 8ð 8ð/ð /ð /ð /ð*+ð +ð +ð +ð 0ð 0ð 0ð 0ð(@ð @ð @ð @ð"að að að að 7ð 7ð 7ð 7ð 7ð 7r6   r8   Ú__main__)r   )r   r   r   r   r   r   )rŽ   Ú
__future__r   r/   r#   ÚunittestÚpathlibr   Útypingr   r   Ú__file__ÚresolveÚparentr%   Ú
WEBAPP_DIRÚas_posixr   r    r!   r"   r5   ÚTestCaser8   r‹   Úmainr�   r6   r4   ú<module>rœ      s”  ððð ð #Ð "Ð "Ð "Ð "Ð "à €€€Ø Ð Ð Ð Ø €€€Ø Ð Ð Ð Ð Ð Ø Ð Ð Ð Ð Ð Ð Ð àˆt�H‰~Œ~×%Ò%Ñ'Ô'Ô.Ô5Ô<€Ø˜HÑ$ xÑ/€
Ø Ñ$ vÑ-Ð0AÑA×KÒKÑMÔM€Ø˜Ñ˜vÑ%¨	Ñ1×
;Ò
;Ñ
=Ô
=€Ø Ñ$ vÑ-Ð0FÑF×PÒPÑRÔR€Ø˜4Ñ $Ñ&¨Ñ5×?Ò?ÑAÔA€
ð>ð >ð >ð >ð >ðBo7ð o7ð o7ð o7ð o7 XÔ%6ñ o7ô o7ð o7ðd ˆzÒÐØ€H„M�O„O€O€O€Oð Ðr6   