@pygmalionjs/pygmalion 0.5.4 → 0.5.6
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/dist-lib/testing.js
CHANGED
|
@@ -1384,6 +1384,140 @@ export function storyboardEnvironmentBootstrapSource() {
|
|
|
1384
1384
|
});
|
|
1385
1385
|
}
|
|
1386
1386
|
|
|
1387
|
+
// Request outcomes and request recording share one interception point: the
|
|
1388
|
+
// frame's own traffic is both what the editor asks about and what a condition
|
|
1389
|
+
// acts on, so recording is the enumeration and conditions are the answer.
|
|
1390
|
+
const network = plainRecord(environment.network);
|
|
1391
|
+
if (network) {
|
|
1392
|
+
const conditions = Array.isArray(network.conditions)
|
|
1393
|
+
? network.conditions.filter((condition) => {
|
|
1394
|
+
const record = plainRecord(condition);
|
|
1395
|
+
return (
|
|
1396
|
+
record &&
|
|
1397
|
+
typeof record.match === 'string' &&
|
|
1398
|
+
record.match.length > 0 &&
|
|
1399
|
+
plainRecord(record.outcome) &&
|
|
1400
|
+
['fail', 'empty', 'slow'].includes(record.outcome.kind)
|
|
1401
|
+
);
|
|
1402
|
+
})
|
|
1403
|
+
: [];
|
|
1404
|
+
const recorded = network.record === true ? [] : null;
|
|
1405
|
+
if (recorded) {
|
|
1406
|
+
Object.defineProperty(globalThis, '__PYGMALION_REQUESTS__', {
|
|
1407
|
+
configurable: true,
|
|
1408
|
+
value: recorded,
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
const methodOf = (init, fallback) =>
|
|
1412
|
+
String(init?.method ?? fallback ?? 'GET').toUpperCase();
|
|
1413
|
+
const matchFor = (url, method) =>
|
|
1414
|
+
conditions.find(
|
|
1415
|
+
(condition) =>
|
|
1416
|
+
url.includes(condition.match) &&
|
|
1417
|
+
(condition.method == null ||
|
|
1418
|
+
String(condition.method).toUpperCase() === method),
|
|
1419
|
+
) ?? null;
|
|
1420
|
+
const wait = (ms) =>
|
|
1421
|
+
new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0)));
|
|
1422
|
+
|
|
1423
|
+
if (typeof globalThis.fetch === 'function') {
|
|
1424
|
+
const originalFetch = globalThis.fetch.bind(globalThis);
|
|
1425
|
+
Object.defineProperty(globalThis, 'fetch', {
|
|
1426
|
+
configurable: true,
|
|
1427
|
+
writable: true,
|
|
1428
|
+
async value(input, init) {
|
|
1429
|
+
const url = String(
|
|
1430
|
+
typeof input === 'string' ? input : (input?.url ?? input),
|
|
1431
|
+
);
|
|
1432
|
+
const method = methodOf(init, input?.method);
|
|
1433
|
+
if (recorded) recorded.push({ url, method });
|
|
1434
|
+
const condition = matchFor(url, method);
|
|
1435
|
+
if (!condition) return originalFetch(input, init);
|
|
1436
|
+
const outcome = condition.outcome;
|
|
1437
|
+
if (outcome.kind === 'slow') {
|
|
1438
|
+
await wait(outcome.delayMs);
|
|
1439
|
+
return originalFetch(input, init);
|
|
1440
|
+
}
|
|
1441
|
+
if (outcome.kind === 'empty') {
|
|
1442
|
+
// A well-formed response with nothing in it — the empty state is a
|
|
1443
|
+
// success path, so failing here would exercise the wrong branch.
|
|
1444
|
+
return new Response(String(outcome.body ?? ''), {
|
|
1445
|
+
status: Number(outcome.status ?? 200),
|
|
1446
|
+
headers: { 'content-type': 'application/json' },
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
const status = Number(outcome.status ?? 0);
|
|
1450
|
+
if (status > 0) {
|
|
1451
|
+
return new Response(null, {
|
|
1452
|
+
status,
|
|
1453
|
+
statusText: String(outcome.message ?? 'Pygmalion request condition'),
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
throw new TypeError(
|
|
1457
|
+
String(outcome.message ?? 'Pygmalion request condition: failed'),
|
|
1458
|
+
);
|
|
1459
|
+
},
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
const XHR = globalThis.XMLHttpRequest;
|
|
1464
|
+
if (typeof XHR === 'function' && XHR.prototype) {
|
|
1465
|
+
const originalOpen = XHR.prototype.open;
|
|
1466
|
+
const originalSend = XHR.prototype.send;
|
|
1467
|
+
const state = new WeakMap();
|
|
1468
|
+
Object.defineProperty(XHR.prototype, 'open', {
|
|
1469
|
+
configurable: true,
|
|
1470
|
+
value(method, url, ...rest) {
|
|
1471
|
+
state.set(this, {
|
|
1472
|
+
url: String(url),
|
|
1473
|
+
method: String(method ?? 'GET').toUpperCase(),
|
|
1474
|
+
});
|
|
1475
|
+
return originalOpen.call(this, method, url, ...rest);
|
|
1476
|
+
},
|
|
1477
|
+
});
|
|
1478
|
+
Object.defineProperty(XHR.prototype, 'send', {
|
|
1479
|
+
configurable: true,
|
|
1480
|
+
value(body) {
|
|
1481
|
+
const opened = state.get(this);
|
|
1482
|
+
if (!opened) return originalSend.call(this, body);
|
|
1483
|
+
if (recorded) recorded.push({ ...opened });
|
|
1484
|
+
const condition = matchFor(opened.url, opened.method);
|
|
1485
|
+
if (!condition) return originalSend.call(this, body);
|
|
1486
|
+
const outcome = condition.outcome;
|
|
1487
|
+
if (outcome.kind === 'slow') {
|
|
1488
|
+
wait(outcome.delayMs).then(() => originalSend.call(this, body));
|
|
1489
|
+
return undefined;
|
|
1490
|
+
}
|
|
1491
|
+
// The event order matters to callers: readyState settles, then the
|
|
1492
|
+
// terminal event fires, the same way the real object behaves.
|
|
1493
|
+
const fail = () => {
|
|
1494
|
+
Object.defineProperty(this, 'readyState', {
|
|
1495
|
+
configurable: true,
|
|
1496
|
+
value: 4,
|
|
1497
|
+
});
|
|
1498
|
+
if (outcome.kind === 'empty') {
|
|
1499
|
+
Object.defineProperties(this, {
|
|
1500
|
+
status: { configurable: true, value: Number(outcome.status ?? 200) },
|
|
1501
|
+
responseText: { configurable: true, value: String(outcome.body ?? '') },
|
|
1502
|
+
response: { configurable: true, value: String(outcome.body ?? '') },
|
|
1503
|
+
});
|
|
1504
|
+
this.dispatchEvent(new Event('load'));
|
|
1505
|
+
} else {
|
|
1506
|
+
Object.defineProperty(this, 'status', {
|
|
1507
|
+
configurable: true,
|
|
1508
|
+
value: Number(outcome.status ?? 0),
|
|
1509
|
+
});
|
|
1510
|
+
this.dispatchEvent(new Event('error'));
|
|
1511
|
+
}
|
|
1512
|
+
this.dispatchEvent(new Event('loadend'));
|
|
1513
|
+
};
|
|
1514
|
+
setTimeout(fail, 0);
|
|
1515
|
+
return undefined;
|
|
1516
|
+
},
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1387
1521
|
const permissions = plainRecord(environment.permissions);
|
|
1388
1522
|
if (permissions && navigator.permissions?.query) {
|
|
1389
1523
|
const originalQuery = navigator.permissions.query.bind(navigator.permissions);
|
package/node/storyboard.mjs
CHANGED
|
@@ -50,3 +50,11 @@ export {
|
|
|
50
50
|
validateRoutePreviewArtifactBundle,
|
|
51
51
|
validateRoutePreviewArtifactV3,
|
|
52
52
|
} from './route-preview-artifact-v3.mjs';
|
|
53
|
+
|
|
54
|
+
// The source scan, so a consumer's own gate can ask the route question outside
|
|
55
|
+
// a running Vite server: the scan lists the routes the codebase can render and
|
|
56
|
+
// `enumerateRouteCandidates` turns the ones with no screen case into demands.
|
|
57
|
+
export {
|
|
58
|
+
PYGMALION_STORYBOARD_ENVIRONMENT_QUERY,
|
|
59
|
+
scanStoryboardEnvironment,
|
|
60
|
+
} from './storyboard-environment.mjs';
|