from threading import Lock import pytest import webview from .util import run_test, wait_release @pytest.fixture def window(): return webview.create_window( 'Evaluate JS test', html='
TEST
' ) def test_state(window): run_test(webview, window, state_test) def test_state_before_start(window): window.state.test = 420 run_test(webview, window, before_start_test) def test_state_from_js(window): run_test(webview, window, state_from_js_test) def test_state_dict(window): run_test(webview, window, state_dict_test) def test_state_none(window): run_test(webview, window, state_none_test) def test_persistence(window): run_test(webview, window, persistence_test) def test_delete(window): run_test(webview, window, delete_test) def test_delete_from_js(window): run_test(webview, window, delete_from_js_test) def test_event_change(window): run_test(webview, window, event_change_test) def test_event_change_js(window): run_test(webview, window, event_change_js_test) def test_event_delete(window): run_test(webview, window, event_delete_test) def test_event_delete_js(window): run_test(webview, window, event_delete_js_test) def test_event_change_from_js(window): run_test(webview, window, event_change_from_js_test) def test_event_delete_from_js(window): run_test(webview, window, event_delete_from_js_test) def test_string_quotes(window): run_test(webview, window, string_quotes_test) def test_string_quotes_from_js(window): run_test(webview, window, string_quotes_from_js_test) def test_state_dictionary_access(window): """Test dictionary-style access: state['key']""" run_test(webview, window, dictionary_access_test) def test_state_dictionary_assignment(window): """Test dictionary-style assignment: state['key'] = value""" run_test(webview, window, dictionary_assignment_test) def test_state_dictionary_deletion(window): """Test dictionary-style deletion: del state['key']""" run_test(webview, window, dictionary_deletion_test) def test_state_mixed_access(window): """Test mixing attribute and dictionary access""" run_test(webview, window, mixed_access_test) def state_test(window): window.state.test = 420 assert window.evaluate_js('pywebview.state.test === 420') def before_start_test(window): assert window.evaluate_js('pywebview.state.test === 420') def state_from_js_test(window): window.run_js('pywebview.state.test = 420') assert window.state.test == 420 def state_dict_test(window): window.state.test = {'test1': 'test1', 'test2': 2} assert window.evaluate_js( 'JSON.stringify(pywebview.state.test) === JSON.stringify({ "test1": "test1", "test2": 2}) ' ) def state_none_test(window): window.state.test = None assert window.evaluate_js('pywebview.state.test === null') def persistence_test(window): window.state.test = 420 assert window.evaluate_js('pywebview.state.test === 420') window.load_html('Reloaded') assert window.evaluate_js('pywebview.state.test === 420') window.load_url('https://www.example.com') assert window.evaluate_js('pywebview.state.test === 420') def delete_test(window): window.state.test = 420 assert window.evaluate_js('pywebview.state.test === 420') del window.state.test assert window.evaluate_js('Object.keys(pywebview.state).length === 0') def delete_from_js_test(window): window.state.test = 420 assert window.evaluate_js('pywebview.state.test === 420') window.run_js('delete pywebview.state.test') assert 'test' not in window.state def event_change_test(window): def on_change(event, name, value): try: assert event == 'change' assert name == 'test' assert value == 420 except AssertionError as e: nonlocal exception exception = e wait_release(lock) lock = Lock() exception = False window.state += on_change window.state.test = 420 assert lock.acquire(3) if exception: raise exception def event_change_js_test(window): def on_change(event, name, value): try: assert event == 'change' assert name == 'test' assert value == 420 except AssertionError as e: nonlocal exception exception = e wait_release(lock) lock = Lock() exception = False window.state += on_change window.evaluate_js('pywebview.state.test = 420') assert lock.acquire(3) if exception: raise exception def event_delete_test(window): def on_delete(event, name, value): try: assert event == 'delete' assert name == 'test' assert value == 420 except AssertionError as e: nonlocal exception exception = e wait_release(lock) exception = False lock = Lock() window.state.test = 420 window.state += on_delete del window.state.test assert lock.acquire(3) if exception: raise exception def event_delete_js_test(window): def on_delete(event, name, value): try: assert event == 'delete' assert name == 'test' assert value == 420 except AssertionError as e: nonlocal exception exception = e wait_release(lock) lock = Lock() exception = False window.state.test = 420 window.state += on_delete window.evaluate_js('delete pywebview.state.test') assert lock.acquire(3) if exception: raise exception def event_change_from_js_test(window): window.run_js( 'pywebview.state.addEventListener("change", event => { pywebview.state.result = `${event.detail.key}: ${event.detail.value}` })' ) window.state.test = 0 assert window.state.result == 'test: 0' def event_delete_from_js_test(window): window.run_js( 'pywebview.state.addEventListener("delete", event => { pywebview.state.result = event.detail.key })' ) window.state.test = 0 assert window.evaluate_js('pywebview.state.test == 0') del window.state.test assert window.state.result == 'test' def string_quotes_test(window): # Test string with single quotes window.state.single_quote_string = "String with 'single' quotes inside" result = window.evaluate_js('pywebview.state.single_quote_string') assert result == "String with 'single' quotes inside" # Test string with double quotes window.state.double_quote_string = 'String with "double" quotes inside' result = window.evaluate_js('pywebview.state.double_quote_string') assert result == 'String with "double" quotes inside' # Test string with both single and double quotes window.state.mixed_quote_string = """String with 'single' and "double" quotes""" result = window.evaluate_js('pywebview.state.mixed_quote_string') assert result == """String with 'single' and "double" quotes""" # Test string with backslashes window.state.backslash_string = 'Path\\to\\file\\with\\backslashes' result = window.evaluate_js('pywebview.state.backslash_string') assert result == 'Path\\to\\file\\with\\backslashes' # Test string with line breaks window.state.multiline_string = 'Line 1\nLine 2\nLine 3' result = window.evaluate_js('pywebview.state.multiline_string') assert result == 'Line 1\nLine 2\nLine 3' # Test string with tabs and carriage returns window.state.special_chars = 'Tab\there\tand\rcarriage\rreturn' result = window.evaluate_js('pywebview.state.special_chars') assert result == 'Tab\there\tand\rcarriage\rreturn' # Test string with escaped characters window.state.escaped_string = 'Quote: " Backslash: \\ Newline: \n Tab: \t' result = window.evaluate_js('pywebview.state.escaped_string') assert result == 'Quote: " Backslash: \\ Newline: \n Tab: \t' # Test assignment from JS side with single quotes window.run_js('pywebview.state.js_single = "JS string with \'single\' quotes"') assert window.state.js_single == "JS string with 'single' quotes" # Test assignment from JS side with double quotes window.run_js('pywebview.state.js_double = \'JS string with "double" quotes\'') assert window.state.js_double == 'JS string with "double" quotes' # Test roundtrip: Python -> JS -> Python with mixed quotes and special chars test_string = """Test with 'single' and "double" quotes\nand backslash\\path""" window.state.roundtrip_test = test_string js_result = window.evaluate_js('pywebview.state.roundtrip_test') assert js_result == test_string def string_quotes_from_js_test(window): # Test assignment from JS with single quotes inside double quotes window.run_js( 'pywebview.state.js_single_in_double = "JavaScript string with \'single\' quotes"' ) assert window.state.js_single_in_double == "JavaScript string with 'single' quotes" # Test assignment from JS with double quotes inside single quotes window.run_js( 'pywebview.state.js_double_in_single = \'JavaScript string with "double" quotes\'' ) assert window.state.js_double_in_single == 'JavaScript string with "double" quotes' # Test assignment from JS with escaped single quotes window.run_js("pywebview.state.js_escaped_single = 'String with \\'escaped\\' quotes'") assert window.state.js_escaped_single == "String with 'escaped' quotes" # Test assignment from JS with escaped double quotes window.run_js('pywebview.state.js_escaped_double = "String with \\"escaped\\" quotes"') assert window.state.js_escaped_double == 'String with "escaped" quotes' # Test assignment from JS with backslashes window.run_js('pywebview.state.js_backslash = "C:\\\\Windows\\\\System32\\\\file.txt"') assert window.state.js_backslash == 'C:\\Windows\\System32\\file.txt' # Test assignment from JS with line breaks window.run_js('pywebview.state.js_multiline = "First line\\nSecond line\\nThird line"') assert window.state.js_multiline == 'First line\nSecond line\nThird line' # Test assignment from JS with tabs and other escape sequences window.run_js('pywebview.state.js_tabs = "Column1\\tColumn2\\tColumn3\\r\\nNew row"') assert window.state.js_tabs == 'Column1\tColumn2\tColumn3\r\nNew row' # Test assignment from JS with mixed quotes using template literals window.run_js( 'pywebview.state.js_template = `Template with \'single\' and "double" quotes\\nwith newline`' ) assert ( window.state.js_template == """Template with 'single' and "double" quotes\nwith newline""" ) # Test assignment from JS with Unicode characters window.run_js( 'pywebview.state.js_unicode = "Unicode: \\u0048\\u0065\\u006C\\u006C\\u006F \\u2764\\uFE0F"' ) assert window.state.js_unicode == 'Unicode: Hello ❤️' # Test assignment from JS with null bytes and control characters window.run_js('pywebview.state.js_control = "Before\\u0000null\\u0001control\\u0008backspace"') assert window.state.js_control == 'Before\x00null\x01control\x08backspace' # Test that JS-assigned strings are accessible back in JS window.run_js('pywebview.state.js_test = "Test\\nfrom\\tJS\\\\path"') assert window.evaluate_js('pywebview.state.js_test === "Test\\nfrom\\tJS\\\\path"') # Test complex string with all special characters from JS window.run_js( '''pywebview.state.js_complex = "Line 1 with 'quotes'\\nLine 2 with \\"quotes\\"\\tand\\\\backslash"''' ) expected = 'Line 1 with \'quotes\'\nLine 2 with "quotes"\tand\\backslash' assert window.state.js_complex == expected # Test template literal with complex content window.run_js( """pywebview.state.js_template_complex = `Multi-line template with 'single' and "double" quotes and backslash: \\\\ and tab: \\t end`""" ) expected = """Multi-line template with 'single' and "double" quotes and backslash: \\ and tab: \t end""" assert window.state.js_template_complex == expected def dictionary_access_test(window): """Test getting values using dictionary syntax""" # Set via attribute, get via dictionary window.state.test_key = 'test_value' assert window.state['test_key'] == 'test_value' # Set via dictionary, get via dictionary window.state['another_key'] = 'another_value' assert window.state['another_key'] == 'another_value' def dictionary_assignment_test(window): """Test setting values using dictionary syntax""" # Dictionary-style assignment window.state['name'] = 'John' window.state['age'] = 30 window.state['data'] = {'nested': 'object'} # Verify via attribute access assert window.state.name == 'John' assert window.state.age == 30 assert window.state.data == {'nested': 'object'} # Verify in JavaScript assert window.evaluate_js('pywebview.state.name === "John"') assert window.evaluate_js('pywebview.state.age === 30') def dictionary_deletion_test(window): """Test deleting values using dictionary syntax""" # Set some values window.state['temp1'] = 'value1' window.state['temp2'] = 'value2' # Delete using dictionary syntax del window.state['temp1'] # Verify deletion assert 'temp1' not in window.state assert 'temp2' in window.state # Test KeyError for non-existent key with pytest.raises(KeyError): del window.state['nonexistent'] def mixed_access_test(window): """Test mixing attribute and dictionary access patterns""" # Set via attribute, access both ways window.state.attr_set = 'attribute_value' assert window.state.attr_set == 'attribute_value' assert window.state['attr_set'] == 'attribute_value' # Set via dictionary, access both ways window.state['dict_set'] = 'dictionary_value' assert window.state['dict_set'] == 'dictionary_value' assert window.state.dict_set == 'dictionary_value' # Test dictionary methods keys = list(window.state.keys()) assert 'attr_set' in keys assert 'dict_set' in keys assert len(window.state) >= 2