@lingxia/types 0.18.0 → 0.20.0
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/automation/index.d.ts +937 -96
- package/dist/automation/index.d.ts.map +1 -1
- package/dist/automation/index.js +61 -3
- package/dist/automation/index.js.map +1 -1
- package/dist/esm/automation/index.js +60 -4
- package/dist/esm/automation/index.js.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/mocks.js +2 -0
- package/dist/esm/mocks.js.map +1 -0
- package/dist/esm/page.js +2 -0
- package/dist/esm/page.js.map +1 -0
- package/dist/esm/testing/public-api.js +61 -2
- package/dist/esm/testing/public-api.js.map +1 -1
- package/dist/generated/logic.d.ts +40 -5
- package/dist/generated/logic.d.ts.map +1 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mocks.d.ts +58 -0
- package/dist/mocks.d.ts.map +1 -0
- package/dist/mocks.js +3 -0
- package/dist/mocks.js.map +1 -0
- package/dist/page.d.ts +26 -0
- package/dist/page.d.ts.map +1 -0
- package/dist/page.js +3 -0
- package/dist/page.js.map +1 -0
- package/dist/testing/public-api.d.ts +59 -8
- package/dist/testing/public-api.d.ts.map +1 -1
- package/dist/testing/public-api.js +61 -2
- package/dist/testing/public-api.js.map +1 -1
- package/package.json +7 -4
- package/src/automation/index.ts +987 -97
- package/src/generated/logic.ts +42 -5
- package/src/index.ts +10 -4
- package/src/mocks.ts +62 -0
- package/src/page.ts +26 -0
- package/src/testing/public-api.ts +75 -2
- package/dist/automation-test-globals.d.ts +0 -14
package/src/automation/index.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* In-process UI/runtime automation — `lx.automation()`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Two roots share one runtime object:
|
|
5
|
+
*
|
|
6
|
+
* - {@link Automation} is what app Logic may call. Selecting the calling lxapp
|
|
7
|
+
* requires the `automation` security privilege; cross-lxapp and host
|
|
8
|
+
* surfaces require `host`. A session holds one only when the privilege
|
|
9
|
+
* class is allowed for the app (the app registry, or its unrestricted
|
|
10
|
+
* default) and the native host seals a matching session grant — from a
|
|
11
|
+
* product `HostAddon`, or a devtools host such as the Runner, which grants
|
|
12
|
+
* the allowed automation privileges and nothing beyond them. Neither
|
|
13
|
+
* `lingxia dev` nor the Runner widens what app Logic is allowed.
|
|
14
|
+
* - {@link HostRunAutomation} is the root of a host automation run
|
|
15
|
+
* (`lxdev test`). That context carries the host's own authority, so every
|
|
16
|
+
* selector is available without a grant, and it adds the test-run-only
|
|
17
|
+
* members: `network`, nav `waitUntil: 'ready'`, and eval call tracing.
|
|
7
18
|
*
|
|
8
19
|
* This mirrors the devtool (`lxdev`) automation surface as a privilege-scoped,
|
|
9
20
|
* product-side API.
|
|
@@ -13,30 +24,113 @@ import type { TerminalSettingsValue } from '../generated/logic.js';
|
|
|
13
24
|
|
|
14
25
|
// ============================ factory ============================
|
|
15
26
|
|
|
16
|
-
|
|
27
|
+
// ============================ errors ============================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Stable `code`s of automation driver rejections. Anything without a more
|
|
31
|
+
* specific code rejects with `E_AUTOMATION`. Desktop codes describe OS
|
|
32
|
+
* automation failures. Match on the code, not the message.
|
|
33
|
+
*/
|
|
34
|
+
export const AUTOMATION_ERROR_CODES = [
|
|
35
|
+
/** Fallback for a failure with no more specific code. */
|
|
36
|
+
'E_AUTOMATION',
|
|
37
|
+
/** A scenario changed only partially or its outcome is unknown; the run is revoked. */
|
|
38
|
+
'E_SCENARIO_STATE_UNKNOWN',
|
|
39
|
+
/** The caller lacks the `automation`/`host` privilege the call needs. */
|
|
40
|
+
'E_AUTOMATION_PRIVILEGE',
|
|
41
|
+
/** The target page is not the active instance: not open, or replaced. `data: { page, instanceId?, current? }`. */
|
|
42
|
+
'E_PAGE_NOT_ACTIVE',
|
|
43
|
+
/** The page has no WebView or current page to act on yet. */
|
|
44
|
+
'E_PAGE_NOT_READY',
|
|
45
|
+
/** A page action (`page.action`) rejected without a code of its own. `data: { action, cause }`. */
|
|
46
|
+
'E_PAGE_ACTION',
|
|
47
|
+
/** No element matched the selector at dispatch. */
|
|
48
|
+
'E_ELEMENT_NOT_FOUND',
|
|
49
|
+
/** The element matched but cannot take the input (disabled, not editable, …). */
|
|
50
|
+
'E_ELEMENT_NOT_INTERACTABLE',
|
|
51
|
+
/** A driver wait (`waitFor`, `waitUntil: 'ready'`, …) ran out of time. */
|
|
52
|
+
'E_AUTOMATION_TIMEOUT',
|
|
53
|
+
/** The evaluated script threw. */
|
|
54
|
+
'E_EVAL_SCRIPT',
|
|
55
|
+
/** The evaluation did not settle within its `timeoutMs`. */
|
|
56
|
+
'E_EVAL_TIMEOUT',
|
|
57
|
+
/** Profile rollback outside an isolated run (`lxdev test --profile`), or for an lxapp the run does not isolate. */
|
|
58
|
+
'E_PROFILE_NOT_ISOLATED',
|
|
59
|
+
/** A test clock call needs an installed clock and none is; an app that reopened is back on real time. */
|
|
60
|
+
'E_CLOCK_NOT_INSTALLED',
|
|
61
|
+
/** `clock.install()` while this lxapp's Logic already runs on a test clock. */
|
|
62
|
+
'E_CLOCK_INSTALLED',
|
|
63
|
+
'E_DESKTOP_USAGE',
|
|
64
|
+
'E_DESKTOP_NOT_FOUND',
|
|
65
|
+
'E_DESKTOP_AMBIGUOUS',
|
|
66
|
+
'E_DESKTOP_TIMEOUT',
|
|
67
|
+
'E_DESKTOP_PERMISSION',
|
|
68
|
+
'E_DESKTOP_UNSUPPORTED',
|
|
69
|
+
'E_DESKTOP_UNAVAILABLE',
|
|
70
|
+
'E_DESKTOP_STALE',
|
|
71
|
+
'E_DESKTOP_FAILED',
|
|
72
|
+
] as const;
|
|
73
|
+
|
|
74
|
+
export type AutomationErrorCode = (typeof AUTOMATION_ERROR_CODES)[number];
|
|
75
|
+
|
|
76
|
+
/** A page as automation error `data` names it. */
|
|
77
|
+
export interface AutomationPageRef {
|
|
78
|
+
/** Configured page name, else its path. */
|
|
79
|
+
page?: string;
|
|
80
|
+
/** The page instance the call resolved to, when it still resolves. */
|
|
81
|
+
instanceId?: string;
|
|
82
|
+
/** The page that was current when the call failed. */
|
|
83
|
+
current?: { name: string | null; path: string; instanceId: string };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Automation root as app Logic sees it (`lx.automation()` in Logic). It grants
|
|
88
|
+
* no capability until one is selected. Reading a driver property never throws:
|
|
89
|
+
* each driver method checks the caller and rejects with
|
|
90
|
+
* `E_AUTOMATION_PRIVILEGE` when it is not allowed.
|
|
91
|
+
*/
|
|
17
92
|
export interface Automation {
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Drive the calling lxapp. Requires the `automation` privilege. Evaluating
|
|
95
|
+
* the calling Logic runtime itself (`eval`) rejects.
|
|
96
|
+
*/
|
|
97
|
+
lxapp(): LogicLxAppDriver;
|
|
98
|
+
/** Drive a specific running lxapp. Requires the `host` privilege. */
|
|
99
|
+
lxapp(appid: string): LogicLxAppDriver;
|
|
100
|
+
/** Cross-lxapp lifecycle and host-window capture. Requires `host`. */
|
|
23
101
|
readonly lxapps: LxAppManager;
|
|
24
|
-
/** The host app's browser tabs. */
|
|
102
|
+
/** The host app's browser tabs. Requires `host`. */
|
|
25
103
|
readonly browser: BrowserDriver;
|
|
26
|
-
/** Persisted host-shell shortcuts for deterministic test setup/assertion. */
|
|
104
|
+
/** Persisted host-shell shortcuts for deterministic test setup/assertion. Requires `host`. */
|
|
27
105
|
readonly shell: ShellDriver;
|
|
28
|
-
/** Simulated-device selection in a host runner. */
|
|
106
|
+
/** Simulated-device selection in a host runner. Requires `host`. */
|
|
29
107
|
readonly device: DeviceDriver;
|
|
30
108
|
/**
|
|
31
|
-
* Session-less local-OS desktop automation (`lxdev desktop`).
|
|
32
|
-
*
|
|
33
|
-
*
|
|
109
|
+
* Session-less local-OS desktop automation (`lxdev desktop`). Windows/macOS
|
|
110
|
+
* only. Gated by the `host` privilege alone, and present only in hosts
|
|
111
|
+
* built with desktop automation (the Runner, or a product that enables the
|
|
112
|
+
* `desktop-automation` feature); elsewhere reading it throws. It drives the
|
|
113
|
+
* whole OS, beyond the app sandbox — grant `host` only to lxapps you trust
|
|
114
|
+
* with that.
|
|
34
115
|
*/
|
|
35
116
|
readonly desktop: DesktopDriver;
|
|
36
|
-
/** Native terminal workspace state and pane actions
|
|
117
|
+
/** Native terminal workspace state and pane actions. Requires `host`. */
|
|
37
118
|
readonly terminal: TerminalDriver;
|
|
38
119
|
}
|
|
39
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Automation root of a host automation run — `rawAutomation()` (and, traced,
|
|
123
|
+
* `t.automation`) from `@lingxia/test` in an `lxdev test` program. The run
|
|
124
|
+
* carries host authority, so no selector needs a privilege grant, and the
|
|
125
|
+
* selected lxapp driver adds the test-run-only members.
|
|
126
|
+
*/
|
|
127
|
+
export interface HostRunAutomation extends Automation {
|
|
128
|
+
/** Drive the host's current lxapp. */
|
|
129
|
+
lxapp(): LxAppDriver;
|
|
130
|
+
/** Drive a specific running lxapp. */
|
|
131
|
+
lxapp(appid: string): LxAppDriver;
|
|
132
|
+
}
|
|
133
|
+
|
|
40
134
|
// ========================== terminal tier ==========================
|
|
41
135
|
|
|
42
136
|
export type TerminalSplitDirection = 'left' | 'right' | 'up' | 'down';
|
|
@@ -114,7 +208,10 @@ export interface TerminalSplitOptions extends TerminalSurfaceRef {
|
|
|
114
208
|
direction: TerminalSplitDirection;
|
|
115
209
|
}
|
|
116
210
|
|
|
117
|
-
/**
|
|
211
|
+
/**
|
|
212
|
+
* Native terminal automation. Requires the `host` privilege from app Logic
|
|
213
|
+
* (a host automation run needs none) and a host with a native terminal.
|
|
214
|
+
*/
|
|
118
215
|
export interface TerminalDriver {
|
|
119
216
|
snapshot(options: TerminalSurfaceRef): Promise<TerminalWorkspaceSnapshot>;
|
|
120
217
|
/** Send text to the focused pane, as if typed into its PTY. */
|
|
@@ -186,6 +283,15 @@ export interface PageEvalOptions extends PageTarget {
|
|
|
186
283
|
timeoutMs?: number;
|
|
187
284
|
}
|
|
188
285
|
|
|
286
|
+
export interface PageActionOptions extends PageTarget {
|
|
287
|
+
/** Name of a unary action of the page's `Page({...})`; stream (generator) actions are refused. */
|
|
288
|
+
name: string;
|
|
289
|
+
/** The action's one JSON argument, passed as is; omit for none. */
|
|
290
|
+
payload?: unknown;
|
|
291
|
+
/** Budget for the page to become ready and the action to settle (default 5000). */
|
|
292
|
+
timeoutMs?: number;
|
|
293
|
+
}
|
|
294
|
+
|
|
189
295
|
export interface PageQueryOptions extends PageTarget {
|
|
190
296
|
/** CSS selector. */
|
|
191
297
|
css: string;
|
|
@@ -209,6 +315,20 @@ export interface PageTypeOptions extends PageSelectorOptions {
|
|
|
209
315
|
text: string;
|
|
210
316
|
}
|
|
211
317
|
|
|
318
|
+
export interface PageClickOptions extends PageSelectorOptions {
|
|
319
|
+
/**
|
|
320
|
+
* Dispatch to the element itself, without the in-viewport and hit-test
|
|
321
|
+
* checks; it must still exist and be enabled. For content no scroll can
|
|
322
|
+
* bring under the pointer, such as the lower part of an overflowing sheet.
|
|
323
|
+
*/
|
|
324
|
+
force?: boolean;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export interface PageFillOptions extends PageTypeOptions {
|
|
328
|
+
/** Write without the in-viewport check; see `PageClickOptions.force`. */
|
|
329
|
+
force?: boolean;
|
|
330
|
+
}
|
|
331
|
+
|
|
212
332
|
export interface PagePressOptions extends PageTarget {
|
|
213
333
|
/** Key name, e.g. `Enter`, `Escape`, `Tab`. */
|
|
214
334
|
key: string;
|
|
@@ -230,23 +350,31 @@ export interface PageScrollToOptions extends PageTarget {
|
|
|
230
350
|
css: string;
|
|
231
351
|
}
|
|
232
352
|
|
|
353
|
+
/**
|
|
354
|
+
* Raw `page.waitFor` states check the first match of `css`:
|
|
355
|
+
* - `attached`: at least one match.
|
|
356
|
+
* - `detached`: no match (also while the page is not yet active).
|
|
357
|
+
* - `visible`: the first match is rendered (a non-empty box, not
|
|
358
|
+
* `display:none`, `visibility:hidden` or `opacity:0`), in the viewport or
|
|
359
|
+
* scrolled out of it.
|
|
360
|
+
* - `hidden`: the first match exists and is not rendered; no match does not
|
|
361
|
+
* satisfy it (wait for `detached`).
|
|
362
|
+
* - `enabled` / `editable`: the first match exists and is enabled / editable.
|
|
363
|
+
* `@lingxia/test` locators apply stricter, uniqueness-aware states.
|
|
364
|
+
*/
|
|
233
365
|
export type PageWaitState =
|
|
234
366
|
| 'attached'
|
|
235
367
|
| 'detached'
|
|
236
368
|
| 'visible'
|
|
237
369
|
| 'hidden'
|
|
238
370
|
| 'enabled'
|
|
239
|
-
| 'editable'
|
|
240
|
-
/** @deprecated Use `attached`. */
|
|
241
|
-
| 'exists'
|
|
242
|
-
/** @deprecated Use `detached`. */
|
|
243
|
-
| 'gone';
|
|
371
|
+
| 'editable';
|
|
244
372
|
|
|
245
373
|
export interface PageWaitForOptions extends PageTarget {
|
|
246
374
|
css: string;
|
|
247
375
|
/** Condition to await (default `visible`). */
|
|
248
376
|
state?: PageWaitState;
|
|
249
|
-
/** Timeout in ms (default
|
|
377
|
+
/** Timeout in ms (default 30000, capped at 60000). */
|
|
250
378
|
timeoutMs?: number;
|
|
251
379
|
}
|
|
252
380
|
|
|
@@ -263,13 +391,13 @@ export interface ElementRect {
|
|
|
263
391
|
height: number;
|
|
264
392
|
right: number;
|
|
265
393
|
bottom: number;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
394
|
+
centerX: number;
|
|
395
|
+
centerY: number;
|
|
396
|
+
viewportWidth: number;
|
|
397
|
+
viewportHeight: number;
|
|
270
398
|
}
|
|
271
399
|
|
|
272
|
-
/** A matched element.
|
|
400
|
+
/** A matched element. Uses JavaScript field names. */
|
|
273
401
|
export interface PageElement {
|
|
274
402
|
exists: true;
|
|
275
403
|
index: number;
|
|
@@ -281,16 +409,21 @@ export interface PageElement {
|
|
|
281
409
|
id: string | null;
|
|
282
410
|
name: string | null;
|
|
283
411
|
role: string | null;
|
|
284
|
-
|
|
412
|
+
ariaLabel: string | null;
|
|
285
413
|
placeholder: string | null;
|
|
286
|
-
/**
|
|
414
|
+
/**
|
|
415
|
+
* Rendered: a non-empty box that is not `display:none`,
|
|
416
|
+
* `visibility:hidden` or `opacity:0`, wherever it is scrolled.
|
|
417
|
+
*/
|
|
287
418
|
visible: boolean;
|
|
419
|
+
/** Rendered and intersecting the viewport. */
|
|
420
|
+
inViewport: boolean;
|
|
288
421
|
enabled: boolean;
|
|
289
422
|
editable: boolean;
|
|
290
423
|
text: string;
|
|
291
|
-
|
|
424
|
+
textTruncated: boolean;
|
|
292
425
|
value: string | null;
|
|
293
|
-
|
|
426
|
+
valueTruncated: boolean;
|
|
294
427
|
rect: ElementRect;
|
|
295
428
|
}
|
|
296
429
|
|
|
@@ -300,6 +433,7 @@ export interface PageElementMiss {
|
|
|
300
433
|
index: number;
|
|
301
434
|
count: number;
|
|
302
435
|
visible: false;
|
|
436
|
+
inViewport: false;
|
|
303
437
|
enabled: false;
|
|
304
438
|
editable: false;
|
|
305
439
|
}
|
|
@@ -324,17 +458,25 @@ export interface Screenshot {
|
|
|
324
458
|
export interface PageDriver {
|
|
325
459
|
/** Evaluate in the page WebView. T describes the expected JSON result; it is not runtime validation. */
|
|
326
460
|
eval<T = unknown>(options: PageEvalOptions): Promise<T>;
|
|
461
|
+
/**
|
|
462
|
+
* Invoke a page action as the View would and resolve to its JSON result.
|
|
463
|
+
* Waits for the page's runtime within `timeoutMs`. A rejection keeps the
|
|
464
|
+
* action's own `code` and message (`data.cause` has what it rejected with);
|
|
465
|
+
* `E_PAGE_NOT_READY` if the page never loaded its actions,
|
|
466
|
+
* `E_AUTOMATION_TIMEOUT` if the action did not settle in time.
|
|
467
|
+
*/
|
|
468
|
+
action<T = unknown>(options: PageActionOptions): Promise<T>;
|
|
327
469
|
/** Query one element's info. */
|
|
328
470
|
query(options: PageQueryOptions & { all?: false }): Promise<PageQueryResult>;
|
|
329
471
|
/** Query every matching element. */
|
|
330
472
|
query(options: PageQueryOptions & { all: true }): Promise<PageQueryAll>;
|
|
331
473
|
query(options: PageQueryOptions): Promise<PageQueryResult | PageQueryAll>;
|
|
332
474
|
/** Single dispatch; test locators provide actionability waiting. */
|
|
333
|
-
click(options:
|
|
475
|
+
click(options: PageClickOptions): Promise<void>;
|
|
334
476
|
/** Type text into an element without clearing existing content. */
|
|
335
477
|
type(options: PageTypeOptions): Promise<void>;
|
|
336
478
|
/** Replace an element's current value. */
|
|
337
|
-
fill(options:
|
|
479
|
+
fill(options: PageFillOptions): Promise<void>;
|
|
338
480
|
press(options: PagePressOptions): Promise<void>;
|
|
339
481
|
/** Scroll the first matching element into view. */
|
|
340
482
|
scrollTo(options: PageScrollToOptions): Promise<void>;
|
|
@@ -351,56 +493,104 @@ export interface PageDriver {
|
|
|
351
493
|
|
|
352
494
|
// ============================ nav tier ============================
|
|
353
495
|
|
|
354
|
-
|
|
496
|
+
/**
|
|
497
|
+
* When a nav action resolves. `'commit'` (default) resolves once the page
|
|
498
|
+
* stack changed, before the landed page is ready. `'ready'` also waits for its
|
|
499
|
+
* `onReady` and rejects if the page is disposed or replaced first (e.g. by the
|
|
500
|
+
* app's own `lx.reLaunch`); it is available only in a host automation run
|
|
501
|
+
* ({@link NavDriver}), since app Logic awaiting its own `onReady` would
|
|
502
|
+
* deadlock. The `@lingxia/test` fixture's `t.app.nav` defaults to `'ready'`.
|
|
503
|
+
*/
|
|
504
|
+
export type NavWaitUntil = 'commit' | 'ready';
|
|
505
|
+
|
|
506
|
+
/** Nav wait options available to app Logic: only `'commit'`. */
|
|
507
|
+
export interface LogicNavWaitOptions {
|
|
508
|
+
waitUntil?: 'commit';
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** Nav wait options of a host automation run. */
|
|
512
|
+
export interface NavWaitOptions {
|
|
513
|
+
waitUntil?: NavWaitUntil;
|
|
514
|
+
/** Bound for `waitUntil: 'ready'` in ms (default 15000, capped at 60000). */
|
|
515
|
+
timeoutMs?: number;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
interface NavTargetFields {
|
|
355
519
|
/** Configured page name (from lxapp.json). */
|
|
356
520
|
page: string;
|
|
357
521
|
/** Query forwarded to the destination page. */
|
|
358
522
|
query?: Record<string, unknown>;
|
|
359
523
|
}
|
|
360
524
|
|
|
361
|
-
|
|
525
|
+
interface NavBackFields {
|
|
362
526
|
/** Number of pages to pop (default 1). */
|
|
363
527
|
delta?: number;
|
|
364
528
|
}
|
|
365
529
|
|
|
530
|
+
export interface LogicNavOptions extends NavTargetFields, LogicNavWaitOptions {}
|
|
531
|
+
export interface LogicNavBackOptions extends NavBackFields, LogicNavWaitOptions {}
|
|
532
|
+
export interface NavOptions extends NavTargetFields, NavWaitOptions {}
|
|
533
|
+
export interface NavBackOptions extends NavBackFields, NavWaitOptions {}
|
|
534
|
+
|
|
366
535
|
/** A page's runtime position. */
|
|
367
536
|
export interface PageInfo {
|
|
368
537
|
path: string;
|
|
369
538
|
/** Configured page name, if the path maps to one. */
|
|
370
539
|
name: string | null;
|
|
540
|
+
/**
|
|
541
|
+
* The page instance. Navigation that replaces a page (even with the same
|
|
542
|
+
* path) gives it a new id. `null` when no live instance backs a stack
|
|
543
|
+
* entry.
|
|
544
|
+
*/
|
|
545
|
+
instanceId: string | null;
|
|
371
546
|
current: boolean;
|
|
372
547
|
inStack: boolean;
|
|
373
|
-
/** Whether the page has
|
|
548
|
+
/** Whether the page has dispatched `onReady` (what `waitUntil: 'ready'` awaits). */
|
|
374
549
|
ready: boolean;
|
|
550
|
+
/** Whether the page currently has an attached WebView; precedes `ready`. */
|
|
551
|
+
webviewAttached: boolean;
|
|
375
552
|
}
|
|
376
553
|
|
|
377
554
|
/**
|
|
378
|
-
* Page-stack navigation for the selected lxapp
|
|
379
|
-
* page name (`redirect` rejects a tab-bar page);
|
|
380
|
-
* read. Unlike the JS `lx.navigateTo` family
|
|
381
|
-
*
|
|
382
|
-
*
|
|
555
|
+
* Page-stack navigation for the selected lxapp, as app Logic sees it. Action
|
|
556
|
+
* verbs take a configured page name (`redirect` rejects a tab-bar page);
|
|
557
|
+
* `back` pops; `current`/`stack` read. Unlike the JS `lx.navigateTo` family
|
|
558
|
+
* this returns the landed page. It resolves once the stack changed, before the
|
|
559
|
+
* destination is ready.
|
|
383
560
|
*/
|
|
384
|
-
export interface
|
|
561
|
+
export interface LogicNavDriver {
|
|
385
562
|
/** Push a page onto the stack. */
|
|
386
|
-
to(options:
|
|
563
|
+
to(options: LogicNavOptions): Promise<PageInfo>;
|
|
387
564
|
/** Replace the current page (rejects tab-bar targets). */
|
|
388
|
-
redirect(options:
|
|
565
|
+
redirect(options: LogicNavOptions): Promise<PageInfo>;
|
|
389
566
|
/** Switch to a configured tab page. */
|
|
390
|
-
switchTab(options:
|
|
391
|
-
/**
|
|
392
|
-
relaunch(options:
|
|
393
|
-
back(options?:
|
|
567
|
+
switchTab(options: LogicNavOptions): Promise<PageInfo>;
|
|
568
|
+
/** Unload every page (cached tab pages included) and open a fresh instance of a page. */
|
|
569
|
+
relaunch(options: LogicNavOptions): Promise<PageInfo>;
|
|
570
|
+
back(options?: LogicNavBackOptions): Promise<PageInfo>;
|
|
394
571
|
current(): Promise<PageInfo>;
|
|
395
572
|
/** Status of a configured page by name; omit `page` for the current page. */
|
|
396
573
|
info(options?: PageTarget): Promise<PageInfo>;
|
|
397
574
|
stack(): Promise<PageInfo[]>;
|
|
398
575
|
}
|
|
399
576
|
|
|
577
|
+
/**
|
|
578
|
+
* Page-stack navigation in a host automation run. Same verbs as
|
|
579
|
+
* {@link LogicNavDriver}; pass `waitUntil: 'ready'` to also wait for the
|
|
580
|
+
* landed page's `onReady`.
|
|
581
|
+
*/
|
|
582
|
+
export interface NavDriver extends LogicNavDriver {
|
|
583
|
+
to(options: NavOptions): Promise<PageInfo>;
|
|
584
|
+
redirect(options: NavOptions): Promise<PageInfo>;
|
|
585
|
+
switchTab(options: NavOptions): Promise<PageInfo>;
|
|
586
|
+
relaunch(options: NavOptions): Promise<PageInfo>;
|
|
587
|
+
back(options?: NavBackOptions): Promise<PageInfo>;
|
|
588
|
+
}
|
|
589
|
+
|
|
400
590
|
// ============================ lxapp driver ============================
|
|
401
591
|
|
|
402
592
|
export interface LxAppSummary {
|
|
403
|
-
|
|
593
|
+
appId: string;
|
|
404
594
|
currentPage: string | null;
|
|
405
595
|
}
|
|
406
596
|
|
|
@@ -409,22 +599,33 @@ export interface LxAppPageConfig {
|
|
|
409
599
|
path: string;
|
|
410
600
|
}
|
|
411
601
|
|
|
602
|
+
/**
|
|
603
|
+
* Envelope `eval` resolves to with `captureCalls: true`.
|
|
604
|
+
*
|
|
605
|
+
* @internal Report plumbing for the test runner; the `@lingxia/test` fixture
|
|
606
|
+
* unwraps it, and specs never see it.
|
|
607
|
+
*/
|
|
412
608
|
export interface LxAppEvalTrace<T = unknown> {
|
|
413
609
|
__lxEval: 1;
|
|
414
610
|
value: T;
|
|
415
611
|
calls: string[];
|
|
416
612
|
}
|
|
417
613
|
|
|
418
|
-
|
|
614
|
+
/** Logic-runtime eval options available to app Logic. */
|
|
615
|
+
export interface LogicLxAppEvalOptions {
|
|
419
616
|
/** JavaScript expression or function body run in the selected Logic runtime. */
|
|
420
617
|
script: string;
|
|
421
618
|
timeoutMs?: number;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/** Logic-runtime eval options of a host automation run. */
|
|
622
|
+
export interface LxAppEvalOptions extends LogicLxAppEvalOptions {
|
|
422
623
|
/**
|
|
423
624
|
* Resolve to `{ value, calls }`, where `calls` lists the `lx.*` members the
|
|
424
625
|
* script reached, instead of the bare value.
|
|
425
626
|
*
|
|
426
|
-
* The test runner sets this so a report can tell a capability a
|
|
427
|
-
* exercised from one it merely declared. Only the evaluated script is
|
|
627
|
+
* @internal The test runner sets this so a report can tell a capability a
|
|
628
|
+
* spec exercised from one it merely declared. Only the evaluated script is
|
|
428
629
|
* observed — the lxapp's own concurrent work is not.
|
|
429
630
|
*/
|
|
430
631
|
captureCalls?: boolean;
|
|
@@ -528,18 +729,709 @@ export interface SurfaceLayoutSnapshot {
|
|
|
528
729
|
tree?: SurfaceLayoutTree;
|
|
529
730
|
}
|
|
530
731
|
|
|
531
|
-
|
|
532
|
-
|
|
732
|
+
// ======================= test network routing =======================
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Which Logic `fetch` requests a route handles: a URL glob over the whole URL
|
|
736
|
+
* (`**` any characters, `*` any except `/`, `{a,b}` alternatives; `?` is
|
|
737
|
+
* literal), a `RegExp` searched like `RegExp.test` (Rust regex syntax: no
|
|
738
|
+
* lookaround or backreferences), or either plus a method and a match budget.
|
|
739
|
+
*/
|
|
740
|
+
export type NetworkRoutePattern =
|
|
741
|
+
| string
|
|
742
|
+
| RegExp
|
|
743
|
+
| {
|
|
744
|
+
url: string | RegExp;
|
|
745
|
+
/** Case-insensitive HTTP method; omit or `'*'` for any. */
|
|
746
|
+
method?: string;
|
|
747
|
+
/** Remove the route after this many matched requests. */
|
|
748
|
+
times?: number;
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
type NetworkRouteFulfillKey =
|
|
752
|
+
| 'status'
|
|
753
|
+
| 'statusText'
|
|
754
|
+
| 'headers'
|
|
755
|
+
| 'contentType'
|
|
756
|
+
| 'body'
|
|
757
|
+
| 'json'
|
|
758
|
+
| 'delay';
|
|
759
|
+
type NetworkRouteForbid<K extends string> = Partial<Record<K, never>>;
|
|
760
|
+
|
|
761
|
+
interface NetworkRouteFulfillFields {
|
|
762
|
+
/** 200..=599. Default 200. */
|
|
763
|
+
status?: number;
|
|
764
|
+
statusText?: string;
|
|
765
|
+
headers?: Record<string, string>;
|
|
766
|
+
/** Sets `content-type` unless `headers` already has one. */
|
|
767
|
+
contentType?: string;
|
|
768
|
+
/**
|
|
769
|
+
* Milliseconds to wait before the response resolves, 0..=30000; an
|
|
770
|
+
* `AbortSignal` passed to `fetch` still rejects it early.
|
|
771
|
+
*/
|
|
772
|
+
delay?: number;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Answer the request without touching the network. The body is either `body`
|
|
777
|
+
* (text or bytes, sent verbatim) or `json` (serialized, implies
|
|
778
|
+
* `content-type: application/json`), never both.
|
|
779
|
+
*/
|
|
780
|
+
export type NetworkRouteFulfill = NetworkRouteFulfillFields &
|
|
781
|
+
NetworkRouteForbid<'abort' | 'continue' | 'patchJson' | 'hang' | 'sse' | 'sequence'> &
|
|
782
|
+
(
|
|
783
|
+
| { body?: string | ArrayBuffer | Uint8Array; json?: never }
|
|
784
|
+
| { json: unknown; body?: never }
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Reject the `fetch` like a transport failure (`TypeError: fetch failed`).
|
|
789
|
+
* `'failed'` is the only failure the wrapper emulates.
|
|
790
|
+
*/
|
|
791
|
+
export type NetworkRouteAbort = { abort: 'failed' } &
|
|
792
|
+
NetworkRouteForbid<NetworkRouteFulfillKey | 'continue' | 'patchJson' | 'hang' | 'sse' | 'sequence'>;
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Let the request reach the network, shadowing older matching routes. With
|
|
796
|
+
* `patchJson`, the real response's JSON body is rewritten host-side by that
|
|
797
|
+
* RFC 7396 merge patch: object keys merge, `null` deletes a key, and any
|
|
798
|
+
* other value (arrays included) replaces what it patches. Status and headers
|
|
799
|
+
* stay the real ones. A body that is not JSON rejects the `fetch` with a
|
|
800
|
+
* `TypeError` naming the URL; an empty body passes through. The patched
|
|
801
|
+
* body is re-serialized, so object keys come back sorted.
|
|
802
|
+
*/
|
|
803
|
+
export type NetworkRouteContinue = { continue: true; patchJson?: unknown } &
|
|
804
|
+
NetworkRouteForbid<NetworkRouteFulfillKey | 'abort' | 'hang' | 'sse' | 'sequence'>;
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Never answer: the `fetch` stays pending until the route is removed (the
|
|
808
|
+
* spec ends, `unroute()`, or the run ends), then rejects like a transport
|
|
809
|
+
* failure. An `AbortSignal` passed to `fetch` still rejects it early. For
|
|
810
|
+
* loading states and client-side timeouts. `times` limits which requests are
|
|
811
|
+
* held, not how long.
|
|
812
|
+
*/
|
|
813
|
+
export type NetworkRouteHang = { hang: true } &
|
|
814
|
+
NetworkRouteForbid<NetworkRouteFulfillKey | 'abort' | 'continue' | 'patchJson' | 'sse' | 'sequence'>;
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* One item of an SSE answer, played in order: an event (`data` that is not a
|
|
818
|
+
* string is sent as JSON; multi-line data becomes several `data:` lines), a
|
|
819
|
+
* `comment` line, a pause of `delayMs` (0..=30000), or `drop`, which closes
|
|
820
|
+
* the stream as a server dropping the connection would and must come last.
|
|
821
|
+
*/
|
|
822
|
+
export type NetworkSseItem =
|
|
823
|
+
| { event?: string; data: unknown; id?: string; retry?: number }
|
|
824
|
+
| { comment: string }
|
|
825
|
+
| { delayMs: number }
|
|
826
|
+
| { drop: true };
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Answer with a `text/event-stream` (status 200) that plays `sse`. Without a
|
|
830
|
+
* final `{ drop: true }` the stream stays open after its last item, like a
|
|
831
|
+
* live server, until the route is removed. `Rong.SSE` in Logic receives the
|
|
832
|
+
* events and, after a drop, reconnects with `Last-Event-ID`, which the next
|
|
833
|
+
* answer of a `sequence` can serve. `delay` (0..=30000 ms) holds the response
|
|
834
|
+
* before it opens.
|
|
835
|
+
*/
|
|
836
|
+
export type NetworkRouteSse = {
|
|
837
|
+
sse: NetworkSseItem[];
|
|
838
|
+
headers?: Record<string, string>;
|
|
839
|
+
delay?: number;
|
|
840
|
+
} & NetworkRouteForbid<
|
|
841
|
+
'status' | 'statusText' | 'contentType' | 'body' | 'json' | 'abort' | 'continue' | 'patchJson' | 'hang' | 'sequence'
|
|
842
|
+
>;
|
|
843
|
+
|
|
844
|
+
/** Exactly one of fulfill, abort, continue, hang, or sse. */
|
|
845
|
+
export type NetworkRouteAnswer =
|
|
846
|
+
| NetworkRouteFulfill
|
|
847
|
+
| NetworkRouteAbort
|
|
848
|
+
| NetworkRouteContinue
|
|
849
|
+
| NetworkRouteHang
|
|
850
|
+
| NetworkRouteSse;
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Answers served in call order: the first matched request gets the first
|
|
854
|
+
* answer, and the last answer repeats. Answers take text or `json` bodies,
|
|
855
|
+
* not bytes. Combine with a pattern's `times` to stop matching.
|
|
856
|
+
*/
|
|
857
|
+
export type NetworkRouteSequence = { sequence: NetworkRouteAnswer[] } &
|
|
858
|
+
NetworkRouteForbid<NetworkRouteFulfillKey | 'abort' | 'continue' | 'patchJson' | 'hang' | 'sse'>;
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* One answer, or a `sequence` of them. String values may contain
|
|
862
|
+
* relative-time templates rendered each time the answer is served:
|
|
863
|
+
* `{{now}}`, `{{now-2h}}`, `{{now+30m}}` (ISO-8601 UTC, like
|
|
864
|
+
* `Date.prototype.toISOString`; units `ms`, `s`, `m`, `h`, `d`) and
|
|
865
|
+
* `{{nowMs}}` (epoch milliseconds, as text).
|
|
866
|
+
*/
|
|
867
|
+
export type NetworkRouteHandler = NetworkRouteAnswer | NetworkRouteSequence;
|
|
868
|
+
|
|
869
|
+
/** `bodyBase64` answers: bytes in a file, instead of `body` or `json`. */
|
|
870
|
+
export type ScenarioHttpBinary = NetworkRouteFulfillFields & { bodyBase64: string } &
|
|
871
|
+
NetworkRouteForbid<'body' | 'json'>;
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* A `match` value: objects match when every listed key is present and
|
|
875
|
+
* matches (other keys are ignored), arrays match element by element with the
|
|
876
|
+
* same length, a `"/regex/flags"` string (flags among `imsu`) matches a
|
|
877
|
+
* scalar whose text it finds, and any other value matches an equal value.
|
|
878
|
+
*/
|
|
879
|
+
export type ScenarioMatchValue = unknown;
|
|
880
|
+
|
|
881
|
+
/** Fields every rule may have. */
|
|
882
|
+
type ScenarioRuleCommon = {
|
|
883
|
+
/** Answer this many matching calls, then stand aside. */
|
|
884
|
+
times?: number;
|
|
885
|
+
/** Free text, ignored. */
|
|
886
|
+
note?: string;
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* A rule for the app's Logic `fetch` / `Rong.SSE`: `http` is
|
|
891
|
+
* `"METHOD url-glob"` (`*` for any method; the URL may be `/regex/flags`),
|
|
892
|
+
* and the answer is a route handler (or `bodyBase64`).
|
|
893
|
+
*/
|
|
894
|
+
export type ScenarioHttpRule = ScenarioRuleCommon & {
|
|
895
|
+
http: string;
|
|
896
|
+
function?: never;
|
|
897
|
+
/** Match the request's JSON body. */
|
|
898
|
+
match?: { json: ScenarioMatchValue };
|
|
899
|
+
} & (NetworkRouteHandler | ScenarioHttpBinary);
|
|
900
|
+
|
|
901
|
+
/** How a `function` rule answers one call. */
|
|
902
|
+
export type ScenarioFunctionAnswer = { delay?: number } & (
|
|
903
|
+
| { result: unknown; error?: never; fault?: never }
|
|
904
|
+
| { error: { code: string; [key: string]: unknown }; result?: never; fault?: never }
|
|
905
|
+
| { fault: 'notRun' | 'unknown'; result?: never; error?: never }
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* A rule for a Worker Function call, answered by the dev session's
|
|
910
|
+
* companion: `result`, a declared `error`, or a transport `fault`.
|
|
911
|
+
*/
|
|
912
|
+
export type ScenarioFunctionRule = ScenarioRuleCommon & {
|
|
913
|
+
function: string;
|
|
914
|
+
http?: never;
|
|
915
|
+
/** Match the call's arguments. */
|
|
916
|
+
match?: { args: ScenarioMatchValue };
|
|
917
|
+
} & (
|
|
918
|
+
| (ScenarioFunctionAnswer & { sequence?: never })
|
|
919
|
+
| { sequence: ScenarioFunctionAnswer[]; result?: never; error?: never; fault?: never; delay?: never }
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
export type ScenarioRule = ScenarioHttpRule | ScenarioFunctionRule;
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* A scenario file: rules tried in file order (the first match answers;
|
|
926
|
+
* nothing matched goes to the real backend), and named variants whose rules
|
|
927
|
+
* go before the shared ones. Unknown fields are rejected.
|
|
928
|
+
*/
|
|
929
|
+
export type ScenarioDefinition = {
|
|
930
|
+
$schema?: string;
|
|
931
|
+
name?: string;
|
|
932
|
+
description?: string;
|
|
933
|
+
rules?: ScenarioRule[];
|
|
934
|
+
variants?: Record<string, { description?: string; rules: ScenarioRule[] }>;
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* What `scenario()` accepts: a typed definition, or an imported JSON file,
|
|
939
|
+
* whose literal types TypeScript widens. Either is validated by the host.
|
|
940
|
+
*/
|
|
941
|
+
export type ScenarioInput =
|
|
942
|
+
| ScenarioDefinition
|
|
943
|
+
| { readonly rules: readonly object[]; readonly [key: string]: unknown }
|
|
944
|
+
| { readonly variants: { readonly [variant: string]: { readonly rules: readonly object[] } }; readonly [key: string]: unknown };
|
|
945
|
+
|
|
946
|
+
/** One rule of an installed scenario. */
|
|
947
|
+
export interface ScenarioRuleInfo {
|
|
948
|
+
/** 1-based, in precedence order (the variant's rules first). */
|
|
949
|
+
index: number;
|
|
950
|
+
/** `GET **\/wifi/main`, `function orders.submit`. */
|
|
951
|
+
target: string;
|
|
952
|
+
kind: 'http' | 'function';
|
|
953
|
+
/** Calls it answered (`http` rules); `null` for `function` rules. */
|
|
954
|
+
hits: number | null;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/** One call that reached a scenario. */
|
|
958
|
+
export interface ScenarioCall {
|
|
959
|
+
/**
|
|
960
|
+
* Increasing and never reused within its log: the scenario's for `http`
|
|
961
|
+
* calls, the dev companion's for `function` calls. Calls in one
|
|
962
|
+
* millisecond differ by it.
|
|
963
|
+
*/
|
|
964
|
+
seq: number;
|
|
965
|
+
/** Epoch milliseconds. */
|
|
966
|
+
time: number;
|
|
967
|
+
kind: 'http' | 'function';
|
|
968
|
+
/** `http`: upper-case method and URL. */
|
|
969
|
+
method?: string;
|
|
970
|
+
url?: string;
|
|
971
|
+
/** `http`: the request body, parsed when it is JSON. */
|
|
972
|
+
body?: unknown;
|
|
973
|
+
/** `http`: the answered status, `null` for abort/continue/hang or no answer. */
|
|
974
|
+
status?: number | null;
|
|
975
|
+
/** `function`: its name, arguments, and `result`/`error`/`fault`/`default`. */
|
|
976
|
+
function?: string;
|
|
977
|
+
args?: unknown;
|
|
978
|
+
outcome?: string;
|
|
979
|
+
/** The rule that answered, or `null` when none did. */
|
|
980
|
+
rule: number | null;
|
|
981
|
+
/** `rule 2 (name:variant)`, `real`, or `companion default`. */
|
|
982
|
+
answeredBy: string;
|
|
983
|
+
/** Why no rule matched, when rules targeted the call. */
|
|
984
|
+
noMatch?: string;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/** `calls()` filter: one rule's target, or its number. */
|
|
988
|
+
export type ScenarioCallFilter = { http: string } | { function: string } | { rule: number };
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Calls of one bounded log with a `seq` above the one asked for, oldest
|
|
992
|
+
* first. The logs drop their oldest entries first: `droppedThrough` is the
|
|
993
|
+
* highest `seq` a trim took (0 when none), so a reader whose last `seq` is
|
|
994
|
+
* below it may have missed calls.
|
|
995
|
+
*/
|
|
996
|
+
export interface CallWindow<T> {
|
|
997
|
+
calls: T[];
|
|
998
|
+
droppedThrough: number;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/** `mock.reset()` result. */
|
|
1002
|
+
export interface MockResetResult {
|
|
1003
|
+
/** The app's new handler generation; `null` when it has no mocks loaded. */
|
|
1004
|
+
generation: number | null;
|
|
1005
|
+
/**
|
|
1006
|
+
* The companion's answer for the run's Functions, `null` when it does not
|
|
1007
|
+
* switch mocks.
|
|
1008
|
+
*/
|
|
1009
|
+
function: { reset: boolean; reason?: string } | null;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/** `lx.automation().lxapp().mock` in a host run. */
|
|
1013
|
+
export interface MockDriver {
|
|
1014
|
+
/**
|
|
1015
|
+
* Install a scenario file (with one of its variants) for the host run:
|
|
1016
|
+
* `http` rules answer Logic `fetch` before the mock selection, `function`
|
|
1017
|
+
* rules go to the dev session's companion. Validated as a whole; it
|
|
1018
|
+
* replaces the scenario the run installed before (including another app), and the
|
|
1019
|
+
* run's end removes it. Routes added with `network.route()` take
|
|
1020
|
+
* precedence over it.
|
|
1021
|
+
*/
|
|
1022
|
+
use(definition: ScenarioInput, variant?: string): Promise<Scenario>;
|
|
1023
|
+
/**
|
|
1024
|
+
* Start the app's mock handler state over: the next intercepted call
|
|
1025
|
+
* evaluates `mocks/index.ts` again. A companion that switches mocks is
|
|
1026
|
+
* asked to do the same for the run.
|
|
1027
|
+
*/
|
|
1028
|
+
reset(): Promise<MockResetResult>;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/** Handle returned by `mock.use()`. */
|
|
1032
|
+
export interface Scenario {
|
|
1033
|
+
readonly name: string | null;
|
|
1034
|
+
readonly variant: string | null;
|
|
1035
|
+
/** Its rules with their hit counts, read when accessed. */
|
|
1036
|
+
readonly rules: ScenarioRuleInfo[];
|
|
1037
|
+
/** Calls that reached the scenario since it was installed, oldest first. */
|
|
1038
|
+
calls(filter?: ScenarioCallFilter): Promise<ScenarioCall[]>;
|
|
1039
|
+
/**
|
|
1040
|
+
* Calls to one target with a `seq` above `after`. `droppedThrough` counts
|
|
1041
|
+
* the log the target's kind reads (HTTP: the scenario's 200 calls;
|
|
1042
|
+
* Function: the companion's log, across every Function).
|
|
1043
|
+
*/
|
|
1044
|
+
callsAfter(target: ScenarioCallFilter, after: number): Promise<CallWindow<ScenarioCall>>;
|
|
1045
|
+
/** Remove the scenario; resolves how many rules were still installed. */
|
|
1046
|
+
unroute(): Promise<number>;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/** One request a route handled, as the app sent it. */
|
|
1050
|
+
export interface NetworkRouteRequest {
|
|
1051
|
+
/**
|
|
1052
|
+
* Increasing across the host's request log and never reused: requests in
|
|
1053
|
+
* one millisecond differ by it, and a trim leaves a gap.
|
|
1054
|
+
*/
|
|
1055
|
+
seq: number;
|
|
1056
|
+
routeId: number;
|
|
1057
|
+
/** The route's glob or `/source/flags`. */
|
|
1058
|
+
pattern: string;
|
|
1059
|
+
/** Upper-case method. */
|
|
1060
|
+
method: string;
|
|
1061
|
+
url: string;
|
|
1062
|
+
/** Request headers with lower-case names. */
|
|
1063
|
+
headers: Record<string, string>;
|
|
1064
|
+
/**
|
|
1065
|
+
* Request body as UTF-8 text, cut to 64 KiB. `null` when there is none or
|
|
1066
|
+
* it cannot be read without consuming it: a stream, `Blob`, `FormData`, or
|
|
1067
|
+
* the body of a `Request` object passed as `fetch`'s first argument.
|
|
1068
|
+
*/
|
|
1069
|
+
body: string | null;
|
|
1070
|
+
/** `body` was cut to the 64 KiB (65536-byte) limit. */
|
|
1071
|
+
bodyTruncated: boolean;
|
|
1072
|
+
/**
|
|
1073
|
+
* `continue` also covers a `patchJson` pass-through; `fulfill` also covers
|
|
1074
|
+
* an `sse` answer.
|
|
1075
|
+
*/
|
|
1076
|
+
action: 'fulfill' | 'abort' | 'continue' | 'hang';
|
|
1077
|
+
/** Fulfilled status (200 for `sse`); `null` for abort/continue/hang. */
|
|
1078
|
+
status: number | null;
|
|
1079
|
+
/** Epoch milliseconds. */
|
|
1080
|
+
timestamp: number;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
export interface NetworkRoute {
|
|
1084
|
+
readonly id: number;
|
|
1085
|
+
readonly pattern: string;
|
|
1086
|
+
/** Resolves `false` when the route already expired or was removed. */
|
|
1087
|
+
unroute(): Promise<boolean>;
|
|
1088
|
+
/** Requests this route handled, oldest first. */
|
|
1089
|
+
requests(): Promise<NetworkRouteRequest[]>;
|
|
1090
|
+
/**
|
|
1091
|
+
* Requests this route handled with a `seq` above `after`; `droppedThrough`
|
|
1092
|
+
* is the highest `seq` of this route's requests the host log (1000
|
|
1093
|
+
* entries, 16 MiB of bodies, shared by every app and run) dropped.
|
|
1094
|
+
*/
|
|
1095
|
+
requestsAfter(after: number): Promise<{ requests: NetworkRouteRequest[]; droppedThrough: number }>;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* One Logic `fetch` response recorded by `NetworkDriver.captureResponses()`.
|
|
1100
|
+
* Headers are never recorded, and `url` keeps only scheme, host and path.
|
|
1101
|
+
*
|
|
1102
|
+
* @internal Test-runner plumbing for `lxdev test --openapi`.
|
|
1103
|
+
*/
|
|
1104
|
+
export interface NetworkResponseRecord {
|
|
1105
|
+
/** Increasing across the run; pass it as `since` to read only newer ones. */
|
|
1106
|
+
seq: number;
|
|
1107
|
+
/** Upper-case method. */
|
|
1108
|
+
method: string;
|
|
1109
|
+
/** `scheme://host[:port]/path`: userinfo, query and fragment removed. */
|
|
1110
|
+
url: string;
|
|
1111
|
+
/**
|
|
1112
|
+
* `route`: a route fulfilled it. `patch`: the real server answered and a
|
|
1113
|
+
* route merge-patched the body. `network`: the real server answered.
|
|
1114
|
+
*/
|
|
1115
|
+
source: 'route' | 'patch' | 'network';
|
|
1116
|
+
/** The route's pattern for `route` and `patch`. */
|
|
1117
|
+
pattern: string | null;
|
|
1118
|
+
status: number;
|
|
1119
|
+
contentType: string | null;
|
|
1120
|
+
/** JSON body text, cut to `maxBodyBytes`; `null` for other content types. */
|
|
1121
|
+
body: string | null;
|
|
1122
|
+
bodyTruncated: boolean;
|
|
1123
|
+
/** Epoch milliseconds. */
|
|
1124
|
+
timestamp: number;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/** @internal `NetworkDriver.captureResponses()` options. */
|
|
1128
|
+
export interface NetworkCaptureOptions {
|
|
1129
|
+
/** Body bytes kept per response, 1..=1048576. Default 262144. */
|
|
1130
|
+
maxBodyBytes?: number;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Test-only routing of the selected lxapp's Logic `fetch` and `Rong.SSE`.
|
|
1135
|
+
* Available only in a host automation run (`lxdev test`); every route is
|
|
1136
|
+
* removed when that run ends. The newest matching route handles a request; unmatched requests are
|
|
1137
|
+
* untouched. A fulfillment never answers a host the app's network policy
|
|
1138
|
+
* refuses — the request goes to the real `fetch`, which rejects it.
|
|
1139
|
+
* WebView page requests are not routed.
|
|
1140
|
+
*/
|
|
1141
|
+
export interface NetworkDriver {
|
|
1142
|
+
route(pattern: NetworkRoutePattern, handler: NetworkRouteHandler): Promise<NetworkRoute>;
|
|
1143
|
+
/** Remove every route this run installed for the app; resolves the count. */
|
|
1144
|
+
unrouteAll(): Promise<number>;
|
|
1145
|
+
/**
|
|
1146
|
+
* Run-scoped: requests any route of this automation run handled for the
|
|
1147
|
+
* app, oldest first, across every spec. `t.app.network.calls()` narrows
|
|
1148
|
+
* this to the current spec.
|
|
1149
|
+
*/
|
|
1150
|
+
requests(): Promise<NetworkRouteRequest[]>;
|
|
1151
|
+
/**
|
|
1152
|
+
* Record the app's Logic `fetch` responses — status, content type and
|
|
1153
|
+
* JSON body, never headers — until the run ends. A buffered body is read
|
|
1154
|
+
* from a clone without delaying the app's fetch; a streamed one (no or a
|
|
1155
|
+
* large Content-Length) is read once and the app gets an equivalent
|
|
1156
|
+
* buffered `Response`.
|
|
1157
|
+
*
|
|
1158
|
+
* @internal Test-runner plumbing for `lxdev test --openapi`.
|
|
1159
|
+
*/
|
|
1160
|
+
captureResponses(options?: NetworkCaptureOptions): Promise<void>;
|
|
1161
|
+
/**
|
|
1162
|
+
* Responses captured for the app in this run, oldest first; `since` skips
|
|
1163
|
+
* records up to that `seq`. `waitMs` (0..3000) first waits for captures
|
|
1164
|
+
* already in flight; one still unfinished is returned by a later read.
|
|
1165
|
+
* Rejects once when unread responses were lost. At most 500 records and
|
|
1166
|
+
* 8 MiB of bodies are kept; the oldest go first.
|
|
1167
|
+
*
|
|
1168
|
+
* @internal Test-runner plumbing for `lxdev test --openapi`.
|
|
1169
|
+
*/
|
|
1170
|
+
responses(options?: { since?: number; waitMs?: number }): Promise<NetworkResponseRecord[]>;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Checkpoint and roll back the isolated data profile of a host automation run
|
|
1175
|
+
* (`lxdev test --profile`). Each call rejects with `E_PROFILE_NOT_ISOLATED`
|
|
1176
|
+
* unless the selected lxapp runs on this run's profile, so it can never touch
|
|
1177
|
+
* the app's real data.
|
|
1178
|
+
*
|
|
1179
|
+
* `checkpoint` and `restore` close the app, copy or swap its closed data and
|
|
1180
|
+
* reopen it at its initial page: a driver selected before the call is bound to
|
|
1181
|
+
* the closed instance, so select the lxapp again afterwards.
|
|
1182
|
+
*/
|
|
1183
|
+
export interface ProfileDriver {
|
|
1184
|
+
/** Snapshot the profile; resolves the checkpoint id. */
|
|
1185
|
+
checkpoint(): Promise<string>;
|
|
1186
|
+
/**
|
|
1187
|
+
* Replace the profile with checkpoint `id`. With `keep`, the storage keys
|
|
1188
|
+
* those globs match keep their current values across the rollback.
|
|
1189
|
+
*/
|
|
1190
|
+
restore(id: string, options?: ProfileRestoreOptions): Promise<ProfileRestoreResult>;
|
|
1191
|
+
/** Discard checkpoint `id`; the app keeps running. */
|
|
1192
|
+
drop(id: string): Promise<void>;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/** `ProfileDriver.restore` options. */
|
|
1196
|
+
export interface ProfileRestoreOptions {
|
|
1197
|
+
/**
|
|
1198
|
+
* `lx.getStorage()` keys whose current state survives the rollback: globs
|
|
1199
|
+
* over the whole key, `*` any run of characters (dots included), `?` one
|
|
1200
|
+
* character, anything else literal (at most 64 patterns). A matching key
|
|
1201
|
+
* keeps its current value, one added since the checkpoint stays, and one
|
|
1202
|
+
* deleted since stays deleted. Files (`lx://userdata`, …) always roll back.
|
|
1203
|
+
*/
|
|
1204
|
+
keep?: string[];
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
export interface ProfileRestoreResult {
|
|
1208
|
+
/** Kept keys that currently exist and were carried into the restored data. */
|
|
1209
|
+
kept: string[];
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// ============================ test clock ============================
|
|
1213
|
+
|
|
1214
|
+
/** A time for the test clock: epoch milliseconds, a `Date.parse` string, or a `Date`. */
|
|
1215
|
+
export type ClockTime = number | string | Date;
|
|
1216
|
+
|
|
1217
|
+
export interface ClockInstallOptions {
|
|
1218
|
+
/** What Logic's `Date` reads at install. Default: the real current time. */
|
|
1219
|
+
now?: ClockTime;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
export interface ClockRunAllOptions {
|
|
1223
|
+
/** Reject once this many timers fired and more are pending. Default 1000. */
|
|
1224
|
+
maxTimers?: number;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/** Logic's test time after a clock call. */
|
|
1228
|
+
export interface ClockState {
|
|
1229
|
+
/** Logic's `Date.now()` afterwards. */
|
|
1230
|
+
now: number;
|
|
1231
|
+
/** Timers still scheduled on the test clock. */
|
|
1232
|
+
pending: number;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/** What `tick` / `runAll` did. */
|
|
1236
|
+
export interface ClockAdvance extends ClockState {
|
|
1237
|
+
/** Timers fired by this call. */
|
|
1238
|
+
fired: number;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
export interface ClockUninstallResult {
|
|
1242
|
+
/** `false` when no clock was installed (never, or the app reopened since). */
|
|
1243
|
+
uninstalled: boolean;
|
|
1244
|
+
/** Timers still pending on the test clock; they are discarded, never fired. */
|
|
1245
|
+
dropped: number;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/**
|
|
1249
|
+
* Test clock for the selected lxapp's Logic context, scoped to the host
|
|
1250
|
+
* automation run (`lxdev test`); outside one every call rejects with
|
|
1251
|
+
* `E_AUTOMATION`. While installed, Logic's `Date` (no-argument
|
|
1252
|
+
* construction, `Date()`, `Date.now()`), `setTimeout` / `setInterval` and
|
|
1253
|
+
* their `clear*`, and `performance.now()` read test time, and those timers
|
|
1254
|
+
* fire only from `tick` / `runAll`, in time order. The page WebView, native
|
|
1255
|
+
* work, real network requests, test route `delay` / `hang` and timers started
|
|
1256
|
+
* before `install` keep real time. The run's end, or the app reopening,
|
|
1257
|
+
* returns the app to real time.
|
|
1258
|
+
*/
|
|
1259
|
+
export interface ClockDriver {
|
|
1260
|
+
/**
|
|
1261
|
+
* Put Logic on test time; resolves its state (`pending` is 0: timers
|
|
1262
|
+
* started before `install` keep real time). Rejects with
|
|
1263
|
+
* `E_CLOCK_INSTALLED` when a clock is already installed.
|
|
1264
|
+
*/
|
|
1265
|
+
install(options?: ClockInstallOptions): Promise<ClockState>;
|
|
1266
|
+
/**
|
|
1267
|
+
* Advance by `ms`, firing each timer due on the way at its own time. After
|
|
1268
|
+
* every firing, promise chains the callback started settle before the next
|
|
1269
|
+
* timer fires. Rejects with `E_CLOCK_NOT_INSTALLED` without a clock.
|
|
1270
|
+
*/
|
|
1271
|
+
tick(ms: number): Promise<ClockAdvance>;
|
|
1272
|
+
/**
|
|
1273
|
+
* Fire timers, including those they schedule, until none is left; rejects
|
|
1274
|
+
* once `maxTimers` fired (an interval never lets the queue empty).
|
|
1275
|
+
*/
|
|
1276
|
+
runAll(options?: ClockRunAllOptions): Promise<ClockAdvance>;
|
|
1277
|
+
/**
|
|
1278
|
+
* Change what `Date` reads without firing timers; timer due times and
|
|
1279
|
+
* `performance.now()` are unaffected. Resolves the new state.
|
|
1280
|
+
*/
|
|
1281
|
+
setSystemTime(time: ClockTime): Promise<ClockState>;
|
|
1282
|
+
/** Return Logic to real time; pending test timers are dropped. */
|
|
1283
|
+
uninstall(): Promise<ClockUninstallResult>;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// ============================== dialogs ==============================
|
|
1287
|
+
|
|
1288
|
+
/** A toast Logic presented while watched; the host drew it as usual. */
|
|
1289
|
+
export interface ToastRecord {
|
|
1290
|
+
title: string;
|
|
1291
|
+
/** `success` / `error` / `loading` / `none`. */
|
|
1292
|
+
icon: string;
|
|
1293
|
+
/** Milliseconds the toast asked to stay. */
|
|
1294
|
+
duration: number;
|
|
1295
|
+
/** Epoch milliseconds it was presented. */
|
|
1296
|
+
at: number;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
export interface ModalAnswer {
|
|
1300
|
+
confirm: boolean;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* A modal Logic opened while watched: drawn (`drawn: true`) until the spec
|
|
1305
|
+
* queued a modal answer, answered from the queue after.
|
|
1306
|
+
*/
|
|
1307
|
+
export interface ModalRecord {
|
|
1308
|
+
title: string;
|
|
1309
|
+
content: string;
|
|
1310
|
+
/** Only when the app set it (the default is localized). */
|
|
1311
|
+
confirmText?: string;
|
|
1312
|
+
/** Only when the modal has a cancel button and the app set its text. */
|
|
1313
|
+
cancelText?: string;
|
|
1314
|
+
/**
|
|
1315
|
+
* The queued answer it got, or the user's choice when drawn. `null`: a
|
|
1316
|
+
* drawn one still open or that failed to present, or (answering) no answer
|
|
1317
|
+
* was queued, so the call rejected and the spec failed.
|
|
1318
|
+
*/
|
|
1319
|
+
answer: ModalAnswer | null;
|
|
1320
|
+
/** Drawn for the user, not answered from the queue. */
|
|
1321
|
+
drawn?: true;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/** `{ index }` picks that item; `{ cancel: true }` dismisses the sheet. */
|
|
1325
|
+
export type ActionSheetAnswer = { index: number } | { cancel: true };
|
|
1326
|
+
|
|
1327
|
+
/** An `lx.showActionSheet` while watched: drawn, or answered like modals. */
|
|
1328
|
+
export interface ActionSheetRecord {
|
|
1329
|
+
/** Item labels, in order. */
|
|
1330
|
+
items: string[];
|
|
1331
|
+
answer: ActionSheetAnswer | null;
|
|
1332
|
+
drawn?: true;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
export interface DialogUnwatchResult {
|
|
1336
|
+
/** Modal answers queued that no modal used. */
|
|
1337
|
+
modalAnswers: number;
|
|
1338
|
+
/** Action sheet answers queued that no sheet used. */
|
|
1339
|
+
actionSheetAnswers: number;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* The dialogs the selected lxapp's Logic opens while a spec of a host
|
|
1344
|
+
* automation run (`lxdev test`) watches it; outside one every call rejects
|
|
1345
|
+
* with `E_AUTOMATION`. Toasts, modals (`showModal`, `alert`, `confirm`) and
|
|
1346
|
+
* `showActionSheet` are recorded and drawn. A queued answer applies once;
|
|
1347
|
+
* later dialogs are drawn unless strict mode explicitly requires an answer.
|
|
1348
|
+
* An unwatched app presents every dialog as usual.
|
|
1349
|
+
*/
|
|
1350
|
+
export interface DialogDriver {
|
|
1351
|
+
/** Start watching for the open spec attempt, with nothing recorded or queued. */
|
|
1352
|
+
watch(): void;
|
|
1353
|
+
/** Stop watching; returns the queued answers no dialog used. */
|
|
1354
|
+
unwatch(): DialogUnwatchResult;
|
|
1355
|
+
/** The first dialog that found no answer, or `null` once the watch ends. */
|
|
1356
|
+
unanswered(): Promise<string | null>;
|
|
1357
|
+
toasts(): ToastRecord[];
|
|
1358
|
+
modals(): ModalRecord[];
|
|
1359
|
+
actionSheets(): ActionSheetRecord[];
|
|
1360
|
+
answerNextModal(answer: ModalAnswer): void;
|
|
1361
|
+
answerNextActionSheet(answer: ActionSheetAnswer): void;
|
|
1362
|
+
/** Strict mode refuses an unqueued dialog; returns the previous modes. */
|
|
1363
|
+
setAnswerMode(mode: { modals?: "draw" | "strict"; actionSheets?: "draw" | "strict" }):
|
|
1364
|
+
{ modals: "draw" | "strict"; actionSheets: "draw" | "strict" };
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/** Capability for one selected running lxapp, as app Logic sees it. */
|
|
1368
|
+
export interface LogicLxAppDriver {
|
|
533
1369
|
readonly page: PageDriver;
|
|
534
|
-
readonly nav:
|
|
1370
|
+
readonly nav: LogicNavDriver;
|
|
535
1371
|
/** Complete runtime snapshot of the selected lxapp. */
|
|
536
1372
|
info(): Promise<LxAppRuntimeInfo>;
|
|
537
1373
|
/** Configured pages of the selected lxapp. */
|
|
538
1374
|
pages(): Promise<LxAppPageConfig[]>;
|
|
539
1375
|
/** Authoritative host surface render plan, for end-to-end assertions. */
|
|
540
1376
|
surfaceLayout(): Promise<SurfaceLayoutSnapshot>;
|
|
541
|
-
/**
|
|
1377
|
+
/**
|
|
1378
|
+
* Logic-runtime eval of another lxapp; evaluating the calling Logic runtime
|
|
1379
|
+
* itself rejects. `T` describes the expected JSON result; it is not
|
|
1380
|
+
* runtime validation.
|
|
1381
|
+
*/
|
|
1382
|
+
eval<T = unknown>(options: LogicLxAppEvalOptions): Promise<T>;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* Capability for one selected running lxapp in a host automation run
|
|
1387
|
+
* (`HostRunAutomation.lxapp()`): the Logic driver plus test-run-only members.
|
|
1388
|
+
*/
|
|
1389
|
+
export interface LxAppDriver extends LogicLxAppDriver {
|
|
1390
|
+
readonly nav: NavDriver;
|
|
1391
|
+
/**
|
|
1392
|
+
* Test-only Logic `fetch` routing, scoped to the host automation run.
|
|
1393
|
+
*
|
|
1394
|
+
* @remarks Reading the property always works, but every call rejects with
|
|
1395
|
+
* `E_AUTOMATION` outside a host test run (`lxdev test`) — from app Logic,
|
|
1396
|
+
* or in a host built without the automation runtime.
|
|
1397
|
+
*/
|
|
1398
|
+
readonly network: NetworkDriver;
|
|
1399
|
+
/**
|
|
1400
|
+
* The app's mocks in the host run: scenario states on top of the mock
|
|
1401
|
+
* selection, and a fresh handler state.
|
|
1402
|
+
*
|
|
1403
|
+
* @remarks Reading the property always works, but every call rejects with
|
|
1404
|
+
* `E_AUTOMATION` outside a host test run (`lxdev test`).
|
|
1405
|
+
*/
|
|
1406
|
+
readonly mock: MockDriver;
|
|
1407
|
+
/**
|
|
1408
|
+
* Isolated data profile rollback, scoped to the host automation run.
|
|
1409
|
+
*
|
|
1410
|
+
* @remarks Reading the property always works; every call rejects with
|
|
1411
|
+
* `E_PROFILE_NOT_ISOLATED` outside an isolated run.
|
|
1412
|
+
*/
|
|
1413
|
+
readonly profile: ProfileDriver;
|
|
1414
|
+
/**
|
|
1415
|
+
* Test clock for the selected lxapp's Logic, scoped to the host automation
|
|
1416
|
+
* run.
|
|
1417
|
+
*
|
|
1418
|
+
* @remarks Reading the property always works, but every call rejects with
|
|
1419
|
+
* `E_AUTOMATION` outside a host test run (`lxdev test`).
|
|
1420
|
+
*/
|
|
1421
|
+
readonly clock: ClockDriver;
|
|
1422
|
+
/**
|
|
1423
|
+
* The dialogs this lxapp's Logic opens while a spec watches it.
|
|
1424
|
+
*
|
|
1425
|
+
* @remarks Reading the property always works, but every call rejects with
|
|
1426
|
+
* `E_AUTOMATION` outside a host test run (`lxdev test`).
|
|
1427
|
+
*/
|
|
1428
|
+
readonly dialogs: DialogDriver;
|
|
1429
|
+
/** @internal Test-runner plumbing: resolves to the call-trace envelope. */
|
|
542
1430
|
eval<T = unknown>(options: LxAppEvalOptions & { captureCalls: true }): Promise<LxAppEvalTrace<T>>;
|
|
1431
|
+
/**
|
|
1432
|
+
* Logic-runtime eval; evaluating the calling Logic runtime itself rejects.
|
|
1433
|
+
* `T` describes the expected JSON result; it is not runtime validation.
|
|
1434
|
+
*/
|
|
543
1435
|
eval<T = unknown>(options: LxAppEvalOptions & { captureCalls?: false }): Promise<T>;
|
|
544
1436
|
eval<T = unknown>(options: LxAppEvalOptions): Promise<T | LxAppEvalTrace<T>>;
|
|
545
1437
|
}
|
|
@@ -552,38 +1444,40 @@ export interface LxAppPageEntry {
|
|
|
552
1444
|
path: string;
|
|
553
1445
|
}
|
|
554
1446
|
|
|
555
|
-
/** Runtime snapshot of a running lxapp
|
|
1447
|
+
/** Runtime snapshot of a running lxapp. */
|
|
556
1448
|
export interface LxAppRuntimeInfo {
|
|
557
|
-
|
|
558
|
-
|
|
1449
|
+
appId: string;
|
|
1450
|
+
appName: string;
|
|
559
1451
|
version: string;
|
|
560
|
-
|
|
561
|
-
|
|
1452
|
+
releaseType: string;
|
|
1453
|
+
sessionId: number;
|
|
562
1454
|
status: string;
|
|
563
1455
|
/** True while the lxapp holds a place in the host's page stack. */
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
1456
|
+
inStack: boolean;
|
|
1457
|
+
isHome: boolean;
|
|
1458
|
+
currentPage: string | null;
|
|
1459
|
+
initialRoute: string;
|
|
1460
|
+
pagesCount: number;
|
|
1461
|
+
pageEntries: LxAppPageEntry[];
|
|
1462
|
+
pageStack: string[];
|
|
1463
|
+
tabBar: LxAppRuntimeTabBarInfo | null;
|
|
1464
|
+
navigationBar: LxAppRuntimeNavigationBarInfo | null;
|
|
1465
|
+
lxappDir: string;
|
|
1466
|
+
dataDir: string;
|
|
1467
|
+
cacheDir: string;
|
|
1468
|
+
/** Supported features keyed by Logic context id. */
|
|
1469
|
+
logicFeatures: Record<string, string[]>;
|
|
576
1470
|
}
|
|
577
1471
|
|
|
578
1472
|
/** Runtime NavigationBar state exposed for deterministic host-level assertions. */
|
|
579
1473
|
export interface LxAppRuntimeNavigationBarInfo {
|
|
580
1474
|
title: string;
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
1475
|
+
homeButton: 'auto' | 'hidden';
|
|
1476
|
+
homeButtonVisible: boolean;
|
|
1477
|
+
runtimeStyle: {
|
|
1478
|
+
backgroundColor: string | null;
|
|
1479
|
+
foregroundColor: string | null;
|
|
1480
|
+
dividerColor: string | null;
|
|
587
1481
|
};
|
|
588
1482
|
}
|
|
589
1483
|
|
|
@@ -591,15 +1485,15 @@ export interface LxAppRuntimeNavigationBarInfo {
|
|
|
591
1485
|
export interface LxAppRuntimeTabBarInfo {
|
|
592
1486
|
presentation: 'standard' | 'immersive';
|
|
593
1487
|
visibility: 'auto' | 'visible' | 'hidden';
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
1488
|
+
routeVisible: boolean;
|
|
1489
|
+
effectiveVisible: boolean;
|
|
1490
|
+
selectedIndex: number;
|
|
597
1491
|
items: Array<{
|
|
598
1492
|
index: number;
|
|
599
1493
|
text: string | null;
|
|
600
|
-
|
|
1494
|
+
iconPath: string | null;
|
|
601
1495
|
badge: string | null;
|
|
602
|
-
|
|
1496
|
+
redDot: boolean;
|
|
603
1497
|
}>;
|
|
604
1498
|
}
|
|
605
1499
|
|
|
@@ -610,14 +1504,14 @@ export interface LxAppRef {
|
|
|
610
1504
|
}
|
|
611
1505
|
|
|
612
1506
|
export interface LxAppOpenOptions {
|
|
613
|
-
|
|
1507
|
+
appId: string;
|
|
614
1508
|
/** Initial page/path. */
|
|
615
1509
|
path?: string;
|
|
616
1510
|
channel?: 'release' | 'draft';
|
|
617
1511
|
}
|
|
618
1512
|
|
|
619
1513
|
export interface LxAppOpenResult {
|
|
620
|
-
|
|
1514
|
+
appId: string;
|
|
621
1515
|
path: string;
|
|
622
1516
|
}
|
|
623
1517
|
|
|
@@ -632,7 +1526,8 @@ export interface ApplinkResult {
|
|
|
632
1526
|
}
|
|
633
1527
|
|
|
634
1528
|
/**
|
|
635
|
-
* Cross-lxapp lifecycle and host-window access. Requires `host`
|
|
1529
|
+
* Cross-lxapp lifecycle and host-window access. Requires the `host` privilege
|
|
1530
|
+
* from app Logic; a host automation run needs no grant.
|
|
636
1531
|
*
|
|
637
1532
|
* `close`, `restart`, and `uninstall` reject when they target the calling app
|
|
638
1533
|
* itself. Use `lx.host.exit()` to self-exit.
|
|
@@ -642,7 +1537,7 @@ export interface LxAppManager {
|
|
|
642
1537
|
current(): Promise<LxAppSummary>;
|
|
643
1538
|
open(options: LxAppOpenOptions): Promise<LxAppOpenResult>;
|
|
644
1539
|
/**
|
|
645
|
-
* Inject an App Link (`lxdev
|
|
1540
|
+
* Inject an App Link (`lxdev host applink`). Warm `onShow`, `scene === 8003`.
|
|
646
1541
|
* Host must match `appLinks.hosts`. Resolves when accepted, not when
|
|
647
1542
|
* navigation finishes.
|
|
648
1543
|
*/
|
|
@@ -685,9 +1580,6 @@ export interface DeviceState {
|
|
|
685
1580
|
landscape: boolean;
|
|
686
1581
|
/** Simulated system appearance of the device screen. */
|
|
687
1582
|
appearance: "system" | "light" | "dark";
|
|
688
|
-
/** Whether the simulated host capsule is enabled (the setting, not
|
|
689
|
-
* per-device visibility: desktop presets draw no phone chrome either way). */
|
|
690
|
-
capsule: boolean;
|
|
691
1583
|
}
|
|
692
1584
|
|
|
693
1585
|
export interface DeviceSetOptions {
|
|
@@ -698,8 +1590,6 @@ export interface DeviceSetOptions {
|
|
|
698
1590
|
landscape?: boolean;
|
|
699
1591
|
/** Simulated appearance; omit to keep. */
|
|
700
1592
|
appearance?: "system" | "light" | "dark";
|
|
701
|
-
/** Show (`true`) or hide (`false`) the simulated host capsule; omit to keep. */
|
|
702
|
-
capsule?: boolean;
|
|
703
1593
|
}
|
|
704
1594
|
|
|
705
1595
|
/**
|
|
@@ -819,9 +1709,9 @@ export interface BrowserElementInfo {
|
|
|
819
1709
|
enabled: boolean;
|
|
820
1710
|
editable: boolean;
|
|
821
1711
|
text?: string;
|
|
822
|
-
|
|
1712
|
+
textTruncated?: boolean;
|
|
823
1713
|
value?: string;
|
|
824
|
-
|
|
1714
|
+
valueTruncated?: boolean;
|
|
825
1715
|
rect?: ElementRect;
|
|
826
1716
|
}
|
|
827
1717
|
|
|
@@ -1019,7 +1909,7 @@ export interface PageKey {
|
|
|
1019
1909
|
press(options: KeyPressOptions): Promise<InputResult>;
|
|
1020
1910
|
}
|
|
1021
1911
|
|
|
1022
|
-
// ======================= desktop (host
|
|
1912
|
+
// ======================= desktop (host) =======================
|
|
1023
1913
|
|
|
1024
1914
|
/** A rectangle in backend-native global desktop coordinates. */
|
|
1025
1915
|
export interface DesktopRect {
|