objs-core 2.3.0 → 2.4.1

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/objs.d.ts CHANGED
@@ -1,525 +1,584 @@
1
- /**
2
- * Objs-core v2.0 TypeScript definitions
3
- * @license Apache-2.0
4
- */
5
-
6
- // ─── Core instance types ──────────────────────────────────────────────────
7
-
8
- /** State definition object for o.init(). `render` is the reserved creation state. */
9
- export interface StateObject {
10
- tag?: string;
11
- tagName?: string;
12
- /** Component name — used for o.autotag data-* attribute (camelCase → kebab-case) */
13
- name?: string;
14
- class?: string;
15
- /** React-style alias for `class` */
16
- className?: string;
17
- id?: string;
18
- html?: string;
19
- innerHTML?: string;
20
- style?: string | Record<string, string>;
21
- dataset?: Record<string, string>;
22
- events?: Record<string, EventListener>;
23
- [attr: string]: unknown;
24
- }
25
-
26
- /** Parameter object passed to every state function.
27
- *
28
- * State functions receive ONE merged object: { ...passedProps, self, o, i, parent, data }.
29
- * - Properties from the calling argument are spread into the context.
30
- * - `data` holds the raw first argument (useful for primitives: `comp.setState(5)` → `data = 5`).
31
- * - `parent` is the ObjsInstance that this component was `appendInside()` into (if any).
32
- *
33
- * @example
34
- * // Passing an object — destructure its keys:
35
- * sync: ({ self, values, errors }) => { ... } // comp.sync(store)
36
- * // Passing a primitive — use `data`:
37
- * setCount: ({ self, data }) => { self.html(data); } // comp.setCount(5)
38
- */
39
- export interface StateParams {
40
- self: ObjsInstance;
41
- o: typeof o;
42
- i: number;
43
- /** The ObjsInstance this component was appendInside() into, or null */
44
- parent: ObjsInstance | null;
45
- /** The raw first argument passed when the state method was called */
46
- data: unknown;
47
- [key: string]: unknown;
48
- }
49
-
50
- /** State function signature */
51
- export type StateFunction = (params: StateParams) => void | unknown;
52
-
53
- /** States map passed to o.init() */
54
- export type StatesMap = {
55
- render?: StateObject | StateFunction | string;
56
- [stateName: string]: StateObject | StateFunction | string | undefined;
57
- };
58
-
59
- /** Test case tuple: [title, testFn | true | false | string] */
60
- export type TestCase = [string, ((info: TestInfo) => boolean | string | undefined) | boolean | string | undefined];
61
-
62
- export interface TestInfo {
63
- n: number;
64
- i: number;
65
- title: string;
66
- tShowOk: boolean;
67
- tStyled: boolean;
68
- }
69
-
70
- /** Object returned by o.addTest() */
71
- export interface TestHandle {
72
- testId: number;
73
- run(): void;
74
- autorun(): void;
75
- }
76
-
77
- /** Object returned by o.newLoader() */
78
- export interface ObjsLoader<T = unknown> {
79
- reload(promise: Promise<T>): void;
80
- isObjsLoader: true;
81
- listeners: Array<[ObjsInstance, string, string?]>;
82
- isFinished(): boolean;
83
- getStore(): T | null;
84
- connect(listener: ObjsInstance, state?: string, fail?: string): void;
85
- disconnect(listener: ObjsInstance): void;
86
- }
87
-
88
- /** Element measurement result from o.measure() */
89
- export interface MeasureResult {
90
- width: number;
91
- height: number;
92
- top: number;
93
- left: number;
94
- visible: boolean;
95
- opacity: string;
96
- zIndex: string;
97
- }
98
-
99
- /** Recorded action entry */
100
- export interface RecordedAction {
101
- type: string;
102
- target: string;
103
- time: number;
104
- scrollY?: number;
105
- value?: string;
106
- checked?: boolean;
107
- key?: string;
108
- code?: string;
109
- targetType?: string;
110
- /** When target matches multiple elements, selector for the repeated list item container */
111
- listSelector?: string;
112
- /** Index within listSelector matches for replay by index */
113
- targetIndex?: number;
114
- }
115
-
116
- export type Assertion = {
117
- actionIdx: number;
118
- type: 'visible' | 'class' | 'style' | 'hidden' | 'disabled' | 'aria-expanded' | 'aria-checked';
119
- selector: string;
120
- text?: string;
121
- className?: string;
122
- style?: string;
123
- hidden?: boolean;
124
- disabled?: boolean;
125
- ariaExpanded?: string | null;
126
- ariaChecked?: string | null;
127
- /** When selector matches multiple, selector for the list item container */
128
- listSelector?: string;
129
- /** Index within listSelector matches */
130
- index?: number;
131
- };
132
-
133
- /** WebSocket connection and message capture */
134
- export interface WebSocketEvent {
135
- url: string;
136
- protocol?: string;
137
- open: boolean;
138
- messages: Array<{ dir: 'in' | 'out'; data: string }>;
139
- }
140
-
141
- /** Full recording object */
142
- export interface Recording {
143
- actions: RecordedAction[];
144
- mocks: Record<string, { url: string; method: string; request: unknown; response: unknown; status: number }>;
145
- initialData: Record<string, unknown>;
146
- stepDelays?: Record<string, number>;
147
- assertions: Assertion[];
148
- observeRoot: string | null;
149
- websocketEvents?: WebSocketEvent[];
150
- }
151
-
152
- /**
153
- * Reactive store returned by o.createStore().
154
- * Components subscribe via store.subscribe(component, stateName).
155
- * When store.notify() is called, each subscribed component's state method
156
- * receives the full store object, whose properties are merged into the
157
- * state context: { ...storeProps, self, o, i, parent, data }.
158
- */
159
- export interface ObjsStore<T extends object = Record<string, unknown>> {
160
- _listeners: Array<(store: this) => void>;
161
- /** Subscribe an Objs component state to this store.
162
- * On every notify(), component[stateName](store) is called. */
163
- subscribe(component: ObjsInstance, stateName: string): this;
164
- /** Notify all subscribers — passes `this` store as the argument. */
165
- notify(): void;
166
- /** Restore store data from initial defaults; does not replace subscribe, notify, _listeners. */
167
- reset(): void;
168
- }
169
-
170
- // ─── ObjsInstance (chainable result of o() / o.init()) ────────────────────
171
-
172
- export interface ObjsInstance {
173
- // Properties
174
- els: Element[];
175
- el: Element | undefined;
176
- last: Element | undefined;
177
- length: number;
178
- initID: number | undefined;
179
- ie: Record<string, Array<[EventListener, unknown?, unknown?]>>;
180
- delegated: Record<string, EventListener[]>;
181
- store: Record<string, unknown>;
182
- states: string[];
183
- isDebug: boolean;
184
- currentState: string;
185
- savedStates: Record<string, unknown>;
186
- isRoot: boolean;
187
- /** The ObjsInstance this component was appendInside() into, or null */
188
- _parent: ObjsInstance | null;
189
- /**
190
- * Named sub-elements collected from `ref="name"` attributes in the rendered HTML.
191
- * Populated automatically on first render; `ref` attributes are removed from the DOM.
192
- * @example
193
- * // In render: html: `<div ref="body"></div>`
194
- * // After render: component.refs.body → ObjsInstance wrapping that div
195
- */
196
- refs: Record<string, ObjsInstance>;
197
-
198
- // Selection
199
- reset(query: QueryArg): ObjsInstance;
200
- /** Select by index or by event: select(e) selects the element in this instance that contains e.target. */
201
- select(i?: number | Event): ObjsInstance;
202
- all(): ObjsInstance;
203
- find(query: string): ObjsInstance;
204
- first(query?: string): ObjsInstance;
205
- add(el: QueryArg): ObjsInstance;
206
- skip(i?: number): ObjsInstance;
207
- remove(i?: number): ObjsInstance;
208
-
209
- // State
210
- init(states: StatesMap | StateFunction | string): ObjsInstance;
211
- initState(state: StateObject, props?: Record<string, unknown>): ObjsInstance;
212
- sample(state?: string): StatesMap;
213
- saveState(stateId?: string, root?: boolean): ObjsInstance;
214
- revertState(stateId?: string): ObjsInstance;
215
- loseState(stateId: string): ObjsInstance;
216
-
217
- // DOM manipulation
218
- html(html?: string): string | ObjsInstance;
219
- toString(): string;
220
- [Symbol.toPrimitive](hint: string): string | number;
221
- innerHTML(html?: string): string[] | ObjsInstance;
222
- innerText(text: string): ObjsInstance;
223
- textContent(content: string): ObjsInstance;
224
- val(value?: string): string | ObjsInstance;
225
- attr(attr: string, value?: string | null): string | string[] | ObjsInstance;
226
- attrs(): Record<string, string> | Array<Record<string, string>>;
227
- dataset(values?: Record<string, string>): Record<string, string> | Array<Record<string, string>> | ObjsInstance;
228
- style(value?: string | null): ObjsInstance;
229
- css(styles: Record<string, string> | null): ObjsInstance;
230
- setClass(value: string): ObjsInstance;
231
- addClass(...cls: string[]): ObjsInstance;
232
- removeClass(...cls: string[]): ObjsInstance;
233
- toggleClass(cls: string, rule?: boolean): ObjsInstance;
234
- haveClass(cls: string): boolean;
235
-
236
- // Events
237
- on(events: string, handler: EventListener, options?: EventListenerOptions): ObjsInstance;
238
- off(events: string, handler: EventListener, options?: EventListenerOptions): ObjsInstance;
239
- offAll(event?: string): ObjsInstance;
240
- onAll(event?: string): ObjsInstance;
241
- onDelegate(events: string, selector: string, handler: EventListener): ObjsInstance;
242
- offDelegate(event?: string): ObjsInstance;
243
- onParent(events: string, selectorOrEl: string | Element, handler: EventListener): ObjsInstance;
244
- offParent(events: string, query: string): ObjsInstance;
245
-
246
- // DOM insertion
247
- appendInside(el: QueryArg): ObjsInstance;
248
- appendBefore(el: QueryArg): ObjsInstance;
249
- appendAfter(el: QueryArg): ObjsInstance;
250
-
251
- // Misc
252
- forEach(fn: (ctx: { o: typeof o; self: ObjsInstance; i: number; el: Element }) => void): ObjsInstance;
253
- connect(loader: ObjsLoader, state?: string, fail?: string): ObjsInstance;
254
- getSSR(initId?: number): ObjsInstance;
255
- saveAs(key: string): ObjsInstance;
256
- unmount(): boolean;
257
- take(query: QueryArg): ObjsInstance;
258
-
259
- // React
260
- prepareFor(createElement: Function, component?: Function): unknown;
261
-
262
- // Debug (behind __DEV__ in build)
263
- debug?: () => ObjsInstance;
264
-
265
- // Dynamic state methods added by init()
266
- render?: (props?: Record<string, unknown> | Array<Record<string, unknown>>) => ObjsInstance;
267
- [stateName: string]: unknown;
268
- }
269
-
270
- type QueryArg = string | Element | Element[] | ObjsInstance | number;
271
-
272
- // ─── o() function and static API ─────────────────────────────────────────
273
-
274
- declare function o(query?: QueryArg): ObjsInstance;
275
-
276
- declare namespace o {
277
- // Core
278
- const inits: ObjsInstance[];
279
- const getSaved: Record<string, ObjsInstance>;
280
- const errors: Error[];
281
- let debug: boolean;
282
- let showErrors: boolean;
283
- function logErrors(): void;
284
- function onError(error: Error, name?: string): void;
285
- function reactRender(...args: unknown[]): unknown;
286
-
287
- // Autotag
288
- let autotag: string | undefined;
289
-
290
- function reactQA(componentName: string): Record<string, string>;
291
-
292
- // Element selection
293
- function first(query: string): ObjsInstance;
294
- function take(query: QueryArg): ObjsInstance;
295
-
296
- // State
297
- function init(states: StatesMap | StateFunction | string): ObjsInstance;
298
- function initState(state: StateObject, props?: Record<string, unknown>): ObjsInstance;
299
- function getStates(): (string[] | undefined)[];
300
- function getStores(): (Record<string, unknown> | undefined)[];
301
- function getListeners(): (Record<string, unknown> | undefined)[];
302
-
303
- /**
304
- * Create a reactive store — plain object with built-in subscribe / notify.
305
- * Components subscribed via store.subscribe(comp, 'state') receive the store
306
- * as their merged state context: { ...storeProps, self, o, i, parent, data }.
307
- * @example
308
- * const store = o.createStore({ count: 0, inc() { this.count++; this.notify(); } });
309
- * store.subscribe(badge, 'sync'); // badge.sync(store) on every notify
310
- */
311
- function createStore<T extends object>(defaults: T): T & ObjsStore<T>;
312
-
313
- // AJAX
314
- function ajax(url: string, props?: Record<string, unknown>): Promise<Response>;
315
- function get(url: string, props?: Record<string, unknown>): Promise<Response>;
316
- function post(url: string, props?: Record<string, unknown>): Promise<Response>;
317
- function getParams(key?: string): Record<string, string> | string;
318
-
319
- // Loader
320
- function newLoader<T = unknown>(promise?: Promise<T>): ObjsLoader<T>;
321
-
322
- // Include
323
- let incCache: boolean;
324
- let incCacheExp: number;
325
- let incTimeout: number;
326
- let incSource: string;
327
- let incForce: boolean;
328
- let incAsync: boolean;
329
- let incCors: boolean;
330
- let incFns: Record<string, number>;
331
- function inc(sources: Record<string, string>, callBack?: (setId: number) => void, callBad?: (setId: number) => void): number;
332
- function incCheck(set?: number, fnId?: number, loaded?: number): boolean;
333
- function incCacheClear(all?: boolean): boolean;
334
-
335
- // Routing
336
- function route(path: string | boolean | ((path: string) => boolean), task?: Function | object): boolean | object;
337
- function router(routes: Record<string, Function | object>): boolean;
338
-
339
- // Cookies
340
- function setCookie(title: string, value?: string | number | boolean, params?: Record<string, unknown>): void;
341
- function getCookie(title: string): string | undefined;
342
- function deleteCookie(title: string): void;
343
- function clearCookies(): void;
344
- /** Clear cookies, localStorage, and test-related sessionStorage. Call after Cookies/LS/SS tests. */
345
- function clearAfterTests(): void;
346
-
347
- // Storage
348
- function clearLocalStorage(all?: boolean): void;
349
- function clearSessionStorage(onlyTests?: boolean): void;
350
- function clearTestsStorage(): void;
351
-
352
- // Utilities
353
- function camelToKebab(str: string): string;
354
- function kebabToCamel(str: string): string;
355
- function verify(pairs: Array<[unknown, string | string[]]>, safe?: boolean): boolean | Error;
356
- function safeVerify(pairs: Array<[unknown, string | string[]]>): boolean;
357
- const C: (obj: object, key: string) => boolean;
358
- const F: false;
359
- const U: undefined;
360
-
361
- // Store adapters (always present, prod + dev)
362
- function connectRedux<S = unknown>(
363
- store: { getState(): S; subscribe(listener: () => void): () => void },
364
- selector: (state: S) => unknown,
365
- component: ObjsInstance,
366
- state?: string
367
- ): () => void;
368
-
369
- function connectMobX<T = unknown>(
370
- mobx: { autorun(fn: () => void): () => void },
371
- observable: T,
372
- accessor: (obs: T) => unknown,
373
- component: ObjsInstance,
374
- state?: string
375
- ): () => void;
376
-
377
- let ObjsContext: unknown;
378
-
379
- function withReactContext<C = unknown>(
380
- React: { useContext(ctx: unknown): C; useEffect(fn: () => void, deps: unknown[]): void },
381
- Context: unknown,
382
- selector: (value: C) => unknown,
383
- component: ObjsInstance,
384
- state?: string
385
- ): () => null;
386
-
387
- // SSR
388
- const DocumentMVP: {
389
- createElement(tag: string): unknown;
390
- addEventListener(): void;
391
- parseElement(elem: unknown, outer?: boolean): string;
392
- };
393
- let D: Document | typeof DocumentMVP;
394
-
395
- const recorder: {
396
- active: boolean;
397
- actions: RecordedAction[];
398
- mocks: Recording['mocks'];
399
- initialData: Record<string, unknown>;
400
- assertions: Assertion[];
401
- observeRoot: string | null;
402
- _observer: MutationObserver | null;
403
- };
404
- /**
405
- * Start recording user interactions.
406
- * Available in all builds — QA testers can record on staging/production.
407
- * @param observe CSS selector to scope the MutationObserver (reduces assertion noise)
408
- * @param events Events to record (default: click, mouseover, scroll, input, change)
409
- * @param timeouts Debounce delays per event type in ms
410
- */
411
- function startRecording(observe?: string, events?: string[], timeouts?: Record<string, number>): void;
412
- function stopRecording(): Recording;
413
- function clearRecording(id?: number): void;
414
- function exportTest(recording: Recording, options?: { delay?: number }): string;
415
- /**
416
- * Run recording assertions against the current DOM.
417
- * @param recording Recording with assertions
418
- * @param root Root element or selector (default: document)
419
- * @param actionIdx When provided, run only assertions for this action index
420
- */
421
- function runRecordingAssertions(
422
- recording: Recording,
423
- root?: Element | string,
424
- actionIdx?: number
425
- ): { passed: number; total: number; failures: Array<{ selector: string; message: string }> };
426
- function exportPlaywrightTest(
427
- recording: Recording,
428
- options?: { testName?: string; baseUrl?: string }
429
- ): string;
430
-
431
- // Test framework
432
- function sleep(ms: number): Promise<void>;
433
- let tLog: string[];
434
- let tRes: boolean[];
435
- let tStatus: Array<Array<boolean | undefined>>;
436
- let tFns: Array<((testN: number) => void) | undefined>;
437
- let tShowOk: boolean;
438
- let tStyled: boolean;
439
- let tTime: number;
440
- let tests: Array<{ title: string; tests: TestCase[]; hooks: { before?: Function; after?: Function } }>;
441
- let tAutolog: boolean;
442
- let tBeforeEach: ((info: TestInfo) => void) | undefined;
443
- let tAfterEach: ((info: TestInfo, result: unknown) => void) | undefined;
444
-
445
- /** Options for o.test when sync or confirmOnFailure is needed */
446
- interface TestOptions {
447
- sync?: boolean;
448
- confirmOnFailure?: boolean;
449
- confirmOnFailureTimeout?: number;
450
- }
451
- function test(
452
- title: string,
453
- ...cases: Array<TestCase | TestOptions | (() => void)>
454
- ): number;
455
- function addTest(title: string, ...cases: Array<TestCase | { before?: Function; after?: Function }>): TestHandle;
456
- function runTest(testId?: number, autoRun?: boolean, savePrev?: boolean): void;
457
- function testUpdate(info: TestInfo, result?: boolean | string, suffix?: string): void;
458
- function updateLogs(): string[];
459
-
460
- // Measurements
461
- function measure(el: Element): MeasureResult;
462
- function assertVisible(el: Element): boolean;
463
- /** Expected size/padding/margin for design system or UI verification. All values in px. */
464
- function assertSize(
465
- el: Element,
466
- expected?: {
467
- w?: number;
468
- h?: number;
469
- padding?: number;
470
- paddingTop?: number;
471
- paddingRight?: number;
472
- paddingBottom?: number;
473
- paddingLeft?: number;
474
- margin?: number;
475
- marginTop?: number;
476
- marginRight?: number;
477
- marginBottom?: number;
478
- marginLeft?: number;
479
- }
480
- ): boolean | string;
481
-
482
- // Dev-only replay + overlay (depend on o.test framework)
483
- /** Manual check inserted after an action or at end of playback */
484
- interface ManualCheck {
485
- afterAction: number | 'end';
486
- label: string;
487
- items: string[];
488
- }
489
- /** Options for o.playRecording when runAssertions is used */
490
- interface PlayRecordingOptions {
491
- runAssertions?: boolean;
492
- root?: string;
493
- actionDelay?: number;
494
- manualChecks?: ManualCheck[];
495
- mockOverrides?: Recording['mocks'];
496
- onComplete?: (assertionResult?: { passed: number; total: number; failures: Array<{ selector: string; message: string }> }) => void;
497
- }
498
- function playRecording(
499
- recording: Recording,
500
- opts?: Recording['mocks'] | PlayRecordingOptions
501
- ): number | { testId: number };
502
- function testOverlay(): void;
503
- /** Common draggable overlay; used by testConfirm, testOverlay, confirmOnFailure */
504
- function overlay(opts: {
505
- innerHTML: string;
506
- onClose?: (result?: { ok?: boolean; errors?: string[]; continue?: boolean }) => void;
507
- timeout?: number;
508
- excludeDragSelector?: string;
509
- removeExisting?: boolean;
510
- className?: string;
511
- id?: string;
512
- }): ObjsInstance;
513
- /**
514
- * Pause an Objs browser test; shows a draggable bar with "Test title: Paused", optional checklist (labels + checkboxes), and Continue.
515
- * Dev-only. Returns ok: true if all items checked; ok: false with errors = list of unchecked item texts.
516
- */
517
- function testConfirm(
518
- label: string,
519
- items?: string[],
520
- opts?: { confirm?: string; timeout?: number }
521
- ): Promise<{ ok: boolean; errors?: string[] }>;
522
- }
523
-
524
- export { o };
525
- export default o;
1
+ /**
2
+ * Objs-core v2.4.1 TypeScript definitions
3
+ * @license Apache-2.0
4
+ */
5
+
6
+ // ─── Core instance types ──────────────────────────────────────────────────
7
+
8
+ /** State definition object for o.init(). `render` is the reserved creation state. */
9
+ export interface StateObject {
10
+ tag?: string;
11
+ tagName?: string;
12
+ /** Component name — used for o.autotag data-* attribute (camelCase → kebab-case) */
13
+ name?: string;
14
+ class?: string;
15
+ /** React-style alias for `class` */
16
+ className?: string;
17
+ id?: string;
18
+ html?: string;
19
+ innerHTML?: string;
20
+ style?: string | Record<string, string>;
21
+ dataset?: Record<string, string>;
22
+ events?: Record<string, EventListener>;
23
+ [attr: string]: unknown;
24
+ }
25
+
26
+ /** Parameter object passed to every state function.
27
+ *
28
+ * State functions receive ONE merged object: { ...passedProps, self, o, i, parent, data }.
29
+ * - Properties from the calling argument are spread into the context.
30
+ * - `data` holds the raw first argument (useful for primitives: `comp.setState(5)` → `data = 5`).
31
+ * - `parent` is the ObjsInstance that this component was `appendInside()` into (if any).
32
+ *
33
+ * @example
34
+ * // Passing an object — destructure its keys:
35
+ * sync: ({ self, values, errors }) => { ... } // comp.sync(store)
36
+ * // Passing a primitive — use `data`:
37
+ * setCount: ({ self, data }) => { self.html(data); } // comp.setCount(5)
38
+ */
39
+ export interface StateParams {
40
+ self: ObjsInstance;
41
+ o: typeof o;
42
+ i: number;
43
+ /** The ObjsInstance this component was appendInside() into, or null */
44
+ parent: ObjsInstance | null;
45
+ /** The raw first argument passed when the state method was called */
46
+ data: unknown;
47
+ [key: string]: unknown;
48
+ }
49
+
50
+ /** State function signature */
51
+ export type StateFunction = (params: StateParams) => void | unknown;
52
+
53
+ /** States map passed to o.init() */
54
+ export type StatesMap = {
55
+ render?: StateObject | StateFunction | string;
56
+ [stateName: string]: StateObject | StateFunction | string | undefined;
57
+ };
58
+
59
+ /** Test case tuple: [title, testFn | true | false | string] */
60
+ export type TestCase = [string, ((info: TestInfo) => boolean | string | undefined) | boolean | string | undefined];
61
+
62
+ export interface TestInfo {
63
+ n: number;
64
+ i: number;
65
+ title: string;
66
+ tShowOk: boolean;
67
+ tStyled: boolean;
68
+ }
69
+
70
+ /** Object returned by o.addTest() */
71
+ export interface TestHandle {
72
+ testId: number;
73
+ run(): void;
74
+ autorun(): void;
75
+ }
76
+
77
+ /** Object returned by o.newLoader() */
78
+ export interface ObjsLoader<T = unknown> {
79
+ reload(promise: Promise<T>): void;
80
+ isObjsLoader: true;
81
+ listeners: Array<[ObjsInstance, string, string?]>;
82
+ isFinished(): boolean;
83
+ getStore(): T | null;
84
+ connect(listener: ObjsInstance, state?: string, fail?: string): void;
85
+ disconnect(listener: ObjsInstance): void;
86
+ }
87
+
88
+ /** Element measurement result from o.measure() */
89
+ export interface MeasureResult {
90
+ width: number;
91
+ height: number;
92
+ top: number;
93
+ left: number;
94
+ visible: boolean;
95
+ opacity: string;
96
+ zIndex: string;
97
+ }
98
+
99
+ /** Recorded action entry */
100
+ export interface RecordedAction {
101
+ type: string;
102
+ target: string;
103
+ time: number;
104
+ scrollY?: number;
105
+ value?: string;
106
+ checked?: boolean;
107
+ key?: string;
108
+ code?: string;
109
+ targetType?: string;
110
+ /** When target matches multiple elements, selector for the repeated list item container */
111
+ listSelector?: string;
112
+ /** Index within listSelector matches for replay by index */
113
+ targetIndex?: number;
114
+ }
115
+
116
+ export type Assertion = {
117
+ actionIdx: number;
118
+ type: 'visible' | 'class' | 'style' | 'hidden' | 'disabled' | 'aria-expanded' | 'aria-checked';
119
+ selector: string;
120
+ text?: string;
121
+ className?: string;
122
+ style?: string;
123
+ hidden?: boolean;
124
+ disabled?: boolean;
125
+ ariaExpanded?: string | null;
126
+ ariaChecked?: string | null;
127
+ /** When selector matches multiple, selector for the list item container */
128
+ listSelector?: string;
129
+ /** Index within listSelector matches */
130
+ index?: number;
131
+ };
132
+
133
+ /** WebSocket connection and message capture */
134
+ export interface WebSocketEvent {
135
+ url: string;
136
+ protocol?: string;
137
+ open: boolean;
138
+ messages: Array<{ dir: 'in' | 'out'; data: string }>;
139
+ }
140
+
141
+ /** Optional strict replay defaults saved with the recording (from startRecording options) */
142
+ export interface RecordingStrictCapture {
143
+ assertions?: boolean;
144
+ network?: boolean;
145
+ websocket?: boolean;
146
+ }
147
+
148
+ /** Entry when a node was removed during recording (for lenient assertion skip or strict absence checks) */
149
+ export interface RecordingRemovedEntry {
150
+ actionIdx: number;
151
+ type: 'removed';
152
+ selector: string;
153
+ text?: string;
154
+ listSelector?: string;
155
+ index?: number;
156
+ }
157
+
158
+ /** Full recording object */
159
+ export interface Recording {
160
+ actions: RecordedAction[];
161
+ mocks: Record<string, { url: string; method: string; request: unknown; response: unknown; status: number }>;
162
+ initialData: Record<string, unknown>;
163
+ stepDelays?: Record<string, number>;
164
+ assertions: Assertion[];
165
+ observeRoot: string | null;
166
+ websocketEvents?: WebSocketEvent[];
167
+ removedElements?: RecordingRemovedEntry[];
168
+ strictCapture?: RecordingStrictCapture;
169
+ }
170
+
171
+ /**
172
+ * Reactive store returned by o.createStore().
173
+ * Components subscribe via store.subscribe(component, stateName).
174
+ * When store.notify() is called, each subscribed component's state method
175
+ * receives the full store object, whose properties are merged into the
176
+ * state context: { ...storeProps, self, o, i, parent, data }.
177
+ */
178
+ export interface ObjsStore<T extends object = Record<string, unknown>> {
179
+ _listeners: Array<(store: this) => void>;
180
+ /** Subscribe an Objs component state to this store.
181
+ * On every notify(), component[stateName](store) is called. */
182
+ subscribe(component: ObjsInstance, stateName: string): this;
183
+ /** Notify all subscribers — passes `this` store as the argument. */
184
+ notify(): void;
185
+ /** Restore store data from initial defaults; does not replace subscribe, notify, _listeners. */
186
+ reset(): void;
187
+ }
188
+
189
+ // ─── ObjsInstance (chainable result of o() / o.init()) ────────────────────
190
+
191
+ export interface ObjsInstance {
192
+ // Properties
193
+ els: Element[];
194
+ el: Element | undefined;
195
+ last: Element | undefined;
196
+ length: number;
197
+ initID: number | undefined;
198
+ ie: Record<string, Array<[EventListener, unknown?, unknown?]>>;
199
+ delegated: Record<string, EventListener[]>;
200
+ store: Record<string, unknown>;
201
+ states: string[];
202
+ isDebug: boolean;
203
+ currentState: string;
204
+ savedStates: Record<string, unknown>;
205
+ isRoot: boolean;
206
+ /** The ObjsInstance this component was appendInside() into, or null */
207
+ _parent: ObjsInstance | null;
208
+ /**
209
+ * Named sub-elements collected from `ref="name"` attributes in the rendered HTML.
210
+ * Populated automatically on first render; `ref` attributes are removed from the DOM.
211
+ * @example
212
+ * // In render: html: `<div ref="body"></div>`
213
+ * // After render: component.refs.body → ObjsInstance wrapping that div
214
+ */
215
+ refs: Record<string, ObjsInstance>;
216
+
217
+ // Selection
218
+ reset(query: QueryArg): ObjsInstance;
219
+ /** Select by index or by event: select(e) selects the element in this instance that contains e.target. */
220
+ select(i?: number | Event): ObjsInstance;
221
+ all(): ObjsInstance;
222
+ find(query: string): ObjsInstance;
223
+ first(query?: string): ObjsInstance;
224
+ add(el: QueryArg): ObjsInstance;
225
+ skip(i?: number): ObjsInstance;
226
+ remove(i?: number): ObjsInstance;
227
+
228
+ // State
229
+ init(states: StatesMap | StateFunction | string): ObjsInstance;
230
+ initState(state: StateObject, props?: Record<string, unknown>): ObjsInstance;
231
+ sample(state?: string): StatesMap;
232
+ saveState(stateId?: string, root?: boolean): ObjsInstance;
233
+ revertState(stateId?: string): ObjsInstance;
234
+ loseState(stateId: string): ObjsInstance;
235
+
236
+ // DOM manipulation
237
+ html(html?: string): string | ObjsInstance;
238
+ toString(): string;
239
+ [Symbol.toPrimitive](hint: string): string | number;
240
+ innerHTML(html?: string): string[] | ObjsInstance;
241
+ innerText(text: string): ObjsInstance;
242
+ textContent(content: string): ObjsInstance;
243
+ val(value?: string): string | ObjsInstance;
244
+ attr(attr: string, value?: string | null): string | string[] | ObjsInstance;
245
+ attrs(): Record<string, string> | Array<Record<string, string>>;
246
+ dataset(values?: Record<string, string>): Record<string, string> | Array<Record<string, string>> | ObjsInstance;
247
+ style(value?: string | null): ObjsInstance;
248
+ css(styles: Record<string, string> | null): ObjsInstance;
249
+ cssMerge(styles: Record<string, string | null | undefined> | null): ObjsInstance;
250
+ setClass(value: string): ObjsInstance;
251
+ addClass(...cls: string[]): ObjsInstance;
252
+ removeClass(...cls: string[]): ObjsInstance;
253
+ toggleClass(cls: string, rule?: boolean): ObjsInstance;
254
+ haveClass(cls: string): boolean;
255
+
256
+ // Events
257
+ on(events: string, handler: EventListener, options?: EventListenerOptions): ObjsInstance;
258
+ off(events: string, handler: EventListener, options?: EventListenerOptions): ObjsInstance;
259
+ offAll(event?: string): ObjsInstance;
260
+ onAll(event?: string): ObjsInstance;
261
+ onDelegate(events: string, selector: string, handler: EventListener): ObjsInstance;
262
+ offDelegate(event?: string): ObjsInstance;
263
+ onParent(events: string, selectorOrEl: string | Element, handler: EventListener): ObjsInstance;
264
+ offParent(events: string, query: string): ObjsInstance;
265
+
266
+ // DOM insertion
267
+ appendInside(el: QueryArg): ObjsInstance;
268
+ appendBefore(el: QueryArg): ObjsInstance;
269
+ appendAfter(el: QueryArg): ObjsInstance;
270
+
271
+ // Misc
272
+ forEach(fn: (ctx: { o: typeof o; self: ObjsInstance; i: number; el: Element }) => void): ObjsInstance;
273
+ connect(loader: ObjsLoader, state?: string, fail?: string): ObjsInstance;
274
+ getSSR(initId?: number): ObjsInstance;
275
+ saveAs(key: string): ObjsInstance;
276
+ unmount(): boolean;
277
+ take(query: QueryArg): ObjsInstance;
278
+
279
+ // React
280
+ prepareFor(createElement: Function, component?: Function): unknown;
281
+
282
+ // Debug (behind __DEV__ in build)
283
+ debug?: () => ObjsInstance;
284
+
285
+ // Dynamic state methods added by init()
286
+ render?: (props?: Record<string, unknown> | Array<Record<string, unknown>>) => ObjsInstance;
287
+ [stateName: string]: unknown;
288
+ }
289
+
290
+ type QueryArg = string | Element | Element[] | ObjsInstance | number;
291
+
292
+ // ─── o() function and static API ─────────────────────────────────────────
293
+
294
+ declare function o(query?: QueryArg): ObjsInstance;
295
+
296
+ declare namespace o {
297
+ // Core
298
+ const inits: ObjsInstance[];
299
+ const getSaved: Record<string, ObjsInstance>;
300
+ const errors: Error[];
301
+ let debug: boolean;
302
+ let showErrors: boolean;
303
+ function logErrors(): void;
304
+ function onError(error: Error, name?: string): void;
305
+ function reactRender(...args: unknown[]): unknown;
306
+
307
+ // Autotag
308
+ let autotag: string | undefined;
309
+
310
+ function reactQA(componentName: string): Record<string, string>;
311
+
312
+ // Element selection
313
+ function first(query: string): ObjsInstance;
314
+ function take(query: QueryArg): ObjsInstance;
315
+
316
+ // State
317
+ function init(states: StatesMap | StateFunction | string): ObjsInstance;
318
+ function initState(state: StateObject, props?: Record<string, unknown>): ObjsInstance;
319
+ function getStates(): (string[] | undefined)[];
320
+ function getStores(): (Record<string, unknown> | undefined)[];
321
+ function getListeners(): (Record<string, unknown> | undefined)[];
322
+
323
+ /**
324
+ * Create a reactive store — plain object with built-in subscribe / notify.
325
+ * Components subscribed via store.subscribe(comp, 'state') receive the store
326
+ * as their merged state context: { ...storeProps, self, o, i, parent, data }.
327
+ * @example
328
+ * const store = o.createStore({ count: 0, inc() { this.count++; this.notify(); } });
329
+ * store.subscribe(badge, 'sync'); // badge.sync(store) on every notify
330
+ */
331
+ function createStore<T extends object>(defaults: T): T & ObjsStore<T>;
332
+
333
+ // AJAX
334
+ function ajax(url: string, props?: Record<string, unknown>): Promise<Response>;
335
+ function get(url: string, props?: Record<string, unknown>): Promise<Response>;
336
+ function post(url: string, props?: Record<string, unknown>): Promise<Response>;
337
+ function getParams(key?: string): Record<string, string> | string;
338
+
339
+ // Loader
340
+ function newLoader<T = unknown>(promise?: Promise<T>): ObjsLoader<T>;
341
+
342
+ // Include
343
+ let incCache: boolean;
344
+ let incCacheExp: number;
345
+ let incTimeout: number;
346
+ let incSource: string;
347
+ let incForce: boolean;
348
+ let incAsync: boolean;
349
+ let incCors: boolean;
350
+ let incFns: Record<string, number>;
351
+ function inc(sources: Record<string, string>, callBack?: (setId: number) => void, callBad?: (setId: number) => void): number;
352
+ function incCheck(set?: number, fnId?: number, loaded?: number): boolean;
353
+ function incCacheClear(all?: boolean): boolean;
354
+
355
+ // Routing
356
+ function route(path: string | boolean | ((path: string) => boolean), task?: Function | object): boolean | object;
357
+ function router(routes: Record<string, Function | object>): boolean;
358
+
359
+ // Cookies
360
+ function setCookie(title: string, value?: string | number | boolean, params?: Record<string, unknown>): void;
361
+ function getCookie(title: string): string | undefined;
362
+ function deleteCookie(title: string): void;
363
+ function clearCookies(): void;
364
+ /** Clear cookies, localStorage, and test-related sessionStorage. Call after Cookies/LS/SS tests. */
365
+ function clearAfterTests(): void;
366
+
367
+ // Storage
368
+ function clearLocalStorage(all?: boolean): void;
369
+ function clearSessionStorage(onlyTests?: boolean): void;
370
+ function clearTestsStorage(): void;
371
+
372
+ // Utilities
373
+ function camelToKebab(str: string): string;
374
+ function kebabToCamel(str: string): string;
375
+ function verify(pairs: Array<[unknown, string | string[]]>, safe?: boolean): boolean | Error;
376
+ function safeVerify(pairs: Array<[unknown, string | string[]]>): boolean;
377
+ const C: (obj: object, key: string) => boolean;
378
+ const F: false;
379
+ const U: undefined;
380
+
381
+ // Store adapters (always present, prod + dev)
382
+ function connectRedux<S = unknown>(
383
+ store: { getState(): S; subscribe(listener: () => void): () => void },
384
+ selector: (state: S) => unknown,
385
+ component: ObjsInstance,
386
+ state?: string
387
+ ): () => void;
388
+
389
+ function connectMobX<T = unknown>(
390
+ mobx: { autorun(fn: () => void): () => void },
391
+ observable: T,
392
+ accessor: (obs: T) => unknown,
393
+ component: ObjsInstance,
394
+ state?: string
395
+ ): () => void;
396
+
397
+ let ObjsContext: unknown;
398
+
399
+ function withReactContext<C = unknown>(
400
+ React: { useContext(ctx: unknown): C; useEffect(fn: () => void, deps: unknown[]): void },
401
+ Context: unknown,
402
+ selector: (value: C) => unknown,
403
+ component: ObjsInstance,
404
+ state?: string
405
+ ): () => null;
406
+
407
+ // SSR
408
+ const DocumentMVP: {
409
+ createElement(tag: string): unknown;
410
+ addEventListener(): void;
411
+ parseElement(elem: unknown, outer?: boolean): string;
412
+ };
413
+ let D: Document | typeof DocumentMVP;
414
+
415
+ const recorder: {
416
+ active: boolean;
417
+ actions: RecordedAction[];
418
+ mocks: Recording['mocks'];
419
+ initialData: Record<string, unknown>;
420
+ assertions: Assertion[];
421
+ observeRoot: string | null;
422
+ strictCapture: RecordingStrictCapture | null;
423
+ _observer: MutationObserver | null;
424
+ };
425
+ /** Options object form for o.startRecording (observe/events/timeouts + optional strict capture flags on the recording) */
426
+ interface StartRecordingOptions {
427
+ observe?: string;
428
+ events?: string[];
429
+ timeouts?: Record<string, number>;
430
+ strictCaptureAssertions?: boolean;
431
+ strictCaptureNetwork?: boolean;
432
+ strictCaptureWebSocket?: boolean;
433
+ }
434
+ /**
435
+ * Start recording user interactions.
436
+ * Available in all builds QA testers can record on staging/production.
437
+ */
438
+ function startRecording(options: StartRecordingOptions): void;
439
+ /**
440
+ * @param observe CSS selector to scope the MutationObserver (reduces assertion noise)
441
+ * @param events Events to record (default: click, mouseover, scroll, input, change)
442
+ * @param timeouts Debounce delays per event type in ms
443
+ */
444
+ function startRecording(observe?: string, events?: string[], timeouts?: Record<string, number>): void;
445
+ function stopRecording(): Recording;
446
+ function clearRecording(id?: number): void;
447
+ function exportTest(
448
+ recording: Recording,
449
+ options?: { delay?: number; extensionExport?: boolean },
450
+ ): string;
451
+ /** Optional filters and strict modes for o.runRecordingAssertions */
452
+ interface RunRecordingAssertionsOpts {
453
+ assertions?: Assertion[];
454
+ removedElements?: Recording['removedElements'];
455
+ strictAssertions?: boolean;
456
+ /** When omitted, defaults to strictAssertions */
457
+ strictRemoved?: boolean;
458
+ }
459
+ /**
460
+ * Run recording assertions against the current DOM.
461
+ * @param recording Recording with assertions
462
+ * @param root Root element or selector (default: document)
463
+ * @param actionIdx When provided, run only assertions for this action index
464
+ * @param opts Optional assertion subset, removedElements, and strict DOM/removed flags
465
+ */
466
+ function runRecordingAssertions(
467
+ recording: Recording,
468
+ root?: Element | string,
469
+ actionIdx?: number,
470
+ opts?: RunRecordingAssertionsOpts
471
+ ): { passed: number; total: number; failures: Array<{ selector: string; message: string }> };
472
+ function exportPlaywrightTest(
473
+ recording: Recording,
474
+ options?: { testName?: string; baseUrl?: string }
475
+ ): string;
476
+
477
+ // Test framework
478
+ function sleep(ms: number): Promise<void>;
479
+ let tLog: string[];
480
+ let tRes: boolean[];
481
+ let tStatus: Array<Array<boolean | undefined>>;
482
+ let tFns: Array<((testN: number) => void) | undefined>;
483
+ let tShowOk: boolean;
484
+ let tStyled: boolean;
485
+ let tTime: number;
486
+ let tests: Array<{ title: string; tests: TestCase[]; hooks: { before?: Function; after?: Function } }>;
487
+ let tAutolog: boolean;
488
+ let tBeforeEach: ((info: TestInfo) => void) | undefined;
489
+ let tAfterEach: ((info: TestInfo, result: unknown) => void) | undefined;
490
+
491
+ /** Options for o.test when sync or confirmOnFailure is needed */
492
+ interface TestOptions {
493
+ sync?: boolean;
494
+ confirmOnFailure?: boolean;
495
+ confirmOnFailureTimeout?: number;
496
+ }
497
+ function test(
498
+ title: string,
499
+ ...cases: Array<TestCase | TestOptions | (() => void)>
500
+ ): number;
501
+ function addTest(title: string, ...cases: Array<TestCase | { before?: Function; after?: Function }>): TestHandle;
502
+ function runTest(testId?: number, autoRun?: boolean, savePrev?: boolean): void;
503
+ function testUpdate(info: TestInfo, result?: boolean | string, suffix?: string): void;
504
+ function updateLogs(): string[];
505
+
506
+ // Measurements
507
+ function measure(el: Element): MeasureResult;
508
+ function assertVisible(el: Element): boolean;
509
+ /** Expected size/padding/margin for design system or UI verification. All values in px. */
510
+ function assertSize(
511
+ el: Element,
512
+ expected?: {
513
+ w?: number;
514
+ h?: number;
515
+ padding?: number;
516
+ paddingTop?: number;
517
+ paddingRight?: number;
518
+ paddingBottom?: number;
519
+ paddingLeft?: number;
520
+ margin?: number;
521
+ marginTop?: number;
522
+ marginRight?: number;
523
+ marginBottom?: number;
524
+ marginLeft?: number;
525
+ }
526
+ ): boolean | string;
527
+
528
+ // Dev-only replay + overlay (depend on o.test framework)
529
+ /** Manual check inserted after an action or at end of playback */
530
+ interface ManualCheck {
531
+ afterAction: number | 'end';
532
+ label: string;
533
+ items: string[];
534
+ }
535
+ /** Options for o.playRecording when runAssertions is used */
536
+ interface PlayRecordingOptions {
537
+ runAssertions?: boolean;
538
+ root?: string;
539
+ actionDelay?: number;
540
+ manualChecks?: ManualCheck[];
541
+ mockOverrides?: Recording['mocks'];
542
+ skipWebSocketMock?: boolean;
543
+ skipNetworkMocks?: boolean;
544
+ recordingAssertionDebug?: boolean;
545
+ /** When true, enables strictAssertions, strictNetwork, and strictWebSocket (and strictRemoved follows strictAssertions). Per-flag opts still override when set. */
546
+ strictPlay?: boolean;
547
+ /** Exact list index, visible text equality (normalized), style/className equality, no list rescan */
548
+ strictAssertions?: boolean;
549
+ /** Require request body to match mock.request when replaying a mocked fetch/XHR */
550
+ strictNetwork?: boolean;
551
+ /** Require outbound WebSocket frames to match recorded order and payload */
552
+ strictWebSocket?: boolean;
553
+ /** When true, removed-element assertions verify absence instead of auto-pass */
554
+ strictRemoved?: boolean;
555
+ onComplete?: (assertionResult?: { passed: number; total: number; failures: Array<{ selector: string; message: string }> }) => void;
556
+ }
557
+ function playRecording(
558
+ recording: Recording,
559
+ opts?: Recording['mocks'] | PlayRecordingOptions
560
+ ): number | { testId: number };
561
+ function testOverlay(): void;
562
+ /** Common draggable overlay; used by testConfirm, testOverlay, confirmOnFailure */
563
+ function overlay(opts: {
564
+ innerHTML: string;
565
+ onClose?: (result?: { ok?: boolean; errors?: string[]; continue?: boolean }) => void;
566
+ timeout?: number;
567
+ excludeDragSelector?: string;
568
+ removeExisting?: boolean;
569
+ className?: string;
570
+ id?: string;
571
+ }): ObjsInstance;
572
+ /**
573
+ * Pause an Objs browser test; shows a draggable bar with "Test title: Paused", optional checklist (labels + checkboxes), and Continue.
574
+ * Dev-only. Returns ok: true if all items checked; ok: false with errors = list of unchecked item texts.
575
+ */
576
+ function testConfirm(
577
+ label: string,
578
+ items?: string[],
579
+ opts?: { confirm?: string; timeout?: number }
580
+ ): Promise<{ ok: boolean; errors?: string[] }>;
581
+ }
582
+
583
+ export { o };
584
+ export default o;