@tstdl/base 0.93.139 → 0.93.141
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 +166 -0
- package/ai/genkit/multi-region.plugin.js +5 -3
- package/ai/genkit/tests/multi-region.test.d.ts +1 -0
- package/ai/genkit/tests/multi-region.test.js +5 -2
- package/ai/parser/parser.js +2 -2
- package/ai/prompts/build.js +1 -0
- package/ai/prompts/instructions-formatter.d.ts +15 -2
- package/ai/prompts/instructions-formatter.js +36 -31
- package/ai/prompts/prompt-builder.js +5 -5
- package/ai/prompts/steering.d.ts +3 -2
- package/ai/prompts/steering.js +3 -1
- package/ai/tests/instructions-formatter.test.js +1 -0
- package/api/README.md +403 -0
- package/api/client/client.js +7 -13
- package/api/client/tests/api-client.test.js +10 -10
- package/api/default-error-handlers.js +1 -1
- package/api/response.d.ts +2 -2
- package/api/response.js +22 -33
- package/api/server/api-controller.d.ts +1 -1
- package/api/server/api-controller.js +3 -3
- package/api/server/api-request-token.provider.d.ts +1 -0
- package/api/server/api-request-token.provider.js +1 -0
- package/api/server/middlewares/allowed-methods.middleware.js +2 -1
- package/api/server/middlewares/content-type.middleware.js +2 -1
- package/api/types.d.ts +3 -2
- package/application/README.md +240 -0
- package/application/application.d.ts +1 -1
- package/application/application.js +3 -3
- package/application/providers.d.ts +20 -2
- package/application/providers.js +34 -7
- package/audit/README.md +267 -0
- package/audit/module.d.ts +5 -0
- package/audit/module.js +9 -1
- package/authentication/README.md +288 -0
- package/authentication/client/authentication.service.d.ts +12 -11
- package/authentication/client/authentication.service.js +21 -21
- package/authentication/client/http-client.middleware.js +2 -2
- package/authentication/server/module.d.ts +5 -0
- package/authentication/server/module.js +9 -1
- package/authentication/tests/authentication.api-controller.test.js +1 -1
- package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
- package/authentication/tests/authentication.client-error-handling.test.js +2 -1
- package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
- package/authentication/tests/authentication.client-service.test.js +1 -1
- package/browser/README.md +401 -0
- package/cancellation/README.md +156 -0
- package/cancellation/tests/coverage.test.d.ts +1 -0
- package/cancellation/tests/coverage.test.js +49 -0
- package/cancellation/tests/leak.test.js +24 -29
- package/cancellation/tests/token.test.d.ts +1 -0
- package/cancellation/tests/token.test.js +136 -0
- package/cancellation/token.d.ts +53 -177
- package/cancellation/token.js +132 -208
- package/circuit-breaker/postgres/module.d.ts +1 -0
- package/circuit-breaker/postgres/module.js +5 -1
- package/context/README.md +174 -0
- package/cookie/README.md +161 -0
- package/css/README.md +157 -0
- package/data-structures/README.md +320 -0
- package/decorators/README.md +140 -0
- package/distributed-loop/README.md +231 -0
- package/distributed-loop/distributed-loop.js +1 -1
- package/document-management/README.md +403 -0
- package/document-management/server/configure.js +5 -1
- package/document-management/server/module.d.ts +1 -1
- package/document-management/server/module.js +1 -1
- package/document-management/server/services/document-management-ancillary.service.js +1 -1
- package/document-management/server/services/document-management.service.js +9 -7
- package/document-management/tests/ai-config-hierarchy.test.js +0 -5
- package/document-management/tests/document-management-ai-overrides.test.js +0 -1
- package/document-management/tests/document-management-core.test.js +2 -7
- package/document-management/tests/document-management.api.test.js +6 -7
- package/document-management/tests/document-statistics.service.test.js +11 -12
- package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
- package/document-management/tests/document.service.test.js +3 -3
- package/document-management/tests/enum-helpers.test.js +2 -3
- package/dom/README.md +213 -0
- package/enumerable/README.md +259 -0
- package/enumeration/README.md +121 -0
- package/errors/README.md +267 -0
- package/examples/document-management/main.d.ts +1 -0
- package/examples/document-management/main.js +14 -11
- package/file/README.md +191 -0
- package/formats/README.md +210 -0
- package/function/README.md +144 -0
- package/http/README.md +318 -0
- package/http/client/adapters/undici.adapter.js +1 -1
- package/http/client/http-client-request.d.ts +6 -5
- package/http/client/http-client-request.js +8 -9
- package/http/server/node/node-http-server.js +1 -2
- package/image-service/README.md +137 -0
- package/injector/README.md +491 -0
- package/intl/README.md +113 -0
- package/json-path/README.md +182 -0
- package/jsx/README.md +154 -0
- package/key-value-store/README.md +191 -0
- package/key-value-store/postgres/module.d.ts +1 -0
- package/key-value-store/postgres/module.js +5 -1
- package/lock/README.md +249 -0
- package/lock/postgres/module.d.ts +1 -0
- package/lock/postgres/module.js +5 -1
- package/lock/web/web-lock.js +119 -47
- package/logger/README.md +287 -0
- package/mail/README.md +256 -0
- package/mail/module.d.ts +5 -1
- package/mail/module.js +11 -6
- package/memory/README.md +144 -0
- package/message-bus/README.md +244 -0
- package/message-bus/message-bus-base.js +1 -1
- package/module/README.md +182 -0
- package/module/module.d.ts +1 -1
- package/module/module.js +77 -17
- package/module/modules/web-server.module.js +3 -4
- package/notification/server/module.d.ts +1 -0
- package/notification/server/module.js +5 -1
- package/notification/tests/notification-flow.test.js +2 -2
- package/notification/tests/notification-type.service.test.js +24 -15
- package/object-storage/README.md +300 -0
- package/openid-connect/README.md +274 -0
- package/orm/README.md +423 -0
- package/orm/decorators.d.ts +5 -1
- package/orm/decorators.js +1 -1
- package/orm/server/drizzle/schema-converter.js +17 -30
- package/orm/server/encryption.d.ts +0 -1
- package/orm/server/encryption.js +1 -4
- package/orm/server/index.d.ts +1 -6
- package/orm/server/index.js +1 -6
- package/orm/server/migration.d.ts +19 -0
- package/orm/server/migration.js +72 -0
- package/orm/server/repository.d.ts +1 -1
- package/orm/server/transaction.d.ts +5 -10
- package/orm/server/transaction.js +22 -26
- package/orm/server/transactional.js +3 -3
- package/orm/tests/database-migration.test.d.ts +1 -0
- package/orm/tests/database-migration.test.js +82 -0
- package/orm/tests/encryption.test.js +3 -4
- package/orm/utils.d.ts +17 -2
- package/orm/utils.js +49 -1
- package/package.json +9 -6
- package/password/README.md +164 -0
- package/pdf/README.md +246 -0
- package/polyfills.js +1 -0
- package/pool/README.md +198 -0
- package/process/README.md +237 -0
- package/promise/README.md +252 -0
- package/promise/cancelable-promise.js +1 -1
- package/random/README.md +193 -0
- package/rate-limit/postgres/module.d.ts +1 -0
- package/rate-limit/postgres/module.js +5 -1
- package/reflection/README.md +305 -0
- package/reflection/decorator-data.js +11 -12
- package/rpc/README.md +386 -0
- package/rxjs-utils/README.md +262 -0
- package/schema/README.md +342 -0
- package/serializer/README.md +342 -0
- package/signals/implementation/README.md +134 -0
- package/sse/README.md +278 -0
- package/task-queue/README.md +293 -0
- package/task-queue/postgres/drizzle/{0000_simple_invisible_woman.sql → 0000_wakeful_sunspot.sql} +22 -14
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +160 -82
- package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
- package/task-queue/postgres/module.d.ts +1 -0
- package/task-queue/postgres/module.js +5 -1
- package/task-queue/postgres/schemas.d.ts +9 -6
- package/task-queue/postgres/schemas.js +4 -3
- package/task-queue/postgres/task-queue.d.ts +4 -13
- package/task-queue/postgres/task-queue.js +462 -355
- package/task-queue/postgres/task.model.d.ts +12 -5
- package/task-queue/postgres/task.model.js +51 -25
- package/task-queue/task-context.d.ts +2 -2
- package/task-queue/task-context.js +8 -8
- package/task-queue/task-queue.d.ts +53 -19
- package/task-queue/task-queue.js +121 -55
- package/task-queue/tests/cascading-cancellations.test.d.ts +1 -0
- package/task-queue/tests/cascading-cancellations.test.js +38 -0
- package/task-queue/tests/complex.test.js +45 -229
- package/task-queue/tests/coverage-branch.test.d.ts +1 -0
- package/task-queue/tests/coverage-branch.test.js +407 -0
- package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
- package/task-queue/tests/coverage-enhancement.test.js +144 -0
- package/task-queue/tests/dag-dependencies.test.d.ts +1 -0
- package/task-queue/tests/dag-dependencies.test.js +41 -0
- package/task-queue/tests/dependencies.test.js +28 -26
- package/task-queue/tests/extensive-dependencies.test.js +64 -139
- package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
- package/task-queue/tests/fan-out-spawning.test.js +53 -0
- package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
- package/task-queue/tests/idempotent-replacement.test.js +61 -0
- package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
- package/task-queue/tests/missing-idempotent-tasks.test.js +38 -0
- package/task-queue/tests/queue.test.js +128 -8
- package/task-queue/tests/worker.test.js +39 -16
- package/task-queue/tests/zombie-parent.test.d.ts +1 -0
- package/task-queue/tests/zombie-parent.test.js +45 -0
- package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
- package/task-queue/tests/zombie-recovery.test.js +51 -0
- package/templates/README.md +287 -0
- package/test5.js +5 -5
- package/testing/README.md +157 -0
- package/testing/integration-setup.d.ts +4 -4
- package/testing/integration-setup.js +54 -29
- package/text/README.md +346 -0
- package/text/localization.service.js +2 -2
- package/threading/README.md +238 -0
- package/types/README.md +311 -0
- package/utils/README.md +322 -0
- package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
- package/utils/async-iterable-helpers/observable-iterable.js +4 -8
- package/utils/async-iterable-helpers/take-until.js +4 -4
- package/utils/backoff.js +89 -30
- package/utils/file-reader.js +1 -2
- package/utils/retry-with-backoff.js +1 -1
- package/utils/timer.d.ts +1 -1
- package/utils/timer.js +5 -7
- package/utils/timing.d.ts +1 -1
- package/utils/timing.js +2 -4
- package/utils/z-base32.d.ts +1 -0
- package/utils/z-base32.js +1 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# Browser Module
|
|
2
|
+
|
|
3
|
+
A high-level, controller-based wrapper for Playwright, designed to simplify web automation, scraping, and testing tasks within a dependency-injection-friendly architecture.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
10
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
11
|
+
- [Global Configuration](#global-configuration)
|
|
12
|
+
- [Persistent Contexts](#persistent-contexts)
|
|
13
|
+
- [Element Interaction & Filtering](#element-interaction--filtering)
|
|
14
|
+
- [PDF Rendering](#pdf-rendering)
|
|
15
|
+
- [Logging & Debugging](#logging--debugging)
|
|
16
|
+
- [Smart Scrolling](#smart-scrolling)
|
|
17
|
+
- [Frame & Popup Handling](#frame--popup-handling)
|
|
18
|
+
- [Element Lifecycle & Existence](#element-lifecycle--existence)
|
|
19
|
+
- [📚 API](#-api)
|
|
20
|
+
|
|
21
|
+
## ✨ Features
|
|
22
|
+
|
|
23
|
+
- **Hierarchical Controllers:** Encapsulates Playwright entities (`Browser`, `Page`, `Locator`) into managed controllers (`BrowserController`, `PageController`, `ElementController`) for a clean, object-oriented API.
|
|
24
|
+
- **Dependency Injection Ready:** Designed to be resolved and managed by the `@tstdl/base/injector`, simplifying lifecycle and dependency management.
|
|
25
|
+
- **Unified Element API:** `ElementController` provides a consistent interface over Playwright's `Locator` and `ElementHandle`, supporting chaining and advanced filtering.
|
|
26
|
+
- **Pop-up & Frame Handling:** Seamlessly handle multiple tabs and `<iframe>` elements with dedicated controllers and automatic tracking.
|
|
27
|
+
- **Enhanced Capabilities:** Built-in helpers for PDF generation (buffer and stream), smart scrolling, and automatic logger attachment.
|
|
28
|
+
- **Robust Resource Management:** Implements `AsyncDisposable` for reliable cleanup of browsers and contexts.
|
|
29
|
+
|
|
30
|
+
## Core Concepts
|
|
31
|
+
|
|
32
|
+
The module is built around a hierarchy of controllers. Each controller wraps a specific part of the browser automation lifecycle:
|
|
33
|
+
|
|
34
|
+
1. **`BrowserService`**: The singleton entry point. It manages the creation and lifecycle of `BrowserController` instances.
|
|
35
|
+
2. **`BrowserController`**: Represents a browser process (e.g., Chromium). It creates isolated `BrowserContextController` sessions.
|
|
36
|
+
3. **`BrowserContextController`**: Represents an isolated session (incognito-like) with its own cookies and storage. It creates `PageController` tabs.
|
|
37
|
+
4. **`PageController`**: Represents a single tab. It provides methods for navigation, evaluation, and finding elements.
|
|
38
|
+
5. **`ElementController`**: Represents a DOM element (via Locator or Handle). It provides methods for interaction (click, type, etc.) and inspection.
|
|
39
|
+
|
|
40
|
+
## 🚀 Basic Usage
|
|
41
|
+
|
|
42
|
+
Here is a typical workflow: launching a browser, navigating to a page, interacting with elements, and cleaning up.
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { BrowserService } from '@tstdl/base/browser';
|
|
46
|
+
import { inject } from '@tstdl/base/injector';
|
|
47
|
+
|
|
48
|
+
// Typically resolved via dependency injection in your application
|
|
49
|
+
const browserService = inject(BrowserService);
|
|
50
|
+
|
|
51
|
+
// Launch a new browser instance (Chromium by default)
|
|
52
|
+
const browser = await browserService.newBrowser({ headless: false });
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Create a new isolated context and a page
|
|
56
|
+
const context = await browser.newContext();
|
|
57
|
+
const page = await context.newPage();
|
|
58
|
+
|
|
59
|
+
// Navigate to a website
|
|
60
|
+
await page.navigate('https://playwright.dev/');
|
|
61
|
+
|
|
62
|
+
// Locate an element using accessibility roles
|
|
63
|
+
const getStartedLink = page.getByRole('link', { name: 'Get started' });
|
|
64
|
+
|
|
65
|
+
// Interact with the element
|
|
66
|
+
await getStartedLink.click();
|
|
67
|
+
|
|
68
|
+
// Wait for navigation or URL change
|
|
69
|
+
await page.waitForUrl('**/docs/intro');
|
|
70
|
+
console.log('Current URL:', page.url());
|
|
71
|
+
|
|
72
|
+
// Find elements by text
|
|
73
|
+
const title = page.getByText('Installation');
|
|
74
|
+
|
|
75
|
+
if (await title.isVisible()) {
|
|
76
|
+
console.log('Installation guide loaded.');
|
|
77
|
+
}
|
|
78
|
+
} finally {
|
|
79
|
+
// Closes the browser and all associated contexts/pages
|
|
80
|
+
await browser.close();
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 🔧 Advanced Topics
|
|
85
|
+
|
|
86
|
+
### Global Configuration
|
|
87
|
+
|
|
88
|
+
You can configure default options for all browsers launched by the `BrowserService`. This is typically done during application bootstrap.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { configureBrowser } from '@tstdl/base/browser';
|
|
92
|
+
import { chromium } from 'playwright';
|
|
93
|
+
|
|
94
|
+
configureBrowser({
|
|
95
|
+
// Optionally provide specific browser binaries
|
|
96
|
+
browsers: { chromium },
|
|
97
|
+
options: {
|
|
98
|
+
defaultNewBrowserOptions: {
|
|
99
|
+
browser: 'chromium',
|
|
100
|
+
headless: 'new',
|
|
101
|
+
windowSize: { width: 1920, height: 1080 },
|
|
102
|
+
defaultNewContextOptions: {
|
|
103
|
+
locale: 'en-US',
|
|
104
|
+
colorScheme: 'dark',
|
|
105
|
+
viewport: { width: 1920, height: 1080 },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Persistent Contexts
|
|
113
|
+
|
|
114
|
+
Persistent contexts store session data (cookies, local storage) on disk, allowing you to maintain login states across restarts.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { BrowserService } from '@tstdl/base/browser';
|
|
118
|
+
import { inject } from '@tstdl/base/injector';
|
|
119
|
+
|
|
120
|
+
const browserService = inject(BrowserService);
|
|
121
|
+
|
|
122
|
+
// Data will be saved to './user-data'
|
|
123
|
+
const context = await browserService.newPersistentContext('./user-data', {
|
|
124
|
+
headless: false,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const page = await context.newPage();
|
|
128
|
+
await page.navigate('https://github.com/login');
|
|
129
|
+
|
|
130
|
+
// ... perform login manually or automatically ...
|
|
131
|
+
|
|
132
|
+
// Close the context to save state
|
|
133
|
+
await context.close();
|
|
134
|
+
|
|
135
|
+
// Next time you run this, the session will be restored
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Element Interaction & Filtering
|
|
139
|
+
|
|
140
|
+
The `ElementController` supports advanced filtering and chaining to precisely target elements.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import type { PageController } from '@tstdl/base/browser';
|
|
144
|
+
|
|
145
|
+
async function selectProduct(page: PageController, productName: string) {
|
|
146
|
+
// Find a list item that contains the specific product name
|
|
147
|
+
const productCard = page.getByRole('listitem').filter({ hasText: productName });
|
|
148
|
+
|
|
149
|
+
// Within that card, find the "Add to Cart" button
|
|
150
|
+
const addButton = productCard.locate(page.getByRole('button', { name: 'Add to Cart' }));
|
|
151
|
+
|
|
152
|
+
if (await addButton.isEnabled()) {
|
|
153
|
+
await addButton.click();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### PDF Rendering
|
|
159
|
+
|
|
160
|
+
`PageController` includes a convenient method to render the current page as a PDF.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { writeFile } from 'node:fs/promises';
|
|
164
|
+
import type { PageController } from '@tstdl/base/browser';
|
|
165
|
+
|
|
166
|
+
async function saveAsPdf(page: PageController) {
|
|
167
|
+
await page.navigate('https://example.com/report');
|
|
168
|
+
|
|
169
|
+
const pdfBuffer = await page.renderPdf({
|
|
170
|
+
format: 'A4',
|
|
171
|
+
printBackground: true,
|
|
172
|
+
margin: { top: '1cm', bottom: '1cm', left: '1cm', right: '1cm' },
|
|
173
|
+
displayHeaderFooter: true,
|
|
174
|
+
footerTemplate: '<div style="font-size: 10px; text-align: center; width: 100%;">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await writeFile('report.pdf', pdfBuffer);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Logging & Debugging
|
|
182
|
+
|
|
183
|
+
Attach a logger to a context or page to automatically capture console messages, page errors, and network requests.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import type { BrowserContextController } from '@tstdl/base/browser';
|
|
187
|
+
import { Logger } from '@tstdl/base/logger';
|
|
188
|
+
import { inject } from '@tstdl/base/injector';
|
|
189
|
+
|
|
190
|
+
const logger = inject(Logger);
|
|
191
|
+
|
|
192
|
+
function debugSession(context: BrowserContextController) {
|
|
193
|
+
// All pages created in this context will now log to the provided logger
|
|
194
|
+
context.attachLogger(logger);
|
|
195
|
+
|
|
196
|
+
// Console logs from the browser will appear in your node logs
|
|
197
|
+
// Network requests will be logged at verbose level
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Smart Scrolling
|
|
202
|
+
|
|
203
|
+
The `scrollTo` method intelligently scrolls the viewport to a specific element or coordinate, handling complex scrolling containers.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import type { PageController } from '@tstdl/base/browser';
|
|
207
|
+
|
|
208
|
+
async function scrollToFooter(page: PageController) {
|
|
209
|
+
const footer = page.getByRole('contentinfo');
|
|
210
|
+
|
|
211
|
+
// Smoothly scrolls until the element is in view
|
|
212
|
+
await page.scrollTo(footer);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Frame & Popup Handling
|
|
217
|
+
|
|
218
|
+
Interacting with `<iframe>` elements or popups is straightforward using dedicated controllers.
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import type { PageController } from '@tstdl/base/browser';
|
|
222
|
+
|
|
223
|
+
async function handleIframe(page: PageController) {
|
|
224
|
+
// Access an iframe by name, url, or index
|
|
225
|
+
const newsletterFrame = page.frame({ url: /.*newsletter.*/ });
|
|
226
|
+
|
|
227
|
+
// Use the same ElementController API inside the frame
|
|
228
|
+
await newsletterFrame.getByPlaceholder('Email').fill('user@example.com');
|
|
229
|
+
await newsletterFrame.getByRole('button', { name: 'Subscribe' }).click();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function handlePopups(page: PageController) {
|
|
233
|
+
// Trigger an action that opens a popup
|
|
234
|
+
await page.getByRole('link', { name: 'Help' }).click();
|
|
235
|
+
|
|
236
|
+
// Find the newly opened page
|
|
237
|
+
const [helpPage] = await page.opened();
|
|
238
|
+
|
|
239
|
+
if (helpPage) {
|
|
240
|
+
console.log('Opened help page:', helpPage.url());
|
|
241
|
+
await helpPage.close();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Element Lifecycle & Existence
|
|
247
|
+
|
|
248
|
+
`ElementController` provides helpful methods to check for elements without throwing errors on timeout immediately.
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import type { PageController } from '@tstdl/base/browser';
|
|
252
|
+
|
|
253
|
+
async function checkStatus(page: PageController) {
|
|
254
|
+
const successMessage = page.getByText('Success!');
|
|
255
|
+
|
|
256
|
+
// Check if it exists and is visible within a short timeout
|
|
257
|
+
if (await successMessage.exists({ timeout: 2000 })) {
|
|
258
|
+
console.log('Operation completed successfully.');
|
|
259
|
+
} else {
|
|
260
|
+
console.warn('Success message did not appear.');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## 📚 API
|
|
266
|
+
|
|
267
|
+
### `BrowserService`
|
|
268
|
+
|
|
269
|
+
Singleton service for managing browser instances.
|
|
270
|
+
|
|
271
|
+
| Method | Description |
|
|
272
|
+
| :------------------------------------ | :-------------------------------------------------------------------- |
|
|
273
|
+
| `newBrowser(options?)` | Launches a new `BrowserController`. |
|
|
274
|
+
| `newPersistentContext(dir, options?)` | Launches a browser with a persistent context for saving session data. |
|
|
275
|
+
| `waitForNoBrowsers()` | Waits until all managed browsers are closed. |
|
|
276
|
+
| `dispose()` | Closes all browsers and contexts created by this service. |
|
|
277
|
+
|
|
278
|
+
### `BrowserController`
|
|
279
|
+
|
|
280
|
+
Controls a single browser process.
|
|
281
|
+
|
|
282
|
+
| Method | Description |
|
|
283
|
+
| :--------------------- | :-------------------------------------------------- |
|
|
284
|
+
| `newContext(options?)` | Creates a new, isolated `BrowserContextController`. |
|
|
285
|
+
| `waitForNoContexts()` | Waits until all contexts are closed. |
|
|
286
|
+
| `close()` | Closes the browser and all its contexts. |
|
|
287
|
+
| `waitForClose()` | Waits for the browser to close. |
|
|
288
|
+
|
|
289
|
+
### `BrowserContextController`
|
|
290
|
+
|
|
291
|
+
Controls an isolated browser session.
|
|
292
|
+
|
|
293
|
+
| Method | Description |
|
|
294
|
+
| :----------------------------- | :------------------------------------------------------- |
|
|
295
|
+
| `newPage(options?)` | Creates a new `PageController` (tab). |
|
|
296
|
+
| `pages()` | Returns all open pages in this context. |
|
|
297
|
+
| `getControllerByPage(page)` | Wraps a native Playwright `Page` in a `PageController`. |
|
|
298
|
+
| `getState()` | Returns the storage state (cookies, localStorage). |
|
|
299
|
+
| `setExtraHttpHeaders(headers)` | Sets headers for all requests in this context. |
|
|
300
|
+
| `attachLogger(logger)` | Attaches a logger to monitor console and network events. |
|
|
301
|
+
| `waitForNoPages()` | Waits until all pages are closed. |
|
|
302
|
+
| `close()` | Closes the context and all pages. |
|
|
303
|
+
|
|
304
|
+
### `PageController`
|
|
305
|
+
|
|
306
|
+
Controls a single tab. Extends `DocumentController`.
|
|
307
|
+
|
|
308
|
+
| Method | Description |
|
|
309
|
+
| :----------------------------- | :----------------------------------------------------- |
|
|
310
|
+
| `url()` | Returns the current URL. |
|
|
311
|
+
| `opened()` | Returns pages opened by this page (e.g., popups). |
|
|
312
|
+
| `opener()` | Returns the page that opened this one. |
|
|
313
|
+
| `navigate(url, options?)` | Navigates to a URL. |
|
|
314
|
+
| `waitForUrl(url, options?)` | Waits for the URL to match a predicate or string. |
|
|
315
|
+
| `waitForLoadState(state?)` | Waits for the page to reach a specific load state. |
|
|
316
|
+
| `setContent(html, options?)` | Sets the page content. |
|
|
317
|
+
| `renderPdf(options?)` | Renders the page as a PDF `Uint8Array`. |
|
|
318
|
+
| `renderPdfStream(options?)` | Renders the page as a PDF `ReadableStream`. |
|
|
319
|
+
| `scroll(deltaX, deltaY)` | Scrolls using the mouse wheel. |
|
|
320
|
+
| `scrollTo(target)` | Scrolls to a coordinate or `ElementController`. |
|
|
321
|
+
| `frame(selector)` | Gets a `FrameController` for an iframe. |
|
|
322
|
+
| `close()` | Closes the page. |
|
|
323
|
+
| `attachLogger(logger)` | Attaches a logger to this specific page. |
|
|
324
|
+
|
|
325
|
+
### `FrameController`
|
|
326
|
+
|
|
327
|
+
Controls a single iframe. Extends `DocumentController`.
|
|
328
|
+
|
|
329
|
+
| Method | Description |
|
|
330
|
+
| :------------------ | :---------------------------------- |
|
|
331
|
+
| `getPage()` | Returns the page containing this frame. |
|
|
332
|
+
| `getFrame(selector)`| Gets a child frame. |
|
|
333
|
+
|
|
334
|
+
### `DocumentController`
|
|
335
|
+
|
|
336
|
+
Base class for `PageController` and `FrameController`. Extends `LocatorController`.
|
|
337
|
+
|
|
338
|
+
| Method | Description |
|
|
339
|
+
| :---------------------------- | :--------------------------------------------------- |
|
|
340
|
+
| `evaluate(fn, arg?)` | Executes JavaScript in the context of the document. |
|
|
341
|
+
| `frames()` | Returns all child frames. |
|
|
342
|
+
| `getControllerByFrame(frame)` | Wraps a native Playwright `Frame` in a controller. |
|
|
343
|
+
| `waitForElement(selector)` | Waits for an element to appear in the DOM. |
|
|
344
|
+
| `locateInFrame(selector)` | Returns a `LocatorController` for an iframe. |
|
|
345
|
+
|
|
346
|
+
### `ElementController`
|
|
347
|
+
|
|
348
|
+
Wraps a Playwright `Locator` or `ElementHandle`. Extends `LocatorController`.
|
|
349
|
+
|
|
350
|
+
| Method | Description |
|
|
351
|
+
| :------------------------------ | :---------------------------------------------------------- |
|
|
352
|
+
| `exists(options?)` | Checks if the element exists and is visible (configurable). |
|
|
353
|
+
| `count()` | Returns the number of matching elements. |
|
|
354
|
+
| `isVisible()` | Checks if the element is visible. |
|
|
355
|
+
| `isHidden()` | Checks if the element is hidden. |
|
|
356
|
+
| `isEnabled()` | Checks if the element is enabled. |
|
|
357
|
+
| `isDisabled()` | Checks if the element is disabled. |
|
|
358
|
+
| `isChecked()` | Checks if the element is checked (radio/checkbox). |
|
|
359
|
+
| `isEditable()` | Checks if the element is editable. |
|
|
360
|
+
| `click(options?)` | Clicks the element. |
|
|
361
|
+
| `dblclick(options?)` | Double-clicks the element. |
|
|
362
|
+
| `fill(text, options?)` | Fills an input. |
|
|
363
|
+
| `clear(options?)` | Clears an input. |
|
|
364
|
+
| `type(text, options?)` | Types text character by character. |
|
|
365
|
+
| `press(key, options?)` | Simulates a key press. |
|
|
366
|
+
| `check(options?)` | Checks a checkbox or radio button. |
|
|
367
|
+
| `uncheck(options?)` | Unchecks a checkbox or radio button. |
|
|
368
|
+
| `setChecked(checked, options?)` | Sets the checked state. |
|
|
369
|
+
| `selectOption(values, options?)`| Selects one or multiple options in a `<select>`. |
|
|
370
|
+
| `setInputFiles(files, options?)`| Sets files for a file input. |
|
|
371
|
+
| `hover(options?)` | Hovers over the element. |
|
|
372
|
+
| `focus(options?)` | Focuses the element. |
|
|
373
|
+
| `tap(options?)` | Taps the element (touch). |
|
|
374
|
+
| `selectText(options?)` | Selects the text in the element. |
|
|
375
|
+
| `inputValue()` | Gets the current value of an input. |
|
|
376
|
+
| `evaluate(fn, arg?)` | Executes JavaScript in the context of the element. |
|
|
377
|
+
| `boundingBox()` | Returns the element's bounding box (x, y, width, height). |
|
|
378
|
+
| `scrollIntoViewIfNeeded()` | Scrolls the element into view if it's not visible. |
|
|
379
|
+
| `filter(filter)` | Narrow down the selection based on text or descendants. |
|
|
380
|
+
| `locate(selector)` | Finds descendant elements. |
|
|
381
|
+
| `and(element)` | Combines two locators (intersection). |
|
|
382
|
+
| `or(element)` | Combines two locators (union). |
|
|
383
|
+
| `first()` | Returns the first matching element. |
|
|
384
|
+
| `last()` | Returns the last matching element. |
|
|
385
|
+
| `nth(index)` | Returns the nth matching element. |
|
|
386
|
+
| `waitFor(state?)` | Waits for a specific state (visible, attached, etc.). |
|
|
387
|
+
| `getAll()` | Returns an array of controllers for all matching elements. |
|
|
388
|
+
|
|
389
|
+
### `LocatorController`
|
|
390
|
+
|
|
391
|
+
Base class for searching elements.
|
|
392
|
+
|
|
393
|
+
| Method | Description |
|
|
394
|
+
| :--------------------- | :--------------------------------------------- |
|
|
395
|
+
| `getBySelector(sel)` | Finds element(s) by CSS selector. |
|
|
396
|
+
| `getByRole(role)` | Finds element(s) by ARIA role. |
|
|
397
|
+
| `getByLabel(text)` | Finds element(s) by associated label text. |
|
|
398
|
+
| `getByPlaceholder(tx)` | Finds element(s) by placeholder text. |
|
|
399
|
+
| `getByText(text)` | Finds element(s) by text content. |
|
|
400
|
+
| `getByAltText(text)` | Finds element(s) by alt attribute. |
|
|
401
|
+
| `getByTitle(text)` | Finds element(s) by title attribute. |
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Cancellation (`@tstdl/base/cancellation`)
|
|
2
|
+
|
|
3
|
+
A powerful and flexible cancellation signaling module for TypeScript, built on the native `AbortSignal` API. It provides a robust mechanism for managing cancellation in asynchronous operations, offering seamless integration with Promises, Observables, and the native `AbortController`.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- **Native Integration:** Built on top of the native `AbortSignal` for maximum compatibility with modern web and Node.js APIs.
|
|
8
|
+
- **One-way Lifecycle:** Signals are one-way (once set, they stay set), simplifying state management and reasoning.
|
|
9
|
+
- **Hierarchical:** Create child signals that inherit cancellation state from parents.
|
|
10
|
+
- **Promise-friendly:** `await` a signal to pause execution until it is cancelled.
|
|
11
|
+
- **Resource Management:** Automatic cleanup via the `Disposable` pattern (`Symbol.dispose`).
|
|
12
|
+
- **Explicit Read/Write Separation:** Use `CancellationToken` to control state and `CancellationSignal` for a read-only view.
|
|
13
|
+
|
|
14
|
+
## Core Concepts
|
|
15
|
+
|
|
16
|
+
The module revolves around two main classes:
|
|
17
|
+
|
|
18
|
+
1. **`CancellationToken`**: A mutable cancellation source. It provides a `.set()` method to trigger cancellation.
|
|
19
|
+
2. **`CancellationSignal`**: A read-only view of a cancellation state. It allows checking if cancellation has occurred and waiting for it.
|
|
20
|
+
|
|
21
|
+
A `CancellationToken` _is a_ `CancellationSignal`, but it has the additional ability to trigger the cancellation.
|
|
22
|
+
|
|
23
|
+
The state of a signal is:
|
|
24
|
+
|
|
25
|
+
- **Unset** (`isUnset` is true): The default state; the operation should continue.
|
|
26
|
+
- **Set** (`isSet` is true): The cancelled state; the operation should stop.
|
|
27
|
+
|
|
28
|
+
Once a signal is **Set**, it cannot be **Unset**.
|
|
29
|
+
|
|
30
|
+
## 🚀 Basic Usage
|
|
31
|
+
|
|
32
|
+
Create a `CancellationToken` to manage the lifecycle of an operation.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { CancellationToken } from '@tstdl/base/cancellation';
|
|
36
|
+
|
|
37
|
+
const token = new CancellationToken();
|
|
38
|
+
|
|
39
|
+
console.log(token.isSet); // false
|
|
40
|
+
console.log(token.isUnset); // true
|
|
41
|
+
|
|
42
|
+
// Pass the read-only signal to a consumer
|
|
43
|
+
doWork(token.signal);
|
|
44
|
+
|
|
45
|
+
// Some time later...
|
|
46
|
+
token.set('User requested cancellation'); // Set the token to a cancelled state
|
|
47
|
+
|
|
48
|
+
console.log(token.isSet); // true
|
|
49
|
+
console.log(token.reason); // Error: Operation cancelled (cause: User requested cancellation)
|
|
50
|
+
|
|
51
|
+
function doWork(signal: CancellationSignal) {
|
|
52
|
+
if (signal.isSet) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// ... perform work
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 🔧 Advanced Topics
|
|
60
|
+
|
|
61
|
+
### Promise Integration
|
|
62
|
+
|
|
63
|
+
You can `await` a signal using the `.wait()` method. It returns a promise that resolves when the signal is set. By default, it throws the cancellation reason as an error.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
async function longRunningTask(signal: CancellationSignal) {
|
|
67
|
+
console.log('Task started.');
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// Wait for the signal to be set (will throw)
|
|
71
|
+
await signal.wait();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.log('Task was cancelled:', error.message);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Hierarchical Cancellation (Inheritance)
|
|
80
|
+
|
|
81
|
+
Create a hierarchy of signals. When a parent signal is set, all its children are also set.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const parent = new CancellationToken();
|
|
85
|
+
|
|
86
|
+
// child1 inherits from parent
|
|
87
|
+
const child1 = parent.fork();
|
|
88
|
+
|
|
89
|
+
// child2 inherits from parent and another source
|
|
90
|
+
const child2 = new CancellationToken([parent, otherSignal]);
|
|
91
|
+
|
|
92
|
+
parent.set();
|
|
93
|
+
console.log(child1.isSet); // true
|
|
94
|
+
console.log(child2.isSet); // true
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Native `AbortSignal` Interoperability
|
|
98
|
+
|
|
99
|
+
`CancellationSignal` wraps a native `AbortSignal` and can be created from any `AbortSignal` or `AbortController`.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const signal = CancellationSignal.from(controller.signal);
|
|
104
|
+
|
|
105
|
+
// Use the wrapped signal with fetch
|
|
106
|
+
const response = await fetch(url, { signal: signal.abortSignal });
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Timeouts
|
|
110
|
+
|
|
111
|
+
Easily create signals that automatically trigger after a timeout.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const signal = parentSignal.withTimeout(5000); // Set after 5 seconds or if parent is set
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Cleanup and Disposal
|
|
118
|
+
|
|
119
|
+
`CancellationSignal` and `CancellationToken` implement the `Disposable` interface. Disposing of a signal will abort its internal controller, effectively setting it.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
using token = new CancellationToken();
|
|
123
|
+
// token will be disposed (and thus set) when the scope is exited
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 📚 API Reference
|
|
127
|
+
|
|
128
|
+
### `CancellationSignal` (Read-only)
|
|
129
|
+
|
|
130
|
+
| Member | Type | Description |
|
|
131
|
+
| :----------------- | :------------------- | :------------------------------------------------------------------------ |
|
|
132
|
+
| `isSet` | `boolean` | Whether the signal is set (cancelled). |
|
|
133
|
+
| `isUnset` | `boolean` | Whether the signal is not yet set. |
|
|
134
|
+
| `reason` | `any` | The reason for cancellation (usually an `Error`). |
|
|
135
|
+
| `abortSignal` | `AbortSignal` | The underlying native `AbortSignal`. |
|
|
136
|
+
| `wait(options?)` | `Promise<void>` | Returns a promise that resolves when the signal is set. |
|
|
137
|
+
| `throwIfSet()` | `void` | Throws the `reason` if the signal is set. |
|
|
138
|
+
| `fork()` | `CancellationToken` | Creates a new `CancellationToken` that inherits from this signal. |
|
|
139
|
+
| `inherit(sources)` | `CancellationSignal` | Creates a new signal that is set when this or any of the sources are set. |
|
|
140
|
+
| `withTimeout(ms)` | `CancellationSignal` | Creates a new signal that is set after a timeout. |
|
|
141
|
+
| `dispose()` | `void` | Manually disposes of the signal (sets it). |
|
|
142
|
+
|
|
143
|
+
### `CancellationToken` (Writable)
|
|
144
|
+
|
|
145
|
+
Extends `CancellationSignal`.
|
|
146
|
+
|
|
147
|
+
| Member | Type | Description |
|
|
148
|
+
| :---------------------- | :------------------- | :-------------------------------------------------------------- |
|
|
149
|
+
| `constructor(parents?)` | - | Creates a new token, optionally inheriting from parent sources. |
|
|
150
|
+
| `set(reason?)` | `void` | Triggers cancellation with an optional reason. |
|
|
151
|
+
| `signal` | `CancellationSignal` | Returns a read-only view of this token. |
|
|
152
|
+
|
|
153
|
+
### `CancellationSource` (Type)
|
|
154
|
+
|
|
155
|
+
A union type representing any object that can be used as a source for cancellation:
|
|
156
|
+
`CancellationToken | CancellationSignal | AbortController | AbortSignal`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '../../polyfills.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import '../../polyfills.js';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import * as core from '../../core.js';
|
|
4
|
+
import { CancellationToken, sourcesToAbortSignal } from '../token.js';
|
|
5
|
+
describe('Cancellation Coverage Extra', () => {
|
|
6
|
+
it('should hit dev mode branch (true)', () => {
|
|
7
|
+
vi.spyOn(core, 'isDevMode').mockReturnValue(true);
|
|
8
|
+
const token = new CancellationToken();
|
|
9
|
+
expect(token.isSet).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
it('should hit dev mode branch (false)', () => {
|
|
12
|
+
vi.spyOn(core, 'isDevMode').mockReturnValue(false);
|
|
13
|
+
const token = new CancellationToken();
|
|
14
|
+
expect(token.isSet).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
it('should hit throw: true branch in wait', async () => {
|
|
17
|
+
const token = new CancellationToken();
|
|
18
|
+
token.set('reason');
|
|
19
|
+
await expect(token.wait({ throw: true })).rejects.toThrow();
|
|
20
|
+
});
|
|
21
|
+
it('should hit waitThrow', async () => {
|
|
22
|
+
const token = new CancellationToken();
|
|
23
|
+
token.set('reason');
|
|
24
|
+
await expect(token.waitThrow()).rejects.toThrow();
|
|
25
|
+
});
|
|
26
|
+
it('should hit signals.length == 1 branch', () => {
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const signal = sourcesToAbortSignal(controller.signal);
|
|
29
|
+
expect(signal).toBe(controller.signal);
|
|
30
|
+
});
|
|
31
|
+
it('should hit signals.length > 1 branch', () => {
|
|
32
|
+
const controller1 = new AbortController();
|
|
33
|
+
const controller2 = new AbortController();
|
|
34
|
+
const signal = sourcesToAbortSignal([controller1.signal, controller2.signal]);
|
|
35
|
+
expect(signal).not.toBe(controller1.signal);
|
|
36
|
+
expect(signal).not.toBe(controller2.signal);
|
|
37
|
+
});
|
|
38
|
+
it('should hit sourcesToAbortSignal error branch', () => {
|
|
39
|
+
expect(() => sourcesToAbortSignal([])).toThrow('At least one cancellation source must be provided');
|
|
40
|
+
});
|
|
41
|
+
it('should hit unsubscribe branch in subscribe', () => {
|
|
42
|
+
const token = new CancellationToken();
|
|
43
|
+
const next = vi.fn();
|
|
44
|
+
const subscription = token.subscribe({ next });
|
|
45
|
+
subscription.unsubscribe();
|
|
46
|
+
token.set();
|
|
47
|
+
expect(next).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -1,40 +1,35 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { CancellationToken } from '../token.js';
|
|
2
|
+
import { CancellationSignal, CancellationToken } from '../token.js';
|
|
3
3
|
describe('CancellationToken Memory Leak', () => {
|
|
4
|
-
it('should not leak
|
|
4
|
+
it('should not leak when using inherit and disposing', () => {
|
|
5
5
|
const parent = new CancellationToken();
|
|
6
|
-
// @ts-ignore
|
|
7
|
-
const initialParentSubscribers = parent._stateSubject.observers.length;
|
|
8
6
|
for (let i = 0; i < 1000; i++) {
|
|
9
|
-
const child = new CancellationToken();
|
|
10
|
-
|
|
11
|
-
child.set();
|
|
7
|
+
const child = parent.inherit(new CancellationToken());
|
|
8
|
+
child.dispose();
|
|
12
9
|
}
|
|
13
|
-
|
|
14
|
-
expect(parent._stateSubject.observers.length).toBeLessThan(initialParentSubscribers + 10);
|
|
10
|
+
expect(parent.isSet).toBe(false);
|
|
15
11
|
});
|
|
16
|
-
it('should
|
|
12
|
+
it('should follow parent signal', () => {
|
|
17
13
|
const parent = new CancellationToken();
|
|
18
|
-
const child = new CancellationToken();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
expect(
|
|
22
|
-
child.complete();
|
|
23
|
-
// @ts-ignore
|
|
24
|
-
expect(parent._stateSubject.observers.length).toBe(0);
|
|
14
|
+
const child = new CancellationToken(parent);
|
|
15
|
+
expect(child.isSet).toBe(false);
|
|
16
|
+
parent.set();
|
|
17
|
+
expect(child.isSet).toBe(true);
|
|
25
18
|
});
|
|
26
|
-
it('should not
|
|
19
|
+
it('should not affect parent when child is set', () => {
|
|
27
20
|
const parent = new CancellationToken();
|
|
28
|
-
const child = new CancellationToken();
|
|
29
|
-
parent.
|
|
30
|
-
|
|
31
|
-
expect(parent.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
const child = new CancellationToken(parent);
|
|
22
|
+
expect(parent.isSet).toBe(false);
|
|
23
|
+
child.set();
|
|
24
|
+
expect(parent.isSet).toBe(false);
|
|
25
|
+
expect(child.isSet).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
it('should clean up when disposed', () => {
|
|
28
|
+
const parent = new CancellationToken();
|
|
29
|
+
const signal = CancellationSignal.from(parent);
|
|
30
|
+
expect(signal.isSet).toBe(false);
|
|
31
|
+
signal.dispose();
|
|
32
|
+
expect(signal.isSet).toBe(true);
|
|
33
|
+
expect(parent.isSet).toBe(false);
|
|
39
34
|
});
|
|
40
35
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '../../polyfills.js';
|