Files
LihonT/tests/test_theme_patch.py
cheb0chik ce1d870d54 LihonT 2 Luna beta: source release
Mod for TikTok 47.1.3 (arm64) in Material 3 style: bottom bar, profile menu, TikTok settings and
comments; downloads without the watermark; switches for ads, live streams and the Shop tab; badges of
other mods next to names and the niche mod badge (members service); app icons; mod settings screen.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 20:32:13 +03:00

260 lines
8.2 KiB
Python

import unittest
from lihont import theme_patch
ACCESSOR = """\
.class public final LX/0LAr;
.super Ljava/lang/Object;
.source "SourceFile"
.method public static LIZ(LX/0LYA;)LX/0LFj;
.registers 2
sget-object v0, LX/0LAs;->LIZ:LX/0MO7;
invoke-interface {p0, v0}, LX/0LYA;->LJJIZ(LX/0MNv;)Ljava/lang/Object;
move-result-object v0
check-cast v0, LX/0LFj;
return-object v0
.end method
.method public static LIZIZ(LX/0LYA;)LX/1MiX;
.registers 2
.prologue
sget-object v0, LX/1MiY;->LIZ:LX/0MO7;
invoke-interface {p0, v0}, LX/0LYA;->LJJIZ(LX/0MNv;)Ljava/lang/Object;
move-result-object v0
check-cast v0, LX/1MiX;
return-object v0
.end method
"""
LABEL = """.class public final LX/0Lis;
.super Ljava/lang/Object;
.method public static final LIZIZ(LX/0DMR;LX/0LYA;I)V
.registers 20
invoke-static {v0}, LX/0LAr;->LIZIZ(LX/0LYA;)LX/1MiX;
move-result-object v1
iget-wide v14, v1, LX/1MiX;->d4:J
return-void
.end method
.method public static final LIZJ(Ljava/lang/String;ZZZLkotlin/jvm/functions/Function0;LX/0LYA;II)V
.registers 40
invoke-static {v0}, LX/0LAr;->LIZIZ(LX/0LYA;)LX/1MiX;
move-result-object v11
iget-wide v12, v11, LX/1MiX;->d4:J
invoke-static {v0}, LX/0LAr;->LJ(LX/0LYA;)LX/0LAb;
.line 83886186
move-result-object v3
.line 83886187
iget-wide v8, v3, LX/1MiX;->d4:J
:goto_6d
return-void
.end method
"""
ROW = """\
.class public final LX/0LDf;
.super Ljava/lang/Object;
.method public final invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
.registers 52
invoke-interface {v0}, LX/0LYA;->LJ()V
.line 33555008
.line 33555009
.line 33555010
move-object/from16 v9, v17
.line 33555011
.line 33555012
invoke-static {v9, v1, v13}, LX/0M8m;->LIZIZ(LX/0M1d;ZLkotlin/jvm/functions/Function1;)LX/0M1d;
.line 33555013
move-result-object v1
invoke-interface {v4, v0}, LX/0LDi;->LJIIJJI(LX/0LYA;)J
move-result-wide v4
.line 33555175
invoke-static {v8, v8, v7, v7}, LX/0Kzn;->LIZJ(FFFF)LX/0LQf;
.line 33555176
.line 33555177
move-result-object v7
move-object/from16 v6, v17
return-object v1
.end method
"""
CARD = """.class public final LX/0Lk9;
.super Ljava/lang/Object;
.method public static final LIZ(LX/0CX2;ZZLcom/ss/android/ugc/aweme/setting/ui/rvmpcompose/group/content/cell/LanguageVM;LX/0LYA;I)V
.registers 40
invoke-static {v15}, LX/0Kzn;->LIZIZ(F)LX/0LQf;
move-result-object v15
invoke-static {v11, v0, v1, v15}, LX/0L5k;->LIZIZ(LX/0M1d;JLX/0LQx;)LX/0M1d;
.line 33555200
.line 33555201
move-result-object v0
sget-object v1, LX/0L0L;->LIZ:LX/0L0J;
return-void
.end method
"""
class ColoursAccessorTest(unittest.TestCase):
def test_hook_sits_between_the_read_and_the_cast(self):
text = theme_patch.rewrite_colors_accessor(ACCESSOR)
self.assertIn(
" move-result-object v0\n\n"
" invoke-static {v0, p0}, %s\n\n"
" move-result-object v0\n\n"
" check-cast v0, LX/1MiX;\n" % theme_patch.HOOK, text)
def test_other_methods_are_untouched(self):
text = theme_patch.rewrite_colors_accessor(ACCESSOR)
self.assertEqual(text.count("NativeTheme"), 1)
self.assertIn("check-cast v0, LX/0LFj;\n\n return-object v0\n", text)
def test_a_second_pass_is_refused(self):
once = theme_patch.rewrite_colors_accessor(ACCESSOR)
with self.assertRaises(ValueError):
theme_patch.rewrite_colors_accessor(once)
def test_a_missing_method_is_refused(self):
with self.assertRaises(ValueError):
theme_patch.rewrite_colors_accessor(ACCESSOR.replace("LIZIZ", "LIZZZ"))
def test_an_unexpected_register_layout_is_refused(self):
with self.assertRaises(ValueError):
theme_patch.rewrite_colors_accessor(
ACCESSOR.replace(".registers 2\n .prologue", ".registers 3\n .prologue"))
def test_runtime_target_matches_the_hook(self):
self.assertEqual(theme_patch.runtime_targets(), [theme_patch.HOOK])
class SectionLabelTest(unittest.TestCase):
def test_both_reads_in_the_group_title_are_rewritten(self):
text = theme_patch.rewrite_section_label(LABEL)
self.assertIn(
" invoke-static/range {v11 .. v11}, %s\n\n move-result-wide v12\n"
% theme_patch.SECTION_HOOK, text)
self.assertIn(
" invoke-static/range {v3 .. v3}, %s\n\n move-result-wide v8\n"
% theme_patch.SECTION_HOOK, text)
self.assertEqual(text.count("sectionLabel"), 2)
# The other composable keeps reading the token itself.
self.assertIn("iget-wide v14, v1, LX/1MiX;->d4:J", text)
def test_a_second_pass_is_refused(self):
once = theme_patch.rewrite_section_label(LABEL)
with self.assertRaises(ValueError):
theme_patch.rewrite_section_label(once)
def test_a_missing_composable_is_refused(self):
with self.assertRaises(ValueError):
theme_patch.rewrite_section_label(
LABEL.replace(".method public static final LIZJ(", ".method public static final LIZZ("))
def test_a_title_with_a_different_number_of_reads_is_refused(self):
with self.assertRaises(ValueError):
theme_patch.rewrite_section_label(
LABEL.replace("v8, v3, LX/1MiX;->d4:J", "v8, v3, LX/1MiX;->a4:J"))
def test_section_target_is_the_hook(self):
self.assertEqual(theme_patch.section_targets(), [theme_patch.SECTION_HOOK])
class RowLambdaTest(unittest.TestCase):
def test_modifier_and_shape_are_routed_through_the_hooks(self):
text = theme_patch.rewrite_row_lambda(ROW)
self.assertIn(
" move-object/from16 v9, v17\n"
" invoke-static {v9, v0}, %s\n\n"
" move-result-object v9\n\n"
" check-cast v9, LX/0M1d;\n" % theme_patch.ROW_MODIFIER_HOOK, text)
self.assertIn("invoke-static {v8, v8, v7, v7, v0}, %s\n" % theme_patch.ROW_SHAPE_HOOK, text)
self.assertIn(" move-result-object v7\n\n check-cast v7, LX/0LQf;\n", text)
self.assertNotIn("LX/0Kzn;->LIZJ", text)
# The original semantics call still follows the padded modifier.
self.assertIn("invoke-static {v9, v1, v13}, LX/0M8m;->LIZIZ", text)
def test_a_second_pass_is_refused(self):
once = theme_patch.rewrite_row_lambda(ROW)
with self.assertRaises(ValueError):
theme_patch.rewrite_row_lambda(once)
def test_a_missing_shape_call_is_refused(self):
with self.assertRaises(ValueError):
theme_patch.rewrite_row_lambda(ROW.replace("LIZJ(FFFF)", "LIZJ(FF)"))
def test_a_missing_modifier_start_is_refused(self):
with self.assertRaises(ValueError):
theme_patch.rewrite_row_lambda(
ROW.replace("move-object/from16 v9, v17", "move-object/from16 v9, v18"))
def test_row_targets_are_the_hooks(self):
self.assertEqual(theme_patch.row_targets(),
[theme_patch.ROW_MODIFIER_HOOK, theme_patch.ROW_SHAPE_HOOK])
class LanguageCardTest(unittest.TestCase):
def test_the_card_background_call_goes_through_the_hook(self):
text = theme_patch.rewrite_language_card(CARD)
self.assertIn(" invoke-static {v11, v0, v1, v15, v3}, %s\n" % theme_patch.CARD_HOOK, text)
self.assertIn(" move-result-object v0\n\n check-cast v0, LX/0M1d;\n", text)
self.assertNotIn("LX/0L5k;->LIZIZ", text)
# The rounded shape built just before it is TikTok's own call and stays.
self.assertIn("LX/0Kzn;->LIZIZ(F)LX/0LQf;", text)
def test_a_second_pass_is_refused(self):
once = theme_patch.rewrite_language_card(CARD)
with self.assertRaises(ValueError):
theme_patch.rewrite_language_card(once)
def test_a_missing_background_call_is_refused(self):
with self.assertRaises(ValueError):
theme_patch.rewrite_language_card(CARD.replace("v11, v0, v1, v15}", "v10, v0, v1, v15}"))
def test_card_target_is_the_hook(self):
self.assertEqual(theme_patch.card_targets(), [theme_patch.CARD_HOOK])
if __name__ == "__main__":
unittest.main()