pywire 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. pywire/__init__.py +2 -0
  2. pywire/cli/__init__.py +1 -0
  3. pywire/cli/generators.py +48 -0
  4. pywire/cli/main.py +309 -0
  5. pywire/cli/tui.py +563 -0
  6. pywire/cli/validate.py +26 -0
  7. pywire/client/.prettierignore +8 -0
  8. pywire/client/.prettierrc +7 -0
  9. pywire/client/build.mjs +73 -0
  10. pywire/client/eslint.config.js +46 -0
  11. pywire/client/package.json +39 -0
  12. pywire/client/pnpm-lock.yaml +2971 -0
  13. pywire/client/src/core/app.ts +263 -0
  14. pywire/client/src/core/dom-updater.test.ts +78 -0
  15. pywire/client/src/core/dom-updater.ts +321 -0
  16. pywire/client/src/core/index.ts +5 -0
  17. pywire/client/src/core/transport-manager.test.ts +179 -0
  18. pywire/client/src/core/transport-manager.ts +159 -0
  19. pywire/client/src/core/transports/base.ts +122 -0
  20. pywire/client/src/core/transports/http.ts +142 -0
  21. pywire/client/src/core/transports/index.ts +13 -0
  22. pywire/client/src/core/transports/websocket.ts +97 -0
  23. pywire/client/src/core/transports/webtransport.ts +149 -0
  24. pywire/client/src/dev/dev-app.ts +93 -0
  25. pywire/client/src/dev/error-trace.test.ts +97 -0
  26. pywire/client/src/dev/error-trace.ts +76 -0
  27. pywire/client/src/dev/index.ts +4 -0
  28. pywire/client/src/dev/status-overlay.ts +63 -0
  29. pywire/client/src/events/handler.test.ts +318 -0
  30. pywire/client/src/events/handler.ts +454 -0
  31. pywire/client/src/pywire.core.ts +22 -0
  32. pywire/client/src/pywire.dev.ts +27 -0
  33. pywire/client/tsconfig.json +17 -0
  34. pywire/client/vitest.config.ts +15 -0
  35. pywire/compiler/__init__.py +6 -0
  36. pywire/compiler/ast_nodes.py +304 -0
  37. pywire/compiler/attributes/__init__.py +6 -0
  38. pywire/compiler/attributes/base.py +24 -0
  39. pywire/compiler/attributes/conditional.py +37 -0
  40. pywire/compiler/attributes/events.py +55 -0
  41. pywire/compiler/attributes/form.py +37 -0
  42. pywire/compiler/attributes/loop.py +75 -0
  43. pywire/compiler/attributes/reactive.py +34 -0
  44. pywire/compiler/build.py +28 -0
  45. pywire/compiler/build_artifacts.py +342 -0
  46. pywire/compiler/codegen/__init__.py +5 -0
  47. pywire/compiler/codegen/attributes/__init__.py +6 -0
  48. pywire/compiler/codegen/attributes/base.py +19 -0
  49. pywire/compiler/codegen/attributes/events.py +35 -0
  50. pywire/compiler/codegen/directives/__init__.py +6 -0
  51. pywire/compiler/codegen/directives/base.py +16 -0
  52. pywire/compiler/codegen/directives/path.py +53 -0
  53. pywire/compiler/codegen/generator.py +2341 -0
  54. pywire/compiler/codegen/template.py +2178 -0
  55. pywire/compiler/directives/__init__.py +7 -0
  56. pywire/compiler/directives/base.py +20 -0
  57. pywire/compiler/directives/component.py +33 -0
  58. pywire/compiler/directives/context.py +93 -0
  59. pywire/compiler/directives/layout.py +49 -0
  60. pywire/compiler/directives/no_spa.py +24 -0
  61. pywire/compiler/directives/path.py +71 -0
  62. pywire/compiler/directives/props.py +88 -0
  63. pywire/compiler/exceptions.py +19 -0
  64. pywire/compiler/interpolation/__init__.py +6 -0
  65. pywire/compiler/interpolation/base.py +28 -0
  66. pywire/compiler/interpolation/jinja.py +272 -0
  67. pywire/compiler/parser.py +750 -0
  68. pywire/compiler/paths.py +29 -0
  69. pywire/compiler/preprocessor.py +43 -0
  70. pywire/core/wire.py +119 -0
  71. pywire/py.typed +0 -0
  72. pywire/runtime/__init__.py +7 -0
  73. pywire/runtime/aioquic_server.py +194 -0
  74. pywire/runtime/app.py +901 -0
  75. pywire/runtime/compile_error_page.py +195 -0
  76. pywire/runtime/debug.py +203 -0
  77. pywire/runtime/dev_server.py +433 -0
  78. pywire/runtime/dev_server.py.broken +268 -0
  79. pywire/runtime/error_page.py +64 -0
  80. pywire/runtime/error_renderer.py +23 -0
  81. pywire/runtime/escape.py +23 -0
  82. pywire/runtime/files.py +40 -0
  83. pywire/runtime/helpers.py +97 -0
  84. pywire/runtime/http_transport.py +253 -0
  85. pywire/runtime/loader.py +272 -0
  86. pywire/runtime/logging.py +72 -0
  87. pywire/runtime/page.py +384 -0
  88. pywire/runtime/pydantic_integration.py +52 -0
  89. pywire/runtime/router.py +229 -0
  90. pywire/runtime/server.py +25 -0
  91. pywire/runtime/style_collector.py +31 -0
  92. pywire/runtime/upload_manager.py +76 -0
  93. pywire/runtime/validation.py +449 -0
  94. pywire/runtime/websocket.py +665 -0
  95. pywire/runtime/webtransport_handler.py +195 -0
  96. pywire/static/pywire.core.min.js +3 -0
  97. pywire/static/pywire.dev.min.js +20 -0
  98. {pywire-0.1.1.dist-info → pywire-0.1.3.dist-info}/METADATA +1 -1
  99. pywire-0.1.3.dist-info/RECORD +106 -0
  100. pywire-0.1.1.dist-info/RECORD +0 -9
  101. {pywire-0.1.1.dist-info → pywire-0.1.3.dist-info}/WHEEL +0 -0
  102. {pywire-0.1.1.dist-info → pywire-0.1.3.dist-info}/entry_points.txt +0 -0
  103. {pywire-0.1.1.dist-info → pywire-0.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,318 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import { UnifiedEventHandler } from './handler'
3
+ import { PyWireApp } from '../core/app'
4
+
5
+ describe('UnifiedEventHandler', () => {
6
+ let appMock: {
7
+ sendEvent: ReturnType<typeof vi.fn>
8
+ getConfig: ReturnType<typeof vi.fn>
9
+ }
10
+ let handler: UnifiedEventHandler
11
+
12
+ beforeEach(() => {
13
+ document.body.innerHTML = ''
14
+ appMock = {
15
+ sendEvent: vi.fn(),
16
+ getConfig: vi.fn().mockReturnValue({ debug: false }),
17
+ }
18
+ handler = new UnifiedEventHandler(appMock as unknown as PyWireApp)
19
+ })
20
+
21
+ it('should register listeners on init', () => {
22
+ const addEventListenerSpy = vi.spyOn(document, 'addEventListener')
23
+ handler.init()
24
+ // Check for some common events
25
+ expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function), undefined)
26
+ expect(addEventListenerSpy).toHaveBeenCalledWith('submit', expect.any(Function), undefined)
27
+ expect(addEventListenerSpy).toHaveBeenCalledWith('focus', expect.any(Function), {
28
+ capture: true,
29
+ })
30
+ })
31
+
32
+ it('should handle basic click events', async () => {
33
+ document.body.innerHTML = '<button id="btn" data-on-click="handleClick">Click Me</button>'
34
+ const btn = document.getElementById('btn')!
35
+
36
+ handler.init()
37
+ btn.click()
38
+
39
+ expect(appMock.sendEvent).toHaveBeenCalledWith(
40
+ 'handleClick',
41
+ expect.objectContaining({
42
+ type: 'click',
43
+ id: 'btn',
44
+ })
45
+ )
46
+ })
47
+
48
+ it('should support .prevent modifier', () => {
49
+ document.body.innerHTML =
50
+ '<a href="#" id="link" data-on-click="nav" data-modifiers-click="prevent">Link</a>'
51
+ const link = document.getElementById('link')!
52
+ const event = new MouseEvent('click', { bubbles: true, cancelable: true })
53
+ const preventDefaultSpy = vi.spyOn(event, 'preventDefault')
54
+
55
+ handler.init()
56
+ link.dispatchEvent(event)
57
+
58
+ expect(preventDefaultSpy).toHaveBeenCalled()
59
+ expect(appMock.sendEvent).toHaveBeenCalled()
60
+ })
61
+
62
+ it('should support .stop modifier', () => {
63
+ document.body.innerHTML =
64
+ '<div id="parent"><button id="child" data-on-click="hit" data-modifiers-click="stop"></button></div>'
65
+ const child = document.getElementById('child')!
66
+ const event = new MouseEvent('click', { bubbles: true, cancelable: true })
67
+ const stopPropagationSpy = vi.spyOn(event, 'stopPropagation')
68
+
69
+ handler.init()
70
+ child.dispatchEvent(event)
71
+
72
+ expect(stopPropagationSpy).toHaveBeenCalled()
73
+ })
74
+
75
+ it('should support .self modifier', () => {
76
+ document.body.innerHTML = `
77
+ <div id="outer" data-on-click="outerHit" data-modifiers-click="self">
78
+ <button id="inner">Inner</button>
79
+ </div>
80
+ `
81
+ const outer = document.getElementById('outer')!
82
+ const inner = document.getElementById('inner')!
83
+
84
+ handler.init()
85
+
86
+ // Clicking inner should NOT trigger outer because of .self
87
+ inner.click()
88
+ expect(appMock.sendEvent).not.toHaveBeenCalledWith('outerHit', expect.anything())
89
+
90
+ // Clicking outer should trigger
91
+ outer.click()
92
+ expect(appMock.sendEvent).toHaveBeenCalledWith('outerHit', expect.anything())
93
+ })
94
+
95
+ it('should support key modifiers like .enter', () => {
96
+ document.body.innerHTML =
97
+ '<input id="input" data-on-keyup="submit" data-modifiers-keyup="enter">'
98
+ const input = document.getElementById('input')!
99
+
100
+ handler.init()
101
+
102
+ // Non-enter key should not trigger
103
+ input.dispatchEvent(new KeyboardEvent('keyup', { key: 'a', bubbles: true }))
104
+ expect(appMock.sendEvent).not.toHaveBeenCalled()
105
+
106
+ // Enter key should trigger
107
+ input.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', bubbles: true }))
108
+ expect(appMock.sendEvent).toHaveBeenCalledWith(
109
+ 'submit',
110
+ expect.objectContaining({
111
+ type: 'keyup',
112
+ key: 'Enter',
113
+ })
114
+ )
115
+ })
116
+
117
+ it('should handle .debounce modifier', async () => {
118
+ vi.useFakeTimers()
119
+ document.body.innerHTML =
120
+ '<input id="input" data-on-input="search" data-modifiers-input="debounce">'
121
+ const input = document.getElementById('input')!
122
+
123
+ handler.init()
124
+
125
+ // First input
126
+ input.dispatchEvent(new Event('input', { bubbles: true }))
127
+ expect(appMock.sendEvent).not.toHaveBeenCalled()
128
+
129
+ // Second input quickly
130
+ input.dispatchEvent(new Event('input', { bubbles: true }))
131
+
132
+ // Wait for debounce (default 250ms)
133
+ vi.advanceTimersByTime(300)
134
+
135
+ expect(appMock.sendEvent).toHaveBeenCalledTimes(1)
136
+ vi.useRealTimers()
137
+ })
138
+
139
+ it('should extract input value', () => {
140
+ document.body.innerHTML = '<input id="input" value="hello" data-on-change="save">'
141
+ const input = document.getElementById('input')!
142
+
143
+ handler.init()
144
+ input.dispatchEvent(new Event('change', { bubbles: true }))
145
+
146
+ expect(appMock.sendEvent).toHaveBeenCalledWith(
147
+ 'save',
148
+ expect.objectContaining({
149
+ value: 'hello',
150
+ })
151
+ )
152
+ })
153
+
154
+ it('should handle form submit and extract data', async () => {
155
+ document.body.innerHTML = `
156
+ <form id="form" data-on-submit="send">
157
+ <input name="user" value="alice">
158
+ <input name="pass" value="secret">
159
+ </form>
160
+ `
161
+ const form = document.getElementById('form')!
162
+
163
+ handler.init()
164
+ form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
165
+
166
+ expect(appMock.sendEvent).toHaveBeenCalledWith(
167
+ 'send',
168
+ expect.objectContaining({
169
+ type: 'submit',
170
+ formData: {
171
+ user: 'alice',
172
+ pass: 'secret',
173
+ },
174
+ })
175
+ )
176
+ })
177
+
178
+ it('should handle .throttle modifier', async () => {
179
+ vi.useFakeTimers()
180
+ document.body.innerHTML =
181
+ '<button id="btn" data-on-click="fire" data-modifiers-click="throttle"></button>'
182
+ const btn = document.getElementById('btn')!
183
+
184
+ handler.init()
185
+
186
+ // First click - immediate
187
+ btn.click()
188
+ expect(appMock.sendEvent).toHaveBeenCalledTimes(1)
189
+
190
+ // Second click quickly - ignored
191
+ btn.click()
192
+ expect(appMock.sendEvent).toHaveBeenCalledTimes(1)
193
+
194
+ // Wait for throttle (default 250ms)
195
+ vi.advanceTimersByTime(300)
196
+
197
+ // Third click - works again
198
+ btn.click()
199
+ expect(appMock.sendEvent).toHaveBeenCalledTimes(2)
200
+ vi.useRealTimers()
201
+ })
202
+
203
+ it('should support system modifiers like .shift.ctrl', () => {
204
+ document.body.innerHTML =
205
+ '<button id="btn" data-on-click="hit" data-modifiers-click="shift ctrl"></button>'
206
+ const btn = document.getElementById('btn')!
207
+ handler.init()
208
+
209
+ // Click without modifiers - no trigger
210
+ btn.dispatchEvent(new MouseEvent('click', { bubbles: true }))
211
+ expect(appMock.sendEvent).not.toHaveBeenCalled()
212
+
213
+ // Click with only shift - no trigger
214
+ btn.dispatchEvent(new MouseEvent('click', { bubbles: true, shiftKey: true }))
215
+ expect(appMock.sendEvent).not.toHaveBeenCalled()
216
+
217
+ // Click with BOTH shift and ctrl - trigger
218
+ btn.dispatchEvent(new MouseEvent('click', { bubbles: true, shiftKey: true, ctrlKey: true }))
219
+ expect(appMock.sendEvent).toHaveBeenCalled()
220
+ })
221
+
222
+ it('should support .window modifier', () => {
223
+ document.body.innerHTML = '<div data-on-click="winHit" data-modifiers-click="window"></div>'
224
+ handler.init()
225
+
226
+ // Click anywhere (document.body)
227
+ document.body.click()
228
+ expect(appMock.sendEvent).toHaveBeenCalledWith('winHit', expect.anything())
229
+ })
230
+
231
+ it('should support .outside modifier', () => {
232
+ document.body.innerHTML = `
233
+ <div id="modal" data-on-click="close" data-modifiers-click="outside">
234
+ <button id="inside">Inside</button>
235
+ </div>
236
+ <button id="outside">Outside</button>
237
+ `
238
+ const inside = document.getElementById('inside')!
239
+ const outside = document.getElementById('outside')!
240
+ handler.init()
241
+
242
+ // Clicking inside should NOT trigger
243
+ inside.click()
244
+ expect(appMock.sendEvent).not.toHaveBeenCalled()
245
+
246
+ // Clicking outside should trigger
247
+ outside.click()
248
+ expect(appMock.sendEvent).toHaveBeenCalledWith('close', expect.anything())
249
+ })
250
+
251
+ it('should handle dynamic delegation', () => {
252
+ handler.init()
253
+
254
+ // Add element AFTER init
255
+ const div = document.createElement('div')
256
+ div.innerHTML = '<button id="dyn" data-on-click="dynamic">Dyn</button>'
257
+ document.body.appendChild(div)
258
+
259
+ const btn = document.getElementById('dyn')!
260
+ btn.click()
261
+
262
+ expect(appMock.sendEvent).toHaveBeenCalledWith('dynamic', expect.anything())
263
+ })
264
+
265
+ it('should support multiple handlers via JSON', () => {
266
+ document.body.innerHTML = `
267
+ <button id="multi"
268
+ data-on-click='[{"handler": "foo", "modifiers": ["stop"]}, {"handler": "bar", "modifiers": ["prevent"]}]'
269
+ ></button>
270
+ `
271
+ const btn = document.getElementById('multi')!
272
+ const event = new MouseEvent('click', { bubbles: true, cancelable: true })
273
+ const stopPropagationSpy = vi.spyOn(event, 'stopPropagation')
274
+ const preventDefaultSpy = vi.spyOn(event, 'preventDefault')
275
+
276
+ handler.init()
277
+ btn.dispatchEvent(event)
278
+
279
+ expect(appMock.sendEvent).toHaveBeenCalledWith('foo', expect.anything())
280
+ expect(appMock.sendEvent).toHaveBeenCalledWith('bar', expect.anything())
281
+ expect(stopPropagationSpy).toHaveBeenCalled()
282
+ expect(preventDefaultSpy).toHaveBeenCalled()
283
+ })
284
+
285
+ it('should handle explicit arguments in JSON handlers', () => {
286
+ document.body.innerHTML = `
287
+ <button id="args"
288
+ data-on-click='[{"handler": "save", "modifiers": [], "args": [1, "test"]}]'
289
+ ></button>
290
+ `
291
+ const btn = document.getElementById('args')!
292
+
293
+ handler.init()
294
+ btn.click()
295
+
296
+ expect(appMock.sendEvent).toHaveBeenCalledWith(
297
+ 'save',
298
+ expect.objectContaining({
299
+ args: {
300
+ arg0: 1,
301
+ arg1: 'test',
302
+ },
303
+ })
304
+ )
305
+ })
306
+
307
+ it('should fallback to e.code for key modifiers', () => {
308
+ document.body.innerHTML = '<input id="input" data-on-keyup="hit" data-modifiers-keyup="h">'
309
+ const input = document.getElementById('input')!
310
+
311
+ handler.init()
312
+
313
+ // Simulate Alt+H which might produce '˙' but has code 'KeyH'
314
+ input.dispatchEvent(new KeyboardEvent('keyup', { key: '˙', code: 'KeyH', bubbles: true }))
315
+
316
+ expect(appMock.sendEvent).toHaveBeenCalledWith('hit', expect.anything())
317
+ })
318
+ })