@invisibleloop/pulse 0.20.37 → 0.20.39
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.
- package/package.json +2 -2
- package/public/.pulse-ui-version +1 -1
- package/src/mcp/guards.js +94 -0
- package/src/mcp/server.js +22 -3
- package/src/runtime/ssr.js +21 -5
- package/src/server/index.js +2 -2
- package/scripts/css-purge.test.js +0 -147
- package/scripts/strip-server.test.js +0 -491
- package/src/cli/check-bundles.test.js +0 -137
- package/src/cli/cli.test.js +0 -341
- package/src/cli/diagnose.test.js +0 -124
- package/src/cli/process-utils.test.js +0 -114
- package/src/cli/review.test.js +0 -143
- package/src/cli/stamp.test.js +0 -149
- package/src/cli/validate.test.js +0 -153
- package/src/mcp/mcp.test.js +0 -617
- package/src/md/md.test.js +0 -217
- package/src/runtime/image.test.js +0 -111
- package/src/runtime/morph.test.js +0 -317
- package/src/runtime/navigate.test.js +0 -304
- package/src/runtime/runtime.test.js +0 -1069
- package/src/runtime/ssr.test.js +0 -709
- package/src/server/server.test.js +0 -2474
- package/src/spec/schema.test.js +0 -467
- package/src/store/store.test.js +0 -210
- package/src/testing/testing.test.js +0 -508
- package/src/ui/ui.test.js +0 -1157
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pulse — Navigate tests
|
|
3
|
-
* run: node src/runtime/navigate.test.js
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { initNavigation } from './navigate.js'
|
|
7
|
-
import assert from 'node:assert'
|
|
8
|
-
|
|
9
|
-
// ── Test runner ───────────────────────────────────────────────────────────────
|
|
10
|
-
|
|
11
|
-
let passed = 0
|
|
12
|
-
let failed = 0
|
|
13
|
-
|
|
14
|
-
async function test(label, fn) {
|
|
15
|
-
try {
|
|
16
|
-
await fn()
|
|
17
|
-
console.log(` ✓ ${label}`)
|
|
18
|
-
passed++
|
|
19
|
-
} catch (e) {
|
|
20
|
-
console.log(` ✗ ${label}`)
|
|
21
|
-
console.log(` ${e.message}`)
|
|
22
|
-
failed++
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ── Minimal browser shims ─────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
function makeRoot() {
|
|
29
|
-
const root = {
|
|
30
|
-
innerHTML: '',
|
|
31
|
-
dataset: {},
|
|
32
|
-
querySelector: () => null,
|
|
33
|
-
querySelectorAll: () => [],
|
|
34
|
-
hasAttribute: (k) => root._attrs.has(k),
|
|
35
|
-
setAttribute: (k) => root._attrs.add(k),
|
|
36
|
-
removeAttribute: (k) => root._attrs.delete(k),
|
|
37
|
-
addEventListener: () => {},
|
|
38
|
-
focus: () => {},
|
|
39
|
-
_attrs: new Set(),
|
|
40
|
-
}
|
|
41
|
-
return root
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Set up fresh browser globals and call initNavigation().
|
|
46
|
-
* Returns helpers to fire the registered click and popstate handlers.
|
|
47
|
-
*/
|
|
48
|
-
function setup({ mountFn = null } = {}) {
|
|
49
|
-
let clickHandler = null
|
|
50
|
-
let popHandler = null
|
|
51
|
-
|
|
52
|
-
global.history = { replaceState: () => {}, pushState: () => {} }
|
|
53
|
-
global.location = { pathname: '/start', search: '', origin: 'http://localhost', href: '' }
|
|
54
|
-
global.window = {
|
|
55
|
-
scrollTo: () => {},
|
|
56
|
-
addEventListener: (e, fn) => { if (e === 'popstate') popHandler = fn },
|
|
57
|
-
}
|
|
58
|
-
global.document = {
|
|
59
|
-
title: 'Test',
|
|
60
|
-
addEventListener: (e, fn) => { if (e === 'click') clickHandler = fn },
|
|
61
|
-
dispatchEvent: () => {},
|
|
62
|
-
querySelectorAll: () => [],
|
|
63
|
-
createElement: () => ({
|
|
64
|
-
rel: '', href: '', src: '', textContent: '', type: '',
|
|
65
|
-
setAttribute: () => {},
|
|
66
|
-
getAttribute: () => null,
|
|
67
|
-
onload: null, onerror: null,
|
|
68
|
-
}),
|
|
69
|
-
head: { appendChild: () => {} },
|
|
70
|
-
}
|
|
71
|
-
global.CustomEvent = class { constructor(n) { this.type = n } }
|
|
72
|
-
|
|
73
|
-
const root = makeRoot()
|
|
74
|
-
initNavigation(root, mountFn)
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
root,
|
|
78
|
-
click: (ev) => clickHandler(ev),
|
|
79
|
-
pop: (ev) => popHandler(ev),
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Build a synthetic click event targeting an anchor. */
|
|
84
|
-
function clickEvent({ href, ctrlKey = false, metaKey = false, shiftKey = false, altKey = false, linkTarget = '' }) {
|
|
85
|
-
const anchor = {
|
|
86
|
-
getAttribute: (k) => k === 'href' ? (href ?? null) : null,
|
|
87
|
-
target: linkTarget,
|
|
88
|
-
}
|
|
89
|
-
return {
|
|
90
|
-
target: { closest: (sel) => sel === 'a' ? anchor : null },
|
|
91
|
-
ctrlKey, metaKey, shiftKey, altKey,
|
|
92
|
-
preventDefault: () => {},
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Event where target has no ancestor <a>. */
|
|
97
|
-
function nonAnchorEvent() {
|
|
98
|
-
return {
|
|
99
|
-
target: { closest: () => null },
|
|
100
|
-
ctrlKey: false, metaKey: false, shiftKey: false, altKey: false,
|
|
101
|
-
preventDefault: () => {},
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ── Click handler — link filtering ───────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
console.log('\nClick handler — link filtering\n')
|
|
108
|
-
|
|
109
|
-
await test('does not intercept clicks with no ancestor anchor', async () => {
|
|
110
|
-
const { click } = setup()
|
|
111
|
-
let called = false
|
|
112
|
-
global.fetch = async () => { called = true; return new Promise(() => {}) }
|
|
113
|
-
click(nonAnchorEvent())
|
|
114
|
-
assert(!called)
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
await test('does not intercept anchor with no href', async () => {
|
|
118
|
-
const { click } = setup()
|
|
119
|
-
let called = false
|
|
120
|
-
global.fetch = async () => { called = true; return new Promise(() => {}) }
|
|
121
|
-
click(clickEvent({ href: null }))
|
|
122
|
-
assert(!called)
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
await test('does not intercept cross-origin links', async () => {
|
|
126
|
-
const { click } = setup()
|
|
127
|
-
let called = false
|
|
128
|
-
global.fetch = async () => { called = true; return new Promise(() => {}) }
|
|
129
|
-
click(clickEvent({ href: 'https://example.com/page' }))
|
|
130
|
-
assert(!called)
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
await test('does not intercept ctrl+click', async () => {
|
|
134
|
-
const { click } = setup()
|
|
135
|
-
let called = false
|
|
136
|
-
global.fetch = async () => { called = true; return new Promise(() => {}) }
|
|
137
|
-
click(clickEvent({ href: '/page', ctrlKey: true }))
|
|
138
|
-
assert(!called)
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
await test('does not intercept meta+click', async () => {
|
|
142
|
-
const { click } = setup()
|
|
143
|
-
let called = false
|
|
144
|
-
global.fetch = async () => { called = true; return new Promise(() => {}) }
|
|
145
|
-
click(clickEvent({ href: '/page', metaKey: true }))
|
|
146
|
-
assert(!called)
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
await test('does not intercept shift+click', async () => {
|
|
150
|
-
const { click } = setup()
|
|
151
|
-
let called = false
|
|
152
|
-
global.fetch = async () => { called = true; return new Promise(() => {}) }
|
|
153
|
-
click(clickEvent({ href: '/page', shiftKey: true }))
|
|
154
|
-
assert(!called)
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
await test('does not intercept target=_blank links', async () => {
|
|
158
|
-
const { click } = setup()
|
|
159
|
-
let called = false
|
|
160
|
-
global.fetch = async () => { called = true; return new Promise(() => {}) }
|
|
161
|
-
click(clickEvent({ href: '/page', linkTarget: '_blank' }))
|
|
162
|
-
assert(!called)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
await test('intercepts same-origin links', async () => {
|
|
166
|
-
const { click } = setup()
|
|
167
|
-
let called = false
|
|
168
|
-
global.fetch = async () => { called = true; return new Promise(() => {}) }
|
|
169
|
-
click(clickEvent({ href: '/same-origin' }))
|
|
170
|
-
assert(called, 'fetch should be called for same-origin link')
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
// ── Navigation — fetch abort and cancellation ────────────────────────────────
|
|
174
|
-
|
|
175
|
-
console.log('\nNavigation — fetch abort and cancellation\n')
|
|
176
|
-
|
|
177
|
-
await test('aborts in-flight fetch when a new navigation starts', async () => {
|
|
178
|
-
const { click } = setup()
|
|
179
|
-
const signals = []
|
|
180
|
-
global.fetch = async (_url, opts) => {
|
|
181
|
-
signals.push(opts.signal)
|
|
182
|
-
return new Promise(() => {}) // never resolves
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
click(clickEvent({ href: '/page-a' }))
|
|
186
|
-
click(clickEvent({ href: '/page-b' }))
|
|
187
|
-
|
|
188
|
-
assert.equal(signals.length, 2, 'two fetches made')
|
|
189
|
-
assert(signals[0].aborted, 'first navigation signal should be aborted')
|
|
190
|
-
assert(!signals[1].aborted, 'second navigation signal should not be aborted')
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
await test('swallows AbortError — does not fall back to location.href', async () => {
|
|
194
|
-
const { click } = setup()
|
|
195
|
-
global.fetch = async () => {
|
|
196
|
-
const err = new Error('aborted')
|
|
197
|
-
err.name = 'AbortError'
|
|
198
|
-
throw err
|
|
199
|
-
}
|
|
200
|
-
let fallbackSet = false
|
|
201
|
-
Object.defineProperty(global.location, 'href', {
|
|
202
|
-
set: () => { fallbackSet = true },
|
|
203
|
-
get: () => '',
|
|
204
|
-
configurable: true,
|
|
205
|
-
})
|
|
206
|
-
click(clickEvent({ href: '/page' }))
|
|
207
|
-
await new Promise(r => setTimeout(r, 10))
|
|
208
|
-
assert(!fallbackSet, 'AbortError must not trigger location.href fallback')
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
await test('falls back to location.href on network error', async () => {
|
|
212
|
-
const { click } = setup()
|
|
213
|
-
global.fetch = async () => { throw new Error('network failure') }
|
|
214
|
-
let fallback = null
|
|
215
|
-
Object.defineProperty(global.location, 'href', {
|
|
216
|
-
set: (v) => { fallback = v },
|
|
217
|
-
get: () => '',
|
|
218
|
-
configurable: true,
|
|
219
|
-
})
|
|
220
|
-
click(clickEvent({ href: '/broken' }))
|
|
221
|
-
await new Promise(r => setTimeout(r, 10))
|
|
222
|
-
assert.equal(fallback, '/broken')
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
await test('falls back to location.href on non-ok response', async () => {
|
|
226
|
-
const { click } = setup()
|
|
227
|
-
global.fetch = async () => ({ ok: false })
|
|
228
|
-
let fallback = null
|
|
229
|
-
Object.defineProperty(global.location, 'href', {
|
|
230
|
-
set: (v) => { fallback = v },
|
|
231
|
-
get: () => '',
|
|
232
|
-
configurable: true,
|
|
233
|
-
})
|
|
234
|
-
click(clickEvent({ href: '/missing' }))
|
|
235
|
-
await new Promise(r => setTimeout(r, 10))
|
|
236
|
-
assert.equal(fallback, '/missing')
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
// ── Navigation — JSON response ───────────────────────────────────────────────
|
|
240
|
-
|
|
241
|
-
console.log('\nNavigation — JSON response\n')
|
|
242
|
-
|
|
243
|
-
function jsonFetch(payload) {
|
|
244
|
-
return async () => ({
|
|
245
|
-
ok: true,
|
|
246
|
-
headers: { get: () => 'application/json' },
|
|
247
|
-
json: async () => payload,
|
|
248
|
-
})
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
await test('sets root.innerHTML from JSON html field', async () => {
|
|
252
|
-
const { click, root } = setup()
|
|
253
|
-
global.fetch = jsonFetch({ html: '<p>Hello</p>', title: 'T', styles: [], scripts: [], hydrate: null, serverState: {}, storeState: null })
|
|
254
|
-
click(clickEvent({ href: '/new' }))
|
|
255
|
-
await new Promise(r => setTimeout(r, 10))
|
|
256
|
-
assert.equal(root.innerHTML, '<p>Hello</p>')
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
await test('updates document.title from JSON title field', async () => {
|
|
260
|
-
const { click } = setup()
|
|
261
|
-
global.fetch = jsonFetch({ html: '', title: 'New Title', styles: [], scripts: [], hydrate: null, serverState: {}, storeState: null })
|
|
262
|
-
click(clickEvent({ href: '/titled' }))
|
|
263
|
-
await new Promise(r => setTimeout(r, 10))
|
|
264
|
-
assert.equal(global.document.title, 'New Title')
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
await test('updates store state when storeState is present', async () => {
|
|
268
|
-
const { click } = setup()
|
|
269
|
-
global.fetch = jsonFetch({ html: '', title: '', styles: [], scripts: [], hydrate: null, serverState: {}, storeState: { theme: 'dark' } })
|
|
270
|
-
let stored = null
|
|
271
|
-
global.window.__updatePulseStore__ = (s) => { stored = s }
|
|
272
|
-
click(clickEvent({ href: '/store-page' }))
|
|
273
|
-
await new Promise(r => setTimeout(r, 10))
|
|
274
|
-
assert.deepEqual(stored, { theme: 'dark' })
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
// ── Navigation — popstate ────────────────────────────────────────────────────
|
|
278
|
-
|
|
279
|
-
console.log('\nNavigation — popstate\n')
|
|
280
|
-
|
|
281
|
-
await test('navigates on pulse popstate', async () => {
|
|
282
|
-
const { pop } = setup()
|
|
283
|
-
let fetchedPath = null
|
|
284
|
-
global.fetch = async (path) => { fetchedPath = path; return new Promise(() => {}) }
|
|
285
|
-
global.location.pathname = '/history-page'
|
|
286
|
-
global.location.search = ''
|
|
287
|
-
pop({ state: { pulse: true } })
|
|
288
|
-
await new Promise(r => setTimeout(r, 0))
|
|
289
|
-
assert.equal(fetchedPath, '/history-page')
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
await test('ignores popstate without pulse state', async () => {
|
|
293
|
-
const { pop } = setup()
|
|
294
|
-
let called = false
|
|
295
|
-
global.fetch = async () => { called = true; return new Promise(() => {}) }
|
|
296
|
-
pop({ state: null })
|
|
297
|
-
pop({ state: { pulse: false } })
|
|
298
|
-
assert(!called, 'fetch should not be called for non-pulse popstate')
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
// ── Summary ───────────────────────────────────────────────────────────────────
|
|
302
|
-
|
|
303
|
-
console.log(`\n${passed + failed} tests: ${passed} passed, ${failed} failed\n`)
|
|
304
|
-
if (failed > 0) process.exit(1)
|