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.
- pywire/__init__.py +2 -0
- pywire/cli/__init__.py +1 -0
- pywire/cli/generators.py +48 -0
- pywire/cli/main.py +309 -0
- pywire/cli/tui.py +563 -0
- pywire/cli/validate.py +26 -0
- pywire/client/.prettierignore +8 -0
- pywire/client/.prettierrc +7 -0
- pywire/client/build.mjs +73 -0
- pywire/client/eslint.config.js +46 -0
- pywire/client/package.json +39 -0
- pywire/client/pnpm-lock.yaml +2971 -0
- pywire/client/src/core/app.ts +263 -0
- pywire/client/src/core/dom-updater.test.ts +78 -0
- pywire/client/src/core/dom-updater.ts +321 -0
- pywire/client/src/core/index.ts +5 -0
- pywire/client/src/core/transport-manager.test.ts +179 -0
- pywire/client/src/core/transport-manager.ts +159 -0
- pywire/client/src/core/transports/base.ts +122 -0
- pywire/client/src/core/transports/http.ts +142 -0
- pywire/client/src/core/transports/index.ts +13 -0
- pywire/client/src/core/transports/websocket.ts +97 -0
- pywire/client/src/core/transports/webtransport.ts +149 -0
- pywire/client/src/dev/dev-app.ts +93 -0
- pywire/client/src/dev/error-trace.test.ts +97 -0
- pywire/client/src/dev/error-trace.ts +76 -0
- pywire/client/src/dev/index.ts +4 -0
- pywire/client/src/dev/status-overlay.ts +63 -0
- pywire/client/src/events/handler.test.ts +318 -0
- pywire/client/src/events/handler.ts +454 -0
- pywire/client/src/pywire.core.ts +22 -0
- pywire/client/src/pywire.dev.ts +27 -0
- pywire/client/tsconfig.json +17 -0
- pywire/client/vitest.config.ts +15 -0
- pywire/compiler/__init__.py +6 -0
- pywire/compiler/ast_nodes.py +304 -0
- pywire/compiler/attributes/__init__.py +6 -0
- pywire/compiler/attributes/base.py +24 -0
- pywire/compiler/attributes/conditional.py +37 -0
- pywire/compiler/attributes/events.py +55 -0
- pywire/compiler/attributes/form.py +37 -0
- pywire/compiler/attributes/loop.py +75 -0
- pywire/compiler/attributes/reactive.py +34 -0
- pywire/compiler/build.py +28 -0
- pywire/compiler/build_artifacts.py +342 -0
- pywire/compiler/codegen/__init__.py +5 -0
- pywire/compiler/codegen/attributes/__init__.py +6 -0
- pywire/compiler/codegen/attributes/base.py +19 -0
- pywire/compiler/codegen/attributes/events.py +35 -0
- pywire/compiler/codegen/directives/__init__.py +6 -0
- pywire/compiler/codegen/directives/base.py +16 -0
- pywire/compiler/codegen/directives/path.py +53 -0
- pywire/compiler/codegen/generator.py +2341 -0
- pywire/compiler/codegen/template.py +2178 -0
- pywire/compiler/directives/__init__.py +7 -0
- pywire/compiler/directives/base.py +20 -0
- pywire/compiler/directives/component.py +33 -0
- pywire/compiler/directives/context.py +93 -0
- pywire/compiler/directives/layout.py +49 -0
- pywire/compiler/directives/no_spa.py +24 -0
- pywire/compiler/directives/path.py +71 -0
- pywire/compiler/directives/props.py +88 -0
- pywire/compiler/exceptions.py +19 -0
- pywire/compiler/interpolation/__init__.py +6 -0
- pywire/compiler/interpolation/base.py +28 -0
- pywire/compiler/interpolation/jinja.py +272 -0
- pywire/compiler/parser.py +750 -0
- pywire/compiler/paths.py +29 -0
- pywire/compiler/preprocessor.py +43 -0
- pywire/core/wire.py +119 -0
- pywire/py.typed +0 -0
- pywire/runtime/__init__.py +7 -0
- pywire/runtime/aioquic_server.py +194 -0
- pywire/runtime/app.py +901 -0
- pywire/runtime/compile_error_page.py +195 -0
- pywire/runtime/debug.py +203 -0
- pywire/runtime/dev_server.py +433 -0
- pywire/runtime/dev_server.py.broken +268 -0
- pywire/runtime/error_page.py +64 -0
- pywire/runtime/error_renderer.py +23 -0
- pywire/runtime/escape.py +23 -0
- pywire/runtime/files.py +40 -0
- pywire/runtime/helpers.py +97 -0
- pywire/runtime/http_transport.py +253 -0
- pywire/runtime/loader.py +272 -0
- pywire/runtime/logging.py +72 -0
- pywire/runtime/page.py +384 -0
- pywire/runtime/pydantic_integration.py +52 -0
- pywire/runtime/router.py +229 -0
- pywire/runtime/server.py +25 -0
- pywire/runtime/style_collector.py +31 -0
- pywire/runtime/upload_manager.py +76 -0
- pywire/runtime/validation.py +449 -0
- pywire/runtime/websocket.py +665 -0
- pywire/runtime/webtransport_handler.py +195 -0
- pywire/static/pywire.core.min.js +3 -0
- pywire/static/pywire.dev.min.js +20 -0
- {pywire-0.1.1.dist-info → pywire-0.1.3.dist-info}/METADATA +1 -1
- pywire-0.1.3.dist-info/RECORD +106 -0
- pywire-0.1.1.dist-info/RECORD +0 -9
- {pywire-0.1.1.dist-info → pywire-0.1.3.dist-info}/WHEEL +0 -0
- {pywire-0.1.1.dist-info → pywire-0.1.3.dist-info}/entry_points.txt +0 -0
- {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
|
+
})
|