§
    Ì½jYE  ã                  óx  — 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mZ  ee¦  «                             ¦   «         j        j        j        Zedz  dz  Zedz  Zed	z  d
z  Zed	z  dz  dz  Zedz  dz  dz  dz  Z G d„ dej        ¦  «        Zedk    r ej        ¦   «          dS dS )z÷
Unit Test Suite for FreeExile WebApp Multi-Channel Chat UI & 2.5D Item Tooltip Integration.
Verifies DOM elements, channel keys, FIFO ring buffer, rich item tag parsing,
anti-spam cooldowns, 9-language localization, and key navigation in WebApp.
é    )ÚannotationsN)ÚPath)ÚAnyÚDictÚListÚclientÚwebappz
index.htmlÚjszmain.jsÚuiz
chat_ui.jsÚsrcÚchatzChatManager.tsc                  óÚ   — e Zd ZdZedd„¦   «         Zdd„Z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S ) ÚTestWebappChatUIzJUnit tests for FreeExile WebApp Chat Dock, Ring Buffer, and Item Tooltips.ÚreturnÚNonec                ó  — t                                d¬¦  «        | _        t                               d¬¦  «        | _        t
                               d¬¦  «        | _        t                               d¬¦  «        | _        d S )Núutf-8)Úencoding)	Ú
INDEX_HTMLÚ	read_textÚhtmlÚMAIN_JSÚmain_jsÚ
CHAT_UI_JSÚ
chat_ui_jsÚCHAT_MANAGER_TSÚchat_mgr_ts)Úclss    ú7C:\Projects\FreeExile\tests\unit\test_webapp_chat_ui.pyÚ
setUpClasszTestWebappChatUI.setUpClass   sf   € å×'Ò'°Ð'Ñ9Ô9ˆŒÝ×'Ò'°Ð'Ñ9Ô9ˆŒÝ#×-Ò-°wÐ-Ñ?Ô?ˆŒÝ)×3Ò3¸WÐ3ÑEÔEˆŒˆˆó    ÚscriptÚstrr   c                óF  — t          t          ¦  «                             dd¦  «        }d|› d|› d�}t          j        ddg|                     d¦  «        d	d	¬
¦  «        }|j                             d¦  «                             ¦   «         }|rt          j
        |¦  «        ndS )z;Helper to evaluate JS snippets using chat_ui.js in Node.js.ú\ú/z
        import('file:///z$').then(chatModule => {
            zl
        }).catch(err => {
            console.error(err);
            process.exit(1);
        });
        Únodez--input-type=moduler   T)ÚinputÚcapture_outputÚcheckN)r#   r   ÚreplaceÚ
subprocessÚrunÚencodeÚstdoutÚdecodeÚstripÚjsonÚloads)Úselfr"   Úescaped_pathÚfull_scriptÚresÚouts         r   Ú_eval_chat_ui_nodez#TestWebappChatUI._eval_chat_ui_node"   sµ   € å�:‘”×.Ò.¨t°SÑ9Ô9ˆðØ%ðð àðð ð ˆõ ŒnØÐ*Ð+Ø×$Ò$ WÑ-Ô-ØØð	
ñ 
ô 
ˆð Œj×Ò Ñ(Ô(×.Ò.Ñ0Ô0ˆØ"%Ð/�tŒz˜#‰Œˆ¨4Ð/r!   c                 ó   — dS )NaB  
        const _createEl = (tag) => ({
            tagName: tag, id: '', className: '', innerHTML: '', textContent: '', children: [],
            classList: { _c: new Set(), add(c){this._c.add(c);}, remove(c){this._c.delete(c);}, contains(c){return this._c.has(c);} },
            appendChild(c){ this.children.push(c); return c; }, querySelectorAll(){ return []; }, addEventListener(){}
        });
        const elements = {};
        global.document = { getElementById: (id) => elements[id] || (elements[id] = _createEl('div')), createElement: (t) => _createEl(t) };
        © r;   r!   r   Ú_mock_dom_jszTestWebappChatUI._mock_dom_js6   s   € ðð r!   c                óN   — dD ]!}|                       d|› d�| j        ¦  «         Œ"dS )z8Chat dock and minimized ticker must exist in index.html.)
z	chat-dockzchat-minimized-barzchat-ticker-channelzchat-ticker-textúchat-expanded-panelzchat-messages-containerz
chat-inputzchat-send-btnzchat-cooldown-badgezchat-btn-collapseúid="ú"N©ÚassertInr   ©r4   Úeids     r   Ú'test_01_chat_dock_mounted_in_index_htmlz8TestWebappChatUI.test_01_chat_dock_mounted_in_index_htmlD   sA   € ð@ð 	4ð 	4ˆCð �MŠM˜- ˜-˜-˜-¨¬Ñ3Ô3Ð3Ð3ð	4ð 	4r!   c           	     ó¨  — t          j        d| j        ¦  «        }d„ |D ¦   «         }|                      t	          |¦  «        g d¢¦  «         dddddd	d
ddœ}|D ]x\  }}|                     ¦   «         }|                      ||t          |¦  «                 ¦  «         |                      t          | 	                    ¦   «         ¦  «        dd¦  «         ŒydS )zLAll 8 channel tabs must be present with valid data-channel-id in index.html.z9<button\b[^>]*data-channel-id="(\d+)"[^>]*>(.*?)</button>c                ó8   — g | ]}t          |d          ¦  «        ‘ŒS )r   )Úint)Ú.0Úts     r   ú
<listcomp>zQTestWebappChatUI.test_02_all_eight_channel_tabs_in_index_html.<locals>.<listcomp>N   s"   € Ð/Ð/Ð/ Q•s˜1˜Qœ4‘y”yÐ/Ð/Ð/r!   ©é   é   é   é   é   é   é   é   u   Tháº¿ Giá»›iu	   Khu Vá»±cu
   Bang Há»™iu   Ä�á»™i NgÅ©u
   Máº­t ThÆ°u   Há»‡ Thá»‘ngu   ChiÃªu Má»™u   GÃ³p Ã�rN   zTab label must be <= 2 wordsN)
ÚreÚfindallr   ÚassertEqualÚsortedr1   rH   ÚassertLessEqualÚlenÚsplit)r4   ÚtabsÚchannel_idsÚexpected_labelsÚcidÚlabelÚclean_labels          r   Ú,test_02_all_eight_channel_tabs_in_index_htmlz=TestWebappChatUI.test_02_all_eight_channel_tabs_in_index_htmlK   sñ   € åŒzÐVÐX\ÔXaÑbÔbˆØ/Ð/¨$Ð/Ñ/Ô/ˆØ×Ò� Ñ,Ô,Ð.FÐ.FÐ.FÑGÔGÐGð  +°,À=Ø °=ÀYð
ð 
ˆð ð 	^ð 	^‰JˆC�ØŸ+š+™-œ-ˆKØ×Ò˜[¨/½#¸c¹(¼(Ô*CÑDÔDÐDØ× Ò ¥ [×%6Ò%6Ñ%8Ô%8Ñ!9Ô!9¸1Ð>\Ñ]Ô]Ð]Ð]ð	^ð 	^r!   c                ó„   — dD ]!}|                       d|› d�| j        ¦  «         Œ"|                       d| j        ¦  «         dS )uI   Rich 2.5D Cá»• VÃµ item link tooltip modal must be mounted in index.html.)úmodal-item-link-tooltipzitem-tooltip-iconzitem-tooltip-namezitem-tooltip-subzitem-tooltip-affixeszitem-tooltip-crafterzitem-tooltip-hmac-badgezbtn-close-item-tooltipr?   r@   õ   âœ“ HMAC XÃ¡c Thá»±cNrA   rC   s     r   Ú(test_03_item_tooltip_modal_in_index_htmlz9TestWebappChatUI.test_03_item_tooltip_modal_in_index_htmlZ   sR   € ðyð 	4ð 	4ˆCà�MŠM˜- ˜-˜-˜-¨¬Ñ3Ô3Ð3Ð3Ø�ŠÐ,¨d¬iÑ8Ô8Ð8Ð8Ð8r!   c                ó<   — |                       d| j        ¦  «         dS )z@chat_ui.js must be imported as a Native ES Module in index.html.z6<script type="module" src="js/ui/chat_ui.js"></script>NrA   ©r4   s    r   Ú&test_04_script_inclusion_in_index_htmlz7TestWebappChatUI.test_04_script_inclusion_in_index_htmla   s!   € à�ŠÐNÐPTÔPYÑZÔZÐZÐZÐZr!   c           	     ó  — |                       d¦  «        }|                      t          |¦  «        d¦  «         dddddddd	d
œ}|                     ¦   «         D ]4\  }}|                      |t	          |¦  «                 d         |¦  «         Œ5dS )zNCHANNELS constant in chat_ui.js must define all 8 channels with SLA cooldowns.z1console.log(JSON.stringify(chatModule.CHANNELS));rT   i˜:  i¸  éô  éÈ   r   i'  iˆ  rL   Ú
cooldownMsN)r9   rW   rZ   Úitemsr#   )r4   ÚdataÚexpected_cdr_   Úcds        r   Ú'test_05_channel_constants_and_cooldownsz8TestWebappChatUI.test_05_channel_constants_and_cooldownsg   s�   € à×&Ò&Ð'ZÑ[Ô[ˆØ×Ò�˜T™œ AÑ&Ô&Ð&Ø D¨S°S¸SÀQÈ5ÐUYÐZÐZˆØ"×(Ò(Ñ*Ô*ð 	?ð 	?‰GˆC�Ø×Ò˜T¥# c¡(¤(œ^¨LÔ9¸2Ñ>Ô>Ð>Ð>ð	?ð 	?r!   c                óØ   — |                       d¦  «        }|                      |d         d¦  «         |                      |d         d¦  «         |                      |d         d¦  «         dS )	z>Ring buffer must cap channel history to strictly 100 messages.a˜  
            for (let i = 1; i <= 120; i++) {
                chatModule.addChatMessage({
                    messageId: `msg_${i}`,
                    senderName: `User_${i}`,
                    channel: 1,
                    displayContent: `Message content ${i}`,
                    linkedItems: [],
                    timestampMs: Date.now() + i
                });
            }
            const history = chatModule.getChannelHistory(1);
            console.log(JSON.stringify({
                total: history.length,
                firstId: history[0].messageId,
                lastId: history[history.length - 1].messageId
            }));
        Útotaléd   ÚfirstIdÚmsg_21ÚlastIdÚmsg_120N©r9   rW   ©r4   ro   s     r   Ú%test_06_fifo_ring_buffer_100_capacityz6TestWebappChatUI.test_06_fifo_ring_buffer_100_capacityo   sr   € à×&Ò&ð (ñ ô ˆð$ 	×Ò˜˜gœ¨Ñ,Ô,Ð,Ø×Ò˜˜iœ¨(Ñ3Ô3Ð3Ø×Ò˜˜hœ¨Ñ3Ô3Ð3Ð3Ð3r!   c                ó–  — |                       d¦  «        }|                      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S )zNparseItemTags must parse single and multiple [item:uuid:sig:name:rarity] tags.u  
            const content = 'Khoe kiáº¿m [item:uuid-101:sig-aaa:Thanh Phong Kiáº¿m:4] vÃ  giÃ¡p [item:uuid-102:sig-bbb:Huyá»�n Thiáº¿t GiÃ¡p:3]!';
            const parsed = chatModule.parseItemTags(content);
            console.log(JSON.stringify(parsed));
        rN   r   ÚitemUuidzuuid-101Ú	signaturezsig-aaaÚitemNameu   Thanh Phong Kiáº¿mÚrarityrP   rM   zuuid-102zsig-bbbu   Huyá»�n Thiáº¿t GiÃ¡prO   N)r9   rW   rZ   r{   s     r   Útest_07_rich_item_tag_parsingz.TestWebappChatUI.test_07_rich_item_tag_parsing‡   s:  € à×&Ò&ð (ñ ô ˆð
 	×Ò�˜T™œ AÑ&Ô&Ð&Ø×Ò˜˜aœ Ô,¨jÑ9Ô9Ð9Ø×Ò˜˜aœ Ô-¨yÑ9Ô9Ð9Ø×Ò˜˜aœ Ô,Ð.BÑCÔCÐCØ×Ò˜˜aœ Ô*¨AÑ.Ô.Ð.à×Ò˜˜aœ Ô,¨jÑ9Ô9Ð9Ø×Ò˜˜aœ Ô-¨yÑ9Ô9Ð9Ø×Ò˜˜aœ Ô,Ð.EÑFÔFÐFØ×Ò˜˜aœ Ô*¨AÑ.Ô.Ð.Ð.Ð.r!   c                ó    — |                       d¦  «        }|                      |d         d¦  «         |                      |d         d¦  «         dS )z>formatItemTag formats tag from arguments and snapshot objects.u�  
            const tag1 = chatModule.formatItemTag('Huyáº¿t Ä�ao', 5, 'uuid-777', 'sig-888');
            const tag2 = chatModule.formatItemTag({
                itemUuid: 'uuid-999',
                signature: 'sig-999',
                itemName: 'ThÃ¡i Cá»• Tháº§n ThÆ°Æ¡ng',
                rarity: 5
            });
            console.log(JSON.stringify({ tag1, tag2 }));
        Útag1u&   [item:uuid-777:sig-888:Huyáº¿t Ä�ao:5]Útag2u4   [item:uuid-999:sig-999:ThÃ¡i Cá»• Tháº§n ThÆ°Æ¡ng:5]Nrz   r{   s     r   Útest_08_item_tag_formattingz,TestWebappChatUI.test_08_item_tag_formatting™   sZ   € à×&Ò&ð 	(ñ 	ô 	ˆð 	×Ò˜˜fœÐ'OÑPÔPÐPØ×Ò˜˜fœÐ']Ñ^Ô^Ð^Ð^Ð^r!   c                ó  — |                       d¦  «        }|                      d|¦  «         |                      d|¦  «         |                      d|¦  «         |                      d|¦  «         |                      d|¦  «         dS )zTrenderMessageHtml must escape raw HTML entities while rendering clickable item tags.uÔ   
            const unsafe = '<script>alert(1)</script> [item:u1:s1:Báº£o Kiáº¿m:4] & "test"';
            const html = chatModule.renderMessageHtml(unsafe);
            console.log(JSON.stringify(html));
        z<script>z&lt;script&gt;zchat-item-linkzdata-uuid="u1"zdata-sig="s1"N)r9   ÚassertNotInrB   r{   s     r   Ú+test_09_html_rendering_and_xss_sanitizationz<TestWebappChatUI.test_09_html_rendering_and_xss_sanitization¨   s�   € à×&Ò&ð (ñ ô ˆð
 	×Ò˜ TÑ*Ô*Ð*Ø�ŠÐ&¨Ñ-Ô-Ð-Ø�ŠÐ&¨Ñ-Ô-Ð-Ø�ŠÐ&¨Ñ-Ô-Ð-Ø�Š�o tÑ,Ô,Ð,Ð,Ð,r!   c                ó¦  — |                       d¦  «        }g d¢}g d¢}|D ]°}|                      ||d|› d�¦  «         |D ]�}|                      |||         d|› d|› �¦  «         t          ||         |                              ¦   «                              ¦   «         ¦  «        }|                      |dd|› d	|› d
||         |         › �¦  «         Œ‘Œ±dS )zHCHANNEL_I18N must provide 8-channel translations across all 9 languages.zK
            console.log(JSON.stringify(chatModule.CHANNEL_I18N));
        )	ÚviÚenÚzhÚjaÚkoÚthÚdeÚruÚes)ÚworldÚzoneÚguildÚpartyÚwhisperÚsystemÚrecruitÚfeedbackzLocale z missing in CHANNEL_I18NzChannel z missing in locale rN   z in z exceeds 2 words: N)r9   rB   rZ   r1   r[   rY   )r4   ro   ÚlocalesÚrequired_keysÚlocÚkÚ
word_counts          r   Ú'test_10_multilingual_channel_dictionaryz8TestWebappChatUI.test_10_multilingual_channel_dictionary·   s$  € à×&Ò&ð (ñ ô ˆð IÐHÐHˆØgÐgÐgˆØð 	mð 	mˆCØ�MŠM˜#˜tÐ%L¨sÐ%LÐ%LÐ%LÑMÔMÐMØ"ð mð m�Ø—’˜a  c¤Ð,R°qÐ,RÐ,RÈSÐ,RÐ,RÑSÔSÐSÝ   c¤¨1¤×!3Ò!3Ñ!5Ô!5×!;Ò!;Ñ!=Ô!=Ñ>Ô>�
Ø×$Ò$ Z°Ð4k¸qÐ4kÐ4kÀcÐ4kÐ4kÐ]aÐbeÔ]fÐghÔ]iÐ4kÐ4kÑlÔlÐlÐlðmð	mð 	mr!   c                óD  — t          j        d| j        ¦  «        }|D ]‚}t          j        dd|¦  «                             ¦   «         }|sŒ-|                     ¦   «         }|                      t          |¦  «        dd|› �¦  «         |                      d|d|› �¦  «         ŒƒdS )	zKAll buttons in chat UI must adhere to <= 2 words and zero bilingual parens.z9<button\b[^>]*class="[^"]*chat-[^"]*"[^>]*>(.*?)</button>z<[^>]+>Ú rN   z"Chat button text exceeds 2 words: z (z,Chat button text contains bilingual parens: N)	rU   rV   r   Úsubr1   r[   rY   rZ   rˆ   )r4   ÚbtnsÚbÚcleanÚwordss        r   Ú/test_11_chat_buttons_microcopy_and_no_bilingualz@TestWebappChatUI.test_11_chat_buttons_microcopy_and_no_bilingualÅ   s¹   € åŒzÐVÐX\ÔXaÑbÔbˆØð 	bð 	bˆAÝ”F˜: r¨1Ñ-Ô-×3Ò3Ñ5Ô5ˆEØð ØØ—K’K‘M”MˆEØ× Ò ¥ U¡¤¨QÐ0\ÐUZÐ0\Ð0\Ñ]Ô]Ð]Ø×Ò˜T 5Ð*`ÐY^Ð*`Ð*`ÑaÔaÐaÐað	bð 	br!   c                ó  — |                       d| j        ¦  «         |                       d| j        ¦  «         |                       d| j        ¦  «         |                       d| j        ¦  «         |                       d| j        ¦  «         dS )z>main.js must import initChatUI and wire Enter and Escape keys.z-import { initChatUI } from './ui/chat_ui.js';zinitChatUI();zkey === 'ENTER'rd   r>   N)rB   r   rh   s    r   Ú+test_12_main_js_wires_chat_enter_and_escapez<TestWebappChatUI.test_12_main_js_wires_chat_enter_and_escapeÒ   sz   € à�ŠÐEÀtÄ|ÑTÔTÐTØ�Š�o t¤|Ñ4Ô4Ð4Ø�ŠÐ'¨¬Ñ6Ô6Ð6Ø�ŠÐ/°´Ñ>Ô>Ð>Ø�ŠÐ+¨T¬\Ñ:Ô:Ð:Ð:Ð:r!   c                ó  — |                       d| j        ¦  «         |                       d| j        ¦  «         |                       d| j        ¦  «         |                       d| j        ¦  «         |                       d| j        ¦  «         dS )zSChatManager.ts ClientItemSnapshot must include itemLevel, createdAtMs, and affixes.zitemLevel: number;zcreatedAtMs: number;zaffixes: ClientItemAffix[];ÚsetQueryItemSnapshotCallbackÚonTappedItemLinkN)rB   r   rh   s    r   Ú0test_13_chat_manager_ts_has_full_snapshot_fieldszATestWebappChatUI.test_13_chat_manager_ts_has_full_snapshot_fieldsÚ   s€   € à�ŠÐ*¨DÔ,<Ñ=Ô=Ð=Ø�ŠÐ,¨dÔ.>Ñ?Ô?Ð?Ø�ŠÐ3°TÔ5EÑFÔFÐFØ�ŠÐ4°dÔ6FÑGÔGÐGØ�ŠÐ(¨$Ô*:Ñ;Ô;Ð;Ð;Ð;r!   c                óØ   — |                       d¦  «        }|                      |d         d¦  «         |                      |d         d¦  «         |                      |d         d¦  «         dS )	zPrenderMessageHtml must escape quotes preventing attribute breakout on data-name.a  
            const payload = '[item:u1:s1:Item" autofocus onfocus="alert(1)":1] & \'single\'';
            const html = chatModule.renderMessageHtml(payload);
            console.log(JSON.stringify({
                html,
                hasBreakout: html.includes('data-name="Item" autofocus onfocus="alert(1)""'),
                hasEscapedQuotes: html.includes('data-name="Item&quot; autofocus onfocus=&quot;alert(1)&quot;"'),
                hasEscapedSingle: html.includes('&#39;single&#39;')
            }));
        ÚhasBreakoutz7Double quotes must not break out of data-name attributeÚhasEscapedQuotesz4Double quotes in item tags must be escaped as &quot;ÚhasEscapedSinglez&Single quotes must be escaped as &#39;N)r9   ÚassertFalseÚ
assertTruer{   s     r   Ú/test_14_item_name_attribute_breakout_preventionz@TestWebappChatUI.test_14_item_name_attribute_breakout_preventionä   sv   € à×&Ò&ð 	(ñ 	ô 	ˆð 	×Ò˜˜mÔ,Ð.gÑhÔhÐhØ�Š˜Ð/Ô0Ð2hÑiÔiÐiØ�Š˜Ð/Ô0Ð2ZÑ[Ô[Ð[Ð[Ð[r!   c                ó  — |                       d|                      ¦   «         › d�¦  «        }|                      d|d         d¦  «         |                      d|d         ¦  «         |                      |d         d¦  «         dS )	zerenderChatLog must sanitize msg.senderName with HTML entity escaping before inserting into innerHTML.ú
            a   
            chatModule.addChatMessage({
                messageId: 'msg_xss_sender',
                senderName: '<img src="x" onerror="alert(1)">',
                channel: 1,
                displayContent: 'Test Content',
                linkedItems: [],
                timestampMs: Date.now()
            });
            const container = elements['chat-messages-container'];
            const lastRow = container.children[container.children.length - 1];
            console.log(JSON.stringify({
                renderedHtml: lastRow?.innerHTML || '',
                escapedSender: chatModule.escapeHtml('<img src="x" onerror="alert(1)">')
            }));
        z<img src="x"ÚrenderedHtmlz1Raw HTML in senderName must never reach innerHTMLz:&lt;img src=&quot;x&quot; onerror=&quot;alert(1)&quot;&gt;ÚescapedSenderN)r9   r<   rˆ   rB   rW   r{   s     r   Ú(test_15_sender_name_html_entity_escapingz9TestWebappChatUI.test_15_sender_name_html_entity_escapingô   s˜   € à×&Ò&ð (Ø×ÒÑ Ô ð(ð (ð (ñ ô ˆð" 	×Ò˜¨¨nÔ)=Ð?rÑsÔsÐsØ�ŠÐRÐTXÐYgÔThÑiÔiÐiØ×Ò˜˜oÔ.Ð0lÑmÔmÐmÐmÐmr!   c                ó0  — |                       d|                      ¦   «         › d�¦  «        }|                      |d         d¦  «         |                      d|d         ¦  «         |                      d|d         ¦  «         |                      d|d         ¦  «         |                      t	          |d	         ¦  «        d
¦  «         |                      |d	         d         d¦  «         |                      dd                     |d	         ¦  «        ¦  «         dS )u`   Unverified/fallback item snapshots must display âš  ChÆ°a XÃ¡c Thá»±c badge and no fake affixes.r¸   u^  
            chatModule.openItemTooltip({
                itemUuid: 'u-unverified-999',
                signature: 'sig-invalid',
                itemName: 'Kiáº¿m Giáº£ Máº¡o',
                rarity: 5,
                isVerified: false
            });
            const badge = elements['item-tooltip-hmac-badge'];
            const affixesContainer = elements['item-tooltip-affixes'];
            const affixTexts = (affixesContainer?.children || []).map(c => c.textContent);
            console.log(JSON.stringify({ badgeText: badge?.textContent, badgeClass: badge?.className, affixTexts }));
        Ú	badgeTextu   âš  ChÆ°a XÃ¡c Thá»±czbg-rose-950Ú
badgeClassztext-rose-400zborder-rose-800/60Ú
affixTextsrM   r   u<   â€¢ KhÃ´ng cÃ³ dá»¯ liá»‡u thuá»™c tÃ­nh (ChÆ°a xÃ¡c thá»±c)u   TÄƒng SÃ¡t ThÆ°Æ¡ng Cá»• VÃµr£   N)r9   r<   rW   rB   rZ   rˆ   Újoinr{   s     r   ÚGtest_16_unverified_item_link_displays_warning_badge_and_no_fake_affixeszXTestWebappChatUI.test_16_unverified_item_link_displays_warning_badge_and_no_fake_affixes  s  € à×&Ò&ð (Ø×ÒÑ Ô ð(ð (ð (ñ ô ˆð 	×Ò˜˜kÔ*Ð,CÑDÔDÐDØ�Š�m T¨,Ô%7Ñ8Ô8Ð8Ø�Š�o t¨LÔ'9Ñ:Ô:Ð:Ø�ŠÐ*¨D°Ô,>Ñ?Ô?Ð?Ø×Ò�˜T ,Ô/Ñ0Ô0°!Ñ4Ô4Ð4Ø×Ò˜˜lÔ+¨AÔ.Ð0nÑoÔoÐoØ×ÒÐ7¸¿ºÀÀlÔASÑ9TÔ9TÑUÔUÐUÐUÐUr!   c                ó6  — |                       d|                      ¦   «         › d�¦  «        }|                      |d         d¦  «         |                      d|d         ¦  «         |                      d|d         ¦  «         |                      d|d         ¦  «         |                      |d	         d
¦  «         |                      t	          |d         ¦  «        d¦  «         |                      d|d         ¦  «         |                      d|d         ¦  «         dS )u[   Authenticated item snapshots must display âœ“ HMAC XÃ¡c Thá»±c badge and authentic affixes.r¸   u6  
            chatModule.registerItemSnapshot({
                itemUuid: 'u-verified-100',
                signature: 'sig-valid',
                itemName: 'ThÃ¡i Cá»• Tháº§n ThÆ°Æ¡ng',
                rarity: 5,
                quality: 20,
                itemLevel: 100,
                crafterName: 'Ä�á»™c CÃ´ Cáº§u Báº¡i',
                affixes: [
                    { statName: 'SÃ¡t ThÆ°Æ¡ng Cá»• VÃµ', statValue: 65, isImplicit: true },
                    { statName: 'Báº¡o KÃ­ch NgÅ© HÃ nh', statValue: 25, isImplicit: false }
                ]
            });
            chatModule.openItemTooltip('u-verified-100');
            const badge = elements['item-tooltip-hmac-badge'];
            const affixesContainer = elements['item-tooltip-affixes'];
            const affixTexts = (affixesContainer?.children || []).map(c => c.textContent);
            const crafterText = elements['item-tooltip-crafter']?.textContent;
            console.log(JSON.stringify({ badgeText: badge?.textContent, badgeClass: badge?.className, affixTexts, crafterText }));
        r½   re   zbg-emerald-950r¾   ztext-emerald-400zborder-emerald-700/60ÚcrafterTextu   Ä�á»™c CÃ´ Cáº§u Báº¡ir¿   rN   u   âœ¦ SÃ¡t ThÆ°Æ¡ng Cá»• VÃµ: +65u   â€¢ Báº¡o KÃ­ch NgÅ© HÃ nh: +25N)r9   r<   rW   rB   rZ   r{   s     r   Ú7test_17_verified_item_link_displays_hmac_verified_badgezHTestWebappChatUI.test_17_verified_item_link_displays_hmac_verified_badge#  s  € à×&Ò&ð (Ø×ÒÑ Ô ð(ð (ð (ñ ô ˆð, 	×Ò˜˜kÔ*Ð,BÑCÔCÐCØ�ŠÐ&¨¨\Ô(:Ñ;Ô;Ð;Ø�ŠÐ(¨$¨|Ô*<Ñ=Ô=Ð=Ø�ŠÐ-¨t°LÔ/AÑBÔBÐBØ×Ò˜˜mÔ,Ð.FÑGÔGÐGØ×Ò�˜T ,Ô/Ñ0Ô0°!Ñ4Ô4Ð4Ø�ŠÐ7¸¸lÔ9KÑLÔLÐLØ�ŠÐ7¸¸lÔ9KÑLÔLÐLÐLÐLr!   c                óÒ   — |                       d¦  «        }|                      |d         d¦  «         |                      |d          o|d          o|d         o|d         ¦  «         dS )	zJsnapshotCache must be strictly bounded to max 500 entries (FIFO eviction).a®  
            for (let i = 1; i <= 600; i++) {
                chatModule.registerItemSnapshot({
                    itemUuid: `uuid_${i}`,
                    signature: `sig_${i}`,
                    itemName: `Item_${i}`,
                    rarity: 1
                });
            }
            console.log(JSON.stringify({
                size: chatModule.snapshotCache.size,
                hasOldest: chatModule.snapshotCache.has('uuid_1'),
                hasEvicted100: chatModule.snapshotCache.has('uuid_100'),
                hasKept101: chatModule.snapshotCache.has('uuid_101'),
                hasNewest: chatModule.snapshotCache.has('uuid_600')
            }));
        Úsizerk   Ú	hasOldestÚhasEvicted100Ú
hasKept101Ú	hasNewestN)r9   rW   rµ   r{   s     r   Ú*test_18_snapshot_cache_size_bounded_to_500z;TestWebappChatUI.test_18_snapshot_cache_size_bounded_to_500D  s}   € à×&Ò&ð (ñ ô ˆð" 	×Ò˜˜fœ sÑ+Ô+Ð+Ø�Š˜D Ô-Ð-Ðx°d¸?Ô6KÐ2KÐxÐPTÐUaÔPbÐxÐgkÐlwÔgxÑyÔyÐyÐyÐyr!   N)r   r   )r"   r#   r   r   )r   r#   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__Úclassmethodr    r9   Ústaticmethodr<   rE   rb   rf   ri   rr   r|   r‚   r†   r‰   r¡   r©   r«   r¯   r¶   r»   rÁ   rÄ   rË   r;   r!   r   r   r      s  € € € € € ØTÐTàðFð Fð Fñ „[ðFð0ð 0ð 0ð 0ð( ð	ð 	ð 	ñ „\ð	ð4ð 4ð 4ð 4ð^ð ^ð ^ð ^ð9ð 9ð 9ð 9ð[ð [ð [ð [ð?ð ?ð ?ð ?ð4ð 4ð 4ð 4ð0/ð /ð /ð /ð$_ð _ð _ð _ð-ð -ð -ð -ðmð mð mð mð	bð 	bð 	bð 	bð;ð ;ð ;ð ;ð<ð <ð <ð <ð\ð \ð \ð \ð nð nð nð nð.Vð Vð Vð Vð0Mð Mð Mð MðBzð zð zð zð zð zr!   r   Ú__main__) rÏ   Ú
__future__r   ÚbuiltinsÚ@py_builtinsÚ_pytest.assertion.rewriteÚ	assertionÚrewriteÚ
@pytest_arr2   rU   r,   ÚunittestÚpathlibr   Útypingr   r   r   Ú__file__ÚresolveÚparentÚPROJECT_ROOTÚ
WEBAPP_DIRr   r   r   r   ÚTestCaser   rÌ   Úmainr;   r!   r   ú<module>rä      sp  ððð ð #Ð "Ð "Ð "Ð "Ð "à  € € € € € € € € € € € € €€€€Ø 	€	€	€	Ø Ð Ð Ð Ø €€€Ø Ð Ð Ð Ð Ð Ø "Ð "Ð "Ð "Ð "Ð "Ð "Ð "Ð "Ð "àˆt�H‰~Œ~×%Ò%Ñ'Ô'Ô.Ô5Ô<€Ø˜HÑ$ xÑ/€
Ø˜,Ñ&€
Ø
�tÑ
˜iÑ
'€Ø˜$Ñ Ñ%¨Ñ4€
Ø Ñ)¨EÑ1°FÑ:Ð=MÑM€ð@zð @zð @zð @zð @z�xÔ(ñ @zô @zð @zðF
 ˆzÒÐØ€H„M�O„O€O€O€Oð Ðr!   