@asenajs/asena 0.7.0 → 0.8.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/README.md +2 -2
- package/dist/lib/adapter/AsenaAdapter.d.ts +5 -3
- package/dist/lib/adapter/AsenaAdapter.js.map +1 -1
- package/dist/lib/adapter/AsenaContext.d.ts +6 -0
- package/dist/lib/adapter/types/ServeOptions.d.ts +16 -0
- package/dist/lib/ioc/Container.d.ts +25 -0
- package/dist/lib/ioc/Container.js +47 -0
- package/dist/lib/ioc/Container.js.map +1 -1
- package/dist/lib/ioc/CoreContainer.d.ts +5 -3
- package/dist/lib/ioc/CoreContainer.js +9 -3
- package/dist/lib/ioc/CoreContainer.js.map +1 -1
- package/dist/lib/ioc/IocEngine.js +4 -5
- package/dist/lib/ioc/IocEngine.js.map +1 -1
- package/dist/lib/ioc/component/decorators/Inject.js +16 -0
- package/dist/lib/ioc/component/decorators/Inject.js.map +1 -1
- package/dist/lib/ioc/constants/ComponentConstants.d.ts +4 -0
- package/dist/lib/ioc/constants/ComponentConstants.js +5 -0
- package/dist/lib/ioc/constants/ComponentConstants.js.map +1 -1
- package/dist/lib/ioc/types/ICoreServiceNamesType.d.ts +1 -0
- package/dist/lib/ioc/types/ICoreServiceNamesType.js +1 -0
- package/dist/lib/ioc/types/ICoreServiceNamesType.js.map +1 -1
- package/dist/lib/ioc/types/InjectableComponent.d.ts +10 -0
- package/dist/lib/ioc/types/decorators/ComponentType.d.ts +2 -1
- package/dist/lib/ioc/types/decorators/ComponentType.js +1 -0
- package/dist/lib/ioc/types/decorators/ComponentType.js.map +1 -1
- package/dist/lib/server/AsenaServer.d.ts +46 -3
- package/dist/lib/server/AsenaServer.js +159 -29
- package/dist/lib/server/AsenaServer.js.map +1 -1
- package/dist/lib/server/AsenaServerFactory.d.ts +38 -3
- package/dist/lib/server/AsenaServerFactory.js +21 -2
- package/dist/lib/server/AsenaServerFactory.js.map +1 -1
- package/dist/lib/server/config/AsenaConfig.d.ts +82 -4
- package/dist/lib/server/config/AsenaConfig.js +39 -1
- package/dist/lib/server/config/AsenaConfig.js.map +1 -1
- package/dist/lib/server/decorators/components/FrontendController.js.map +1 -1
- package/dist/lib/server/decorators/components/MessageController.d.ts +49 -0
- package/dist/lib/server/decorators/components/MessageController.js +62 -0
- package/dist/lib/server/decorators/components/MessageController.js.map +1 -0
- package/dist/lib/server/decorators/components/index.d.ts +1 -0
- package/dist/lib/server/decorators/components/index.js +1 -0
- package/dist/lib/server/decorators/components/index.js.map +1 -1
- package/dist/lib/server/event/EventDispatchService.d.ts +3 -7
- package/dist/lib/server/event/EventDispatchService.js +9 -40
- package/dist/lib/server/event/EventDispatchService.js.map +1 -1
- package/dist/lib/server/event/PatternHandlerIndex.d.ts +53 -0
- package/dist/lib/server/event/PatternHandlerIndex.js +100 -0
- package/dist/lib/server/event/PatternHandlerIndex.js.map +1 -0
- package/dist/lib/server/event/decorators/On.d.ts +4 -0
- package/dist/lib/server/event/decorators/On.js +6 -0
- package/dist/lib/server/event/decorators/On.js.map +1 -1
- package/dist/lib/server/event/index.d.ts +1 -0
- package/dist/lib/server/event/index.js +2 -0
- package/dist/lib/server/event/index.js.map +1 -1
- package/dist/lib/server/event/types.d.ts +12 -0
- package/dist/lib/server/health/HealthServer.d.ts +26 -0
- package/dist/lib/server/health/HealthServer.js +63 -0
- package/dist/lib/server/health/HealthServer.js.map +1 -0
- package/dist/lib/server/health/index.d.ts +1 -0
- package/dist/lib/server/health/index.js +2 -0
- package/dist/lib/server/health/index.js.map +1 -0
- package/dist/lib/server/messaging/Ulak.d.ts +156 -0
- package/dist/lib/server/messaging/Ulak.js +174 -0
- package/dist/lib/server/messaging/Ulak.js.map +1 -1
- package/dist/lib/server/messaging/types.d.ts +5 -1
- package/dist/lib/server/messaging/types.js +5 -0
- package/dist/lib/server/messaging/types.js.map +1 -1
- package/dist/lib/server/microservice/InMemoryTransport.d.ts +45 -0
- package/dist/lib/server/microservice/InMemoryTransport.js +124 -0
- package/dist/lib/server/microservice/InMemoryTransport.js.map +1 -0
- package/dist/lib/server/microservice/MicroserviceTransport.d.ts +132 -0
- package/dist/lib/server/microservice/MicroserviceTransport.js +2 -0
- package/dist/lib/server/microservice/MicroserviceTransport.js.map +1 -0
- package/dist/lib/server/microservice/decorators/EventPattern.d.ts +38 -0
- package/dist/lib/server/microservice/decorators/EventPattern.js +59 -0
- package/dist/lib/server/microservice/decorators/EventPattern.js.map +1 -0
- package/dist/lib/server/microservice/decorators/MessagePattern.d.ts +31 -0
- package/dist/lib/server/microservice/decorators/MessagePattern.js +55 -0
- package/dist/lib/server/microservice/decorators/MessagePattern.js.map +1 -0
- package/dist/lib/server/microservice/decorators/index.d.ts +2 -0
- package/dist/lib/server/microservice/decorators/index.js +3 -0
- package/dist/lib/server/microservice/decorators/index.js.map +1 -0
- package/dist/lib/server/microservice/index.d.ts +5 -0
- package/dist/lib/server/microservice/index.js +11 -0
- package/dist/lib/server/microservice/index.js.map +1 -0
- package/dist/lib/server/microservice/interceptorChain.d.ts +20 -0
- package/dist/lib/server/microservice/interceptorChain.js +39 -0
- package/dist/lib/server/microservice/interceptorChain.js.map +1 -0
- package/dist/lib/server/microservice/types.d.ts +142 -0
- package/dist/lib/server/microservice/types.js +5 -0
- package/dist/lib/server/microservice/types.js.map +1 -0
- package/dist/lib/server/src/services/PrepareEventService.d.ts +4 -1
- package/dist/lib/server/src/services/PrepareEventService.js +10 -3
- package/dist/lib/server/src/services/PrepareEventService.js.map +1 -1
- package/dist/lib/server/src/services/PrepareFrontendControllerService.d.ts +1 -0
- package/dist/lib/server/src/services/PrepareFrontendControllerService.js +1 -1
- package/dist/lib/server/src/services/PrepareFrontendControllerService.js.map +1 -1
- package/dist/lib/server/src/services/PrepareMicroserviceService.d.ts +66 -0
- package/dist/lib/server/src/services/PrepareMicroserviceService.js +193 -0
- package/dist/lib/server/src/services/PrepareMicroserviceService.js.map +1 -0
- package/dist/lib/server/src/services/PrepareScheduleService.js.map +1 -1
- package/dist/lib/server/web/decorators/methods/Connect.js +9 -1
- package/dist/lib/server/web/decorators/methods/Connect.js.map +1 -1
- package/dist/lib/server/web/decorators/methods/Options.js +9 -1
- package/dist/lib/server/web/decorators/methods/Options.js.map +1 -1
- package/dist/lib/server/web/websocket/AsenaSocket.js.map +1 -1
- package/dist/lib/test/factory/deepMock.d.ts +25 -0
- package/dist/lib/test/factory/deepMock.js +105 -0
- package/dist/lib/test/factory/deepMock.js.map +1 -0
- package/dist/lib/test/factory/ulakStub.d.ts +44 -0
- package/dist/lib/test/factory/ulakStub.js +40 -0
- package/dist/lib/test/factory/ulakStub.js.map +1 -0
- package/dist/lib/test/harness/createTestApp.d.ts +33 -0
- package/dist/lib/test/harness/createTestApp.js +105 -0
- package/dist/lib/test/harness/createTestApp.js.map +1 -0
- package/dist/lib/test/harness/createWebTest.d.ts +28 -0
- package/dist/lib/test/harness/createWebTest.js +127 -0
- package/dist/lib/test/harness/createWebTest.js.map +1 -0
- package/dist/lib/test/harness/silentLogger.d.ts +27 -0
- package/dist/lib/test/harness/silentLogger.js +54 -0
- package/dist/lib/test/harness/silentLogger.js.map +1 -0
- package/dist/lib/test/harness/types.d.ts +147 -0
- package/dist/lib/test/harness/types.js +2 -0
- package/dist/lib/test/harness/types.js.map +1 -0
- package/dist/lib/test/http/TestHttpCall.d.ts +78 -0
- package/dist/lib/test/http/TestHttpCall.js +149 -0
- package/dist/lib/test/http/TestHttpCall.js.map +1 -0
- package/dist/lib/test/http/TestHttpResponse.d.ts +46 -0
- package/dist/lib/test/http/TestHttpResponse.js +70 -0
- package/dist/lib/test/http/TestHttpResponse.js.map +1 -0
- package/dist/lib/test/index.d.ts +9 -0
- package/dist/lib/test/index.js +8 -0
- package/dist/lib/test/index.js.map +1 -1
- package/dist/lib/test/metadata/discovery.d.ts +21 -0
- package/dist/lib/test/metadata/discovery.js +37 -8
- package/dist/lib/test/metadata/discovery.js.map +1 -1
- package/dist/lib/test/mockComponent.d.ts +25 -0
- package/dist/lib/test/mockComponent.js +39 -6
- package/dist/lib/test/mockComponent.js.map +1 -1
- package/dist/lib/test/types.d.ts +13 -0
- package/package.json +11 -7
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { AsenaAdapter } from '../../adapter';
|
|
2
|
+
import type { Container } from '../../ioc';
|
|
3
|
+
import type { ServerLogger } from '../../logger';
|
|
4
|
+
import type { AsenaServer } from '../../server/AsenaServer';
|
|
5
|
+
import type { Class } from '../../server/types';
|
|
6
|
+
import type { TestHttpCall } from '../http/TestHttpCall';
|
|
7
|
+
/**
|
|
8
|
+
* How the test app accepts requests.
|
|
9
|
+
*
|
|
10
|
+
* - `server` - listens on a TCP port (default). Matches production exactly.
|
|
11
|
+
* - `socket` - listens on a unix domain socket instead. Still the adapter's real routing
|
|
12
|
+
* pipeline, but occupies no port, so parallel suites cannot collide. Requires an adapter
|
|
13
|
+
* that honours `AsenaStartOptions.unix`.
|
|
14
|
+
*/
|
|
15
|
+
export type DispatchMode = 'server' | 'socket';
|
|
16
|
+
/**
|
|
17
|
+
* Options for {@link createTestApp}
|
|
18
|
+
*/
|
|
19
|
+
export interface TestAppOptions<A extends AsenaAdapter<any, any> = AsenaAdapter<any, any>> {
|
|
20
|
+
/**
|
|
21
|
+
* The adapter instance to boot, e.g. `createHonoAdapter({ logger: silentLogger })[0]`
|
|
22
|
+
*/
|
|
23
|
+
adapter: A;
|
|
24
|
+
/**
|
|
25
|
+
* Components to register. Passing this list skips filesystem scanning entirely.
|
|
26
|
+
*/
|
|
27
|
+
components: Class[];
|
|
28
|
+
/**
|
|
29
|
+
* Replace registered components with test doubles, keyed by service name (`@MockBean`).
|
|
30
|
+
*
|
|
31
|
+
* Override services, not controllers: a plain object mock carries no `@Controller`
|
|
32
|
+
* metadata, so an overridden controller's routes would never be registered.
|
|
33
|
+
*/
|
|
34
|
+
overrides?: Record<string, object>;
|
|
35
|
+
/**
|
|
36
|
+
* Logger for the booted server
|
|
37
|
+
*
|
|
38
|
+
* @default silentLogger
|
|
39
|
+
*/
|
|
40
|
+
logger?: ServerLogger;
|
|
41
|
+
/**
|
|
42
|
+
* Port to bind. Ignored in `socket` dispatch.
|
|
43
|
+
*
|
|
44
|
+
* @default 0 - Bun picks a free ephemeral port, which removes the port race entirely
|
|
45
|
+
*/
|
|
46
|
+
port?: number;
|
|
47
|
+
/**
|
|
48
|
+
* @default 'server'
|
|
49
|
+
*/
|
|
50
|
+
dispatch?: DispatchMode;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A booted test application
|
|
54
|
+
*/
|
|
55
|
+
export interface TestApp {
|
|
56
|
+
/**
|
|
57
|
+
* The running server
|
|
58
|
+
*/
|
|
59
|
+
readonly server: AsenaServer<any>;
|
|
60
|
+
/**
|
|
61
|
+
* The user component container
|
|
62
|
+
*/
|
|
63
|
+
readonly container: Container;
|
|
64
|
+
/**
|
|
65
|
+
* The bound TCP port. `0` in `socket` dispatch, where no port is used.
|
|
66
|
+
*/
|
|
67
|
+
readonly port: number;
|
|
68
|
+
/**
|
|
69
|
+
* Base URL for HTTP calls. In `socket` dispatch the host is a placeholder - routing
|
|
70
|
+
* happens over {@link socketPath}.
|
|
71
|
+
*/
|
|
72
|
+
readonly baseUrl: string;
|
|
73
|
+
/**
|
|
74
|
+
* Path of the unix socket, when running in `socket` dispatch
|
|
75
|
+
*/
|
|
76
|
+
readonly socketPath?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Resolves a component from the container
|
|
79
|
+
*
|
|
80
|
+
* @param name - Registered service name
|
|
81
|
+
*/
|
|
82
|
+
resolve<T>(name: string): Promise<T>;
|
|
83
|
+
/**
|
|
84
|
+
* Builds a WebSocket URL for the given path, correct for the active dispatch mode
|
|
85
|
+
*
|
|
86
|
+
* @param path - Route path, e.g. `/ws/chat`
|
|
87
|
+
*/
|
|
88
|
+
wsUrl(path: string): string;
|
|
89
|
+
/**
|
|
90
|
+
* Starts a request with an explicit method in `init`
|
|
91
|
+
*
|
|
92
|
+
* @param path - Path relative to the app root
|
|
93
|
+
* @param init - Standard fetch init
|
|
94
|
+
*/
|
|
95
|
+
request(path: string, init?: RequestInit): TestHttpCall;
|
|
96
|
+
get(path: string, init?: RequestInit): TestHttpCall;
|
|
97
|
+
post(path: string, init?: RequestInit): TestHttpCall;
|
|
98
|
+
put(path: string, init?: RequestInit): TestHttpCall;
|
|
99
|
+
patch(path: string, init?: RequestInit): TestHttpCall;
|
|
100
|
+
delete(path: string, init?: RequestInit): TestHttpCall;
|
|
101
|
+
head(path: string, init?: RequestInit): TestHttpCall;
|
|
102
|
+
options(path: string, init?: RequestInit): TestHttpCall;
|
|
103
|
+
/**
|
|
104
|
+
* Stops the server and removes the unix socket if one was created. Idempotent.
|
|
105
|
+
*/
|
|
106
|
+
stop(): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Enables `await using app = await createTestApp(...)`
|
|
109
|
+
*/
|
|
110
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Options for {@link createWebTest}
|
|
114
|
+
*/
|
|
115
|
+
export interface WebTestOptions<A extends AsenaAdapter<any, any> = AsenaAdapter<any, any>> {
|
|
116
|
+
adapter: A;
|
|
117
|
+
/**
|
|
118
|
+
* Controller(s) under test
|
|
119
|
+
*/
|
|
120
|
+
controllers: Class | Class[];
|
|
121
|
+
/**
|
|
122
|
+
* Extra components to register for real, alongside the controllers' middlewares and
|
|
123
|
+
* validators (which are always real, since the framework resolves them by name)
|
|
124
|
+
*/
|
|
125
|
+
components?: Class[];
|
|
126
|
+
/**
|
|
127
|
+
* Explicit doubles, keyed by service name. These win over the generated auto-mocks.
|
|
128
|
+
*/
|
|
129
|
+
overrides?: Record<string, object>;
|
|
130
|
+
logger?: ServerLogger;
|
|
131
|
+
port?: number;
|
|
132
|
+
dispatch?: DispatchMode;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Result of {@link createWebTest}
|
|
136
|
+
*/
|
|
137
|
+
export interface WebTestResult {
|
|
138
|
+
app: TestApp;
|
|
139
|
+
/**
|
|
140
|
+
* Every double in play - both auto-generated mocks and the explicit `overrides` - keyed
|
|
141
|
+
* by **service name** (not field name, unlike `mockComponent`).
|
|
142
|
+
*
|
|
143
|
+
* One shared mock per service, so two controllers injecting the same service see the
|
|
144
|
+
* same double.
|
|
145
|
+
*/
|
|
146
|
+
mocks: Record<string, any>;
|
|
147
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../lib/test/harness/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { TestHttpResponse } from './TestHttpResponse';
|
|
2
|
+
/**
|
|
3
|
+
* A pending HTTP call with a fluent assertion chain.
|
|
4
|
+
*
|
|
5
|
+
* Nothing is sent until the call is awaited. Assertions queue up in the order they were
|
|
6
|
+
* chained and run against the buffered response; the send is memoized, so awaiting the
|
|
7
|
+
* same call twice does not issue a second request.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* await app.get('/users/1').expectStatus(200).expectJson({ id: '1', name: 'Ada' });
|
|
12
|
+
*
|
|
13
|
+
* const res = await app.post('/users', { body: JSON.stringify({ name: 'Ada' }) }).expectStatus(201);
|
|
14
|
+
* expect(res.json().id).toBeString();
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare class TestHttpCall implements PromiseLike<TestHttpResponse> {
|
|
18
|
+
private readonly assertions;
|
|
19
|
+
private pending?;
|
|
20
|
+
private readonly url;
|
|
21
|
+
private readonly init?;
|
|
22
|
+
constructor(url: string, init?: RequestInit);
|
|
23
|
+
/**
|
|
24
|
+
* Asserts the response status code
|
|
25
|
+
*
|
|
26
|
+
* @param expected - Expected status
|
|
27
|
+
* @returns this, for chaining
|
|
28
|
+
*/
|
|
29
|
+
expectStatus(expected: number): this;
|
|
30
|
+
/**
|
|
31
|
+
* Asserts a response header
|
|
32
|
+
*
|
|
33
|
+
* @param name - Header name (case-insensitive)
|
|
34
|
+
* @param expected - Exact value, or a pattern to match
|
|
35
|
+
* @returns this, for chaining
|
|
36
|
+
*/
|
|
37
|
+
expectHeader(name: string, expected: string | RegExp): this;
|
|
38
|
+
/**
|
|
39
|
+
* Asserts the whole JSON body deep-equals the expected value
|
|
40
|
+
*
|
|
41
|
+
* @param expected - Expected body
|
|
42
|
+
* @returns this, for chaining
|
|
43
|
+
*/
|
|
44
|
+
expectJson<T>(expected: T): this;
|
|
45
|
+
/**
|
|
46
|
+
* Asserts the JSON body contains at least the given properties
|
|
47
|
+
*
|
|
48
|
+
* @param partial - Subset the body must match
|
|
49
|
+
* @returns this, for chaining
|
|
50
|
+
*/
|
|
51
|
+
expectJsonContains(partial: object): this;
|
|
52
|
+
/**
|
|
53
|
+
* Asserts the raw text body
|
|
54
|
+
*
|
|
55
|
+
* @param expected - Exact body, or a pattern to match
|
|
56
|
+
* @returns this, for chaining
|
|
57
|
+
*/
|
|
58
|
+
expectBody(expected: string | RegExp): this;
|
|
59
|
+
/**
|
|
60
|
+
* Escape hatch for assertions the chain does not cover
|
|
61
|
+
*
|
|
62
|
+
* @param assertion - Runs against the buffered response, may be async
|
|
63
|
+
* @returns this, for chaining
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* await app.get('/users').expect((res) => {
|
|
68
|
+
* expect(res.json<User[]>()).toHaveLength(3);
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
expect(assertion: (response: TestHttpResponse) => void | Promise<void>): this;
|
|
73
|
+
then<TResult1 = TestHttpResponse, TResult2 = never>(onfulfilled?: ((value: TestHttpResponse) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
|
|
74
|
+
private enqueue;
|
|
75
|
+
private send;
|
|
76
|
+
private run;
|
|
77
|
+
private describeRequest;
|
|
78
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { expect } from 'bun:test';
|
|
2
|
+
import { TestHttpResponse, truncate } from './TestHttpResponse';
|
|
3
|
+
/**
|
|
4
|
+
* A pending HTTP call with a fluent assertion chain.
|
|
5
|
+
*
|
|
6
|
+
* Nothing is sent until the call is awaited. Assertions queue up in the order they were
|
|
7
|
+
* chained and run against the buffered response; the send is memoized, so awaiting the
|
|
8
|
+
* same call twice does not issue a second request.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* await app.get('/users/1').expectStatus(200).expectJson({ id: '1', name: 'Ada' });
|
|
13
|
+
*
|
|
14
|
+
* const res = await app.post('/users', { body: JSON.stringify({ name: 'Ada' }) }).expectStatus(201);
|
|
15
|
+
* expect(res.json().id).toBeString();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export class TestHttpCall {
|
|
19
|
+
assertions = [];
|
|
20
|
+
pending;
|
|
21
|
+
url;
|
|
22
|
+
init;
|
|
23
|
+
constructor(url, init) {
|
|
24
|
+
this.url = url;
|
|
25
|
+
this.init = init;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Asserts the response status code
|
|
29
|
+
*
|
|
30
|
+
* @param expected - Expected status
|
|
31
|
+
* @returns this, for chaining
|
|
32
|
+
*/
|
|
33
|
+
expectStatus(expected) {
|
|
34
|
+
// Deliberately not `expect(status).toBe(n)`: a bare number mismatch is the least
|
|
35
|
+
// useful failure in an HTTP test. The body almost always explains why.
|
|
36
|
+
return this.enqueue((response) => {
|
|
37
|
+
if (response.status !== expected) {
|
|
38
|
+
throw new Error(`Expected status ${expected} but received ${response.status} for ${this.describeRequest()}\n` +
|
|
39
|
+
`Body: ${truncate(response.text())}`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Asserts a response header
|
|
45
|
+
*
|
|
46
|
+
* @param name - Header name (case-insensitive)
|
|
47
|
+
* @param expected - Exact value, or a pattern to match
|
|
48
|
+
* @returns this, for chaining
|
|
49
|
+
*/
|
|
50
|
+
expectHeader(name, expected) {
|
|
51
|
+
return this.enqueue((response) => {
|
|
52
|
+
const actual = response.headers.get(name);
|
|
53
|
+
if (actual === null) {
|
|
54
|
+
const received = [];
|
|
55
|
+
response.headers.forEach((_value, key) => received.push(key));
|
|
56
|
+
throw new Error(`Expected header '${name}' to be present for ${this.describeRequest()}, but it was not sent.\n` +
|
|
57
|
+
`Received headers: ${received.join(', ') || '<none>'}`);
|
|
58
|
+
}
|
|
59
|
+
if (expected instanceof RegExp) {
|
|
60
|
+
expect(actual).toMatch(expected);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
expect(actual).toBe(expected);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Asserts the whole JSON body deep-equals the expected value
|
|
69
|
+
*
|
|
70
|
+
* @param expected - Expected body
|
|
71
|
+
* @returns this, for chaining
|
|
72
|
+
*/
|
|
73
|
+
expectJson(expected) {
|
|
74
|
+
return this.enqueue((response) => {
|
|
75
|
+
expect(response.json()).toEqual(expected);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Asserts the JSON body contains at least the given properties
|
|
80
|
+
*
|
|
81
|
+
* @param partial - Subset the body must match
|
|
82
|
+
* @returns this, for chaining
|
|
83
|
+
*/
|
|
84
|
+
expectJsonContains(partial) {
|
|
85
|
+
return this.enqueue((response) => {
|
|
86
|
+
expect(response.json()).toMatchObject(partial);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Asserts the raw text body
|
|
91
|
+
*
|
|
92
|
+
* @param expected - Exact body, or a pattern to match
|
|
93
|
+
* @returns this, for chaining
|
|
94
|
+
*/
|
|
95
|
+
expectBody(expected) {
|
|
96
|
+
return this.enqueue((response) => {
|
|
97
|
+
if (expected instanceof RegExp) {
|
|
98
|
+
expect(response.text()).toMatch(expected);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
expect(response.text()).toBe(expected);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Escape hatch for assertions the chain does not cover
|
|
107
|
+
*
|
|
108
|
+
* @param assertion - Runs against the buffered response, may be async
|
|
109
|
+
* @returns this, for chaining
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* await app.get('/users').expect((res) => {
|
|
114
|
+
* expect(res.json<User[]>()).toHaveLength(3);
|
|
115
|
+
* });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
expect(assertion) {
|
|
119
|
+
return this.enqueue(assertion);
|
|
120
|
+
}
|
|
121
|
+
then(onfulfilled, onrejected) {
|
|
122
|
+
return this.send().then(onfulfilled, onrejected);
|
|
123
|
+
}
|
|
124
|
+
enqueue(assertion) {
|
|
125
|
+
if (this.pending !== undefined) {
|
|
126
|
+
throw new Error('Cannot add assertions to a TestHttpCall that has already been sent. ' +
|
|
127
|
+
'Chain every expect* call before awaiting the request.');
|
|
128
|
+
}
|
|
129
|
+
this.assertions.push(assertion);
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
send() {
|
|
133
|
+
if (this.pending === undefined) {
|
|
134
|
+
this.pending = this.run();
|
|
135
|
+
}
|
|
136
|
+
return this.pending;
|
|
137
|
+
}
|
|
138
|
+
async run() {
|
|
139
|
+
const response = await TestHttpResponse.from(await fetch(this.url, this.init));
|
|
140
|
+
for (const assertion of this.assertions) {
|
|
141
|
+
await assertion(response);
|
|
142
|
+
}
|
|
143
|
+
return response;
|
|
144
|
+
}
|
|
145
|
+
describeRequest() {
|
|
146
|
+
return `${(this.init?.method ?? 'GET').toUpperCase()} ${this.url}`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=TestHttpCall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestHttpCall.js","sourceRoot":"","sources":["../../../../lib/test/http/TestHttpCall.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A response whose body has already been read.
|
|
3
|
+
*
|
|
4
|
+
* `Response` bodies can only be consumed once, which makes them awkward to assert against
|
|
5
|
+
* more than one way. This buffers the body at construction so `text()` and `json()` are
|
|
6
|
+
* synchronous and repeatable.
|
|
7
|
+
*/
|
|
8
|
+
export declare class TestHttpResponse {
|
|
9
|
+
/**
|
|
10
|
+
* The underlying fetch Response. Its body stream is already consumed.
|
|
11
|
+
*/
|
|
12
|
+
readonly raw: Response;
|
|
13
|
+
private readonly body;
|
|
14
|
+
private constructor();
|
|
15
|
+
/**
|
|
16
|
+
* Buffers a fetch Response into a TestHttpResponse
|
|
17
|
+
*
|
|
18
|
+
* @param raw - Response to consume
|
|
19
|
+
* @returns The buffered response
|
|
20
|
+
*/
|
|
21
|
+
static from(raw: Response): Promise<TestHttpResponse>;
|
|
22
|
+
get status(): number;
|
|
23
|
+
get headers(): Headers;
|
|
24
|
+
/**
|
|
25
|
+
* The response body as text
|
|
26
|
+
*
|
|
27
|
+
* @returns Body string
|
|
28
|
+
*/
|
|
29
|
+
text(): string;
|
|
30
|
+
/**
|
|
31
|
+
* The response body parsed as JSON
|
|
32
|
+
*
|
|
33
|
+
* @returns Parsed body
|
|
34
|
+
* @throws When the body is not valid JSON - the message includes the raw body
|
|
35
|
+
*/
|
|
36
|
+
json<T = any>(): T;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Shortens a body for error messages so a large HTML page cannot bury the failure
|
|
40
|
+
*
|
|
41
|
+
* @param value - Body text
|
|
42
|
+
* @returns Truncated text
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
export declare function truncate(value: string, limit?: number): string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A response whose body has already been read.
|
|
3
|
+
*
|
|
4
|
+
* `Response` bodies can only be consumed once, which makes them awkward to assert against
|
|
5
|
+
* more than one way. This buffers the body at construction so `text()` and `json()` are
|
|
6
|
+
* synchronous and repeatable.
|
|
7
|
+
*/
|
|
8
|
+
export class TestHttpResponse {
|
|
9
|
+
/**
|
|
10
|
+
* The underlying fetch Response. Its body stream is already consumed.
|
|
11
|
+
*/
|
|
12
|
+
raw;
|
|
13
|
+
body;
|
|
14
|
+
constructor(raw, body) {
|
|
15
|
+
this.raw = raw;
|
|
16
|
+
this.body = body;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Buffers a fetch Response into a TestHttpResponse
|
|
20
|
+
*
|
|
21
|
+
* @param raw - Response to consume
|
|
22
|
+
* @returns The buffered response
|
|
23
|
+
*/
|
|
24
|
+
static async from(raw) {
|
|
25
|
+
return new TestHttpResponse(raw, await raw.text());
|
|
26
|
+
}
|
|
27
|
+
get status() {
|
|
28
|
+
return this.raw.status;
|
|
29
|
+
}
|
|
30
|
+
get headers() {
|
|
31
|
+
return this.raw.headers;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The response body as text
|
|
35
|
+
*
|
|
36
|
+
* @returns Body string
|
|
37
|
+
*/
|
|
38
|
+
text() {
|
|
39
|
+
return this.body;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The response body parsed as JSON
|
|
43
|
+
*
|
|
44
|
+
* @returns Parsed body
|
|
45
|
+
* @throws When the body is not valid JSON - the message includes the raw body
|
|
46
|
+
*/
|
|
47
|
+
json() {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(this.body);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new Error(`Expected a JSON body but parsing failed. Received: ${truncate(this.body)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Shortens a body for error messages so a large HTML page cannot bury the failure
|
|
58
|
+
*
|
|
59
|
+
* @param value - Body text
|
|
60
|
+
* @returns Truncated text
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export function truncate(value, limit = 500) {
|
|
65
|
+
if (value.length <= limit) {
|
|
66
|
+
return value.length ? value : '<empty body>';
|
|
67
|
+
}
|
|
68
|
+
return `${value.slice(0, limit)}... (${value.length} bytes total)`;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=TestHttpResponse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestHttpResponse.js","sourceRoot":"","sources":["../../../../lib/test/http/TestHttpResponse.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,OAAO,gBAAgB;IAC3B;;OAEG;IACa,GAAG,CAAW;IAEb,IAAI,CAAS;IAE9B,YAAoB,GAAa,EAAE,IAAY;QAC7C,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAa;QACpC,OAAO,IAAI,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,IAAW,MAAM;QACf,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;IACzB,CAAC;IAED,IAAW,OAAO;QAChB,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;IAC1B,CAAC;IAED;;;;OAIG;IACI,IAAI;QACT,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED;;;;;OAKG;IACI,IAAI;QACT,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAM,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,sDAAsD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/F,CAAC;IACH,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAa,EAAE,KAAK,GAAG,GAAG;IACjD,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC;IAC/C,CAAC;IAED,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,KAAK,CAAC,MAAM,eAAe,CAAC;AACrE,CAAC"}
|
package/dist/lib/test/index.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
export { mockComponent, mockComponentAsync } from './mockComponent';
|
|
2
2
|
export type { MockComponentOptions, MockedComponent } from './types';
|
|
3
3
|
export { createMockFromClass } from './factory/mockFactory';
|
|
4
|
+
export { createDeepMock } from './factory/deepMock';
|
|
5
|
+
export { createTestUlakStub, type TestUlakNameSpace } from './factory/ulakStub';
|
|
6
|
+
export { discoverInjectedFields, discoverInjectedFieldsFromClass } from './metadata/discovery';
|
|
7
|
+
export { createTestApp } from './harness/createTestApp';
|
|
8
|
+
export { createWebTest } from './harness/createWebTest';
|
|
9
|
+
export { createCapturingLogger, silentLogger, type CapturedLogEntry } from './harness/silentLogger';
|
|
10
|
+
export type { DispatchMode, TestApp, TestAppOptions, WebTestOptions, WebTestResult } from './harness/types';
|
|
11
|
+
export { TestHttpCall } from './http/TestHttpCall';
|
|
12
|
+
export { TestHttpResponse } from './http/TestHttpResponse';
|
package/dist/lib/test/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
export { mockComponent, mockComponentAsync } from './mockComponent';
|
|
2
2
|
export { createMockFromClass } from './factory/mockFactory';
|
|
3
|
+
export { createDeepMock } from './factory/deepMock';
|
|
4
|
+
export { createTestUlakStub } from './factory/ulakStub';
|
|
5
|
+
export { discoverInjectedFields, discoverInjectedFieldsFromClass } from './metadata/discovery';
|
|
6
|
+
export { createTestApp } from './harness/createTestApp';
|
|
7
|
+
export { createWebTest } from './harness/createWebTest';
|
|
8
|
+
export { createCapturingLogger, silentLogger } from './harness/silentLogger';
|
|
9
|
+
export { TestHttpCall } from './http/TestHttpCall';
|
|
10
|
+
export { TestHttpResponse } from './http/TestHttpResponse';
|
|
3
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAA0B,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,+BAA+B,EAAE,MAAM,sBAAsB,CAAC;AAC/F,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAyB,MAAM,wBAAwB,CAAC;AAEpG,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -16,6 +16,27 @@ import type { FieldMetadata } from '../types';
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
export declare function discoverInjectedFields(instance: any): FieldMetadata[];
|
|
19
|
+
/**
|
|
20
|
+
* Discovers all fields with @Inject decorator in a component class
|
|
21
|
+
* Traverses the constructor chain to support inheritance - no instantiation required
|
|
22
|
+
*
|
|
23
|
+
* This is the class-based counterpart of {@link discoverInjectedFields}. It is what
|
|
24
|
+
* slice tests use, since they must inspect a component's dependencies before deciding
|
|
25
|
+
* whether to register it for real or replace it with a mock.
|
|
26
|
+
*
|
|
27
|
+
* @param ComponentClass - Component class to inspect
|
|
28
|
+
* @returns Array of field metadata containing field names, service names, class refs and expressions
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const fields = discoverInjectedFieldsFromClass(AuthService);
|
|
33
|
+
* // Returns: [
|
|
34
|
+
* // { fieldName: 'userService', serviceName: 'UserService', serviceClass: UserService },
|
|
35
|
+
* // { fieldName: 'chat', serviceName: '__Ulak__', expression: (u) => u.namespace('/chat') }
|
|
36
|
+
* // ]
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function discoverInjectedFieldsFromClass(ComponentClass: any): FieldMetadata[];
|
|
19
40
|
/**
|
|
20
41
|
* Checks if a component has any injected dependencies
|
|
21
42
|
*
|
|
@@ -17,16 +17,44 @@ import { getTypedMetadata, getOwnTypedMetadata } from '../../utils';
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export function discoverInjectedFields(instance) {
|
|
20
|
+
return discoverInjectedFieldsFromClass(Object.getPrototypeOf(instance)?.constructor);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Discovers all fields with @Inject decorator in a component class
|
|
24
|
+
* Traverses the constructor chain to support inheritance - no instantiation required
|
|
25
|
+
*
|
|
26
|
+
* This is the class-based counterpart of {@link discoverInjectedFields}. It is what
|
|
27
|
+
* slice tests use, since they must inspect a component's dependencies before deciding
|
|
28
|
+
* whether to register it for real or replace it with a mock.
|
|
29
|
+
*
|
|
30
|
+
* @param ComponentClass - Component class to inspect
|
|
31
|
+
* @returns Array of field metadata containing field names, service names, class refs and expressions
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const fields = discoverInjectedFieldsFromClass(AuthService);
|
|
36
|
+
* // Returns: [
|
|
37
|
+
* // { fieldName: 'userService', serviceName: 'UserService', serviceClass: UserService },
|
|
38
|
+
* // { fieldName: 'chat', serviceName: '__Ulak__', expression: (u) => u.namespace('/chat') }
|
|
39
|
+
* // ]
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function discoverInjectedFieldsFromClass(ComponentClass) {
|
|
20
43
|
const fields = [];
|
|
21
44
|
const processedFields = new Set();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
while (
|
|
25
|
-
|
|
45
|
+
let currentClass = ComponentClass;
|
|
46
|
+
// Traverse the constructor chain (similar to how Container does it)
|
|
47
|
+
while (currentClass && currentClass !== Function.prototype) {
|
|
48
|
+
// Stop at native code - matches Container.getPrototypeChain behaviour
|
|
49
|
+
if (typeof currentClass !== 'function' || currentClass.toString().includes('[native code]')) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
26
52
|
// Get dependencies metadata for this level
|
|
27
|
-
const dependencies = getOwnTypedMetadata(ComponentConstants.DependencyKey,
|
|
53
|
+
const dependencies = getOwnTypedMetadata(ComponentConstants.DependencyKey, currentClass);
|
|
54
|
+
// Get injected class references for this level (class-based injections only)
|
|
55
|
+
const dependencyClasses = getOwnTypedMetadata(ComponentConstants.DependencyClassKey, currentClass);
|
|
28
56
|
// Get expressions metadata for this level
|
|
29
|
-
const expressions = getOwnTypedMetadata(ComponentConstants.ExpressionKey,
|
|
57
|
+
const expressions = getOwnTypedMetadata(ComponentConstants.ExpressionKey, currentClass);
|
|
30
58
|
if (dependencies) {
|
|
31
59
|
// Process each dependency field
|
|
32
60
|
for (const [fieldName, serviceName] of Object.entries(dependencies)) {
|
|
@@ -38,12 +66,13 @@ export function discoverInjectedFields(instance) {
|
|
|
38
66
|
fields.push({
|
|
39
67
|
fieldName,
|
|
40
68
|
serviceName,
|
|
69
|
+
serviceClass: dependencyClasses?.[fieldName],
|
|
41
70
|
expression: expressions?.[fieldName],
|
|
42
71
|
});
|
|
43
72
|
}
|
|
44
73
|
}
|
|
45
|
-
// Move up the
|
|
46
|
-
|
|
74
|
+
// Move up the constructor chain
|
|
75
|
+
currentClass = Object.getPrototypeOf(currentClass);
|
|
47
76
|
}
|
|
48
77
|
return fields;
|
|
49
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../../lib/test/metadata/discovery.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAa;IAClD,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,MAAM,eAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAE1C,
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../../lib/test/metadata/discovery.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAa;IAClD,OAAO,+BAA+B,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC,CAAC;AACvF,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,+BAA+B,CAAC,cAAmB;IACjE,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,MAAM,eAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAE1C,IAAI,YAAY,GAAG,cAAc,CAAC;IAElC,oEAAoE;IACpE,OAAO,YAAY,IAAI,YAAY,KAAK,QAAQ,CAAC,SAAS,EAAE,CAAC;QAC3D,sEAAsE;QACtE,IAAI,OAAO,YAAY,KAAK,UAAU,IAAI,YAAY,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;YAC5F,MAAM;QACR,CAAC;QAED,2CAA2C;QAC3C,MAAM,YAAY,GAAG,mBAAmB,CAAe,kBAAkB,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;QAEvG,6EAA6E;QAC7E,MAAM,iBAAiB,GAAG,mBAAmB,CAC3C,kBAAkB,CAAC,kBAAkB,EACrC,YAAY,CACb,CAAC;QAEF,0CAA0C;QAC1C,MAAM,WAAW,GAAG,mBAAmB,CAAc,kBAAkB,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;QAErG,IAAI,YAAY,EAAE,CAAC;YACjB,gCAAgC;YAChC,KAAK,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;gBACpE,oDAAoD;gBACpD,IAAI,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;oBACnC,SAAS;gBACX,CAAC;gBAED,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAE/B,MAAM,CAAC,IAAI,CAAC;oBACV,SAAS;oBACT,WAAW;oBACX,YAAY,EAAE,iBAAiB,EAAE,CAAC,SAAS,CAAC;oBAC5C,UAAU,EAAE,WAAW,EAAE,CAAC,SAAS,CAAC;iBACrC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,gCAAgC;QAChC,YAAY,GAAG,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,cAA2C;IAC3E,MAAM,YAAY,GAAG,gBAAgB,CAAe,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IACtG,OAAO,YAAY,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,cAA2C,EAC3C,SAAiB;IAEjB,MAAM,YAAY,GAAG,gBAAgB,CAAe,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IACtG,OAAO,YAAY,EAAE,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,cAA2C,EAC3C,SAAiB;IAEjB,MAAM,WAAW,GAAG,gBAAgB,CAAc,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IACpG,OAAO,WAAW,EAAE,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
|