twd-js 0.4.0 → 0.5.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 +331 -51
- package/dist/client-CUE3jVb4.mjs +25226 -0
- package/dist/index.d.ts +1 -1
- package/dist/initializers/initSidebar.d.ts +17 -0
- package/dist/initializers/viteLoadTests.d.ts +32 -0
- package/dist/mock-sw.js +1 -1
- package/dist/twd.es.js +3022 -2972
- package/dist/twd.umd.js +332 -83
- package/dist/ui/ClosedSidebar.d.ts +2 -1
- package/dist/ui/TWDSidebar.d.ts +12 -1
- package/package.json +26 -15
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# TWD
|
|
2
2
|
|
|
3
3
|
[](https://github.com/BRIKEV/twd/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/twd-js)
|
|
@@ -12,7 +12,27 @@ TWD (Testing Web Development) is a library designed to seamlessly integrate test
|
|
|
12
12
|
|
|
13
13
|
Currently, TWD supports React, with plans to add more frameworks soon.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Table of Contents
|
|
16
|
+
|
|
17
|
+
- [Features](#features)
|
|
18
|
+
- [Installation](#installation)
|
|
19
|
+
- [Quick Start](#quick-start)
|
|
20
|
+
- [Writing Tests](#writing-tests)
|
|
21
|
+
- [Test Structure](#test-structure)
|
|
22
|
+
- [Element Selection](#element-selection)
|
|
23
|
+
- [Assertions](#assertions)
|
|
24
|
+
- [User Interactions](#user-interactions)
|
|
25
|
+
- [API Mocking](#api-mocking)
|
|
26
|
+
- [Setup](#setup)
|
|
27
|
+
- [Mock Requests](#mock-requests)
|
|
28
|
+
- [Wait for Requests](#wait-for-requests)
|
|
29
|
+
- [API Reference](#api-reference)
|
|
30
|
+
- [Test Functions](#test-functions)
|
|
31
|
+
- [TWD Commands](#twd-commands)
|
|
32
|
+
- [Assertions](#assertions-1)
|
|
33
|
+
- [Examples](#examples)
|
|
34
|
+
- [Contributing](#contributing)
|
|
35
|
+
- [License](#license)
|
|
16
36
|
|
|
17
37
|
## Features
|
|
18
38
|
|
|
@@ -47,16 +67,39 @@ pnpm add twd-js
|
|
|
47
67
|
import { createRoot } from "react-dom/client";
|
|
48
68
|
import App from "./App";
|
|
49
69
|
import "./index.css";
|
|
50
|
-
|
|
70
|
+
|
|
71
|
+
// Only load the test sidebar and tests in development mode
|
|
72
|
+
if (import.meta.env.DEV) {
|
|
73
|
+
// Use Vite's glob import to find all test files
|
|
74
|
+
const testModules = import.meta.glob("./**/*.twd.test.ts");
|
|
75
|
+
const { initViteLoadTests, twd } = await import('twd-js');
|
|
76
|
+
// Initialize the TWD sidebar and load tests
|
|
77
|
+
initViteLoadTests(testModules, { open: true, position: 'left' });
|
|
78
|
+
// Optionally initialize request mocking
|
|
79
|
+
twd.initRequestMocking()
|
|
80
|
+
.then(() => {
|
|
81
|
+
console.log("Request mocking initialized");
|
|
82
|
+
})
|
|
83
|
+
.catch((err) => {
|
|
84
|
+
console.error("Error initializing request mocking:", err);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
51
87
|
|
|
52
88
|
createRoot(document.getElementById("root")!).render(
|
|
53
89
|
<StrictMode>
|
|
54
90
|
<App />
|
|
55
|
-
<TWDSidebar />
|
|
91
|
+
<TWDSidebar open={false} />
|
|
56
92
|
</StrictMode>
|
|
57
93
|
);
|
|
58
94
|
```
|
|
59
95
|
|
|
96
|
+
**TWDSidebar Props**
|
|
97
|
+
|
|
98
|
+
| Prop | Type | Default | Description |
|
|
99
|
+
|-----------|---------------------|---------|---------------------------------------------|
|
|
100
|
+
| open | boolean | true | Whether the sidebar is open by default |
|
|
101
|
+
| position | "left" \| "right" | "left" | Sidebar position (left or right side)
|
|
102
|
+
|
|
60
103
|
2. **Write your tests:**
|
|
61
104
|
|
|
62
105
|
Create files ending with `.twd.test.ts` (or any extension you prefer):
|
|
@@ -82,47 +125,159 @@ pnpm add twd-js
|
|
|
82
125
|
|
|
83
126
|
3. **Auto-load your tests:**
|
|
84
127
|
|
|
85
|
-
|
|
128
|
+
|
|
129
|
+
- With Vite and the new TWD loader:
|
|
86
130
|
|
|
87
131
|
```ts
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
import
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
100
|
-
|
|
132
|
+
// src/main.tsx (or your main entry file)
|
|
133
|
+
import { StrictMode } from 'react';
|
|
134
|
+
import { createRoot } from 'react-dom/client';
|
|
135
|
+
import './index.css';
|
|
136
|
+
import router from './routes.ts';
|
|
137
|
+
import { RouterProvider } from 'react-router';
|
|
138
|
+
|
|
139
|
+
// Only load the test sidebar and tests in development mode
|
|
140
|
+
if (import.meta.env.DEV) {
|
|
141
|
+
// Use Vite's glob import to find all test files
|
|
142
|
+
const testModules = import.meta.glob("./**/*.twd.test.ts");
|
|
143
|
+
const { initViteLoadTests, twd } = await import('twd-js');
|
|
144
|
+
// Initialize the TWD sidebar and load tests
|
|
145
|
+
initViteLoadTests(testModules, { open: true, position: 'left' });
|
|
146
|
+
// Optionally initialize request mocking
|
|
147
|
+
twd.initRequestMocking()
|
|
148
|
+
.then(() => {
|
|
149
|
+
console.log("Request mocking initialized");
|
|
150
|
+
})
|
|
151
|
+
.catch((err) => {
|
|
152
|
+
console.error("Error initializing request mocking:", err);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
// ...rest of your app bootstrap
|
|
156
|
+
createRoot(document.getElementById('root')!).render(
|
|
157
|
+
<StrictMode>
|
|
158
|
+
<RouterProvider router={router} />
|
|
159
|
+
</StrictMode>,
|
|
160
|
+
);
|
|
101
161
|
```
|
|
102
162
|
|
|
103
|
-
- Or manually:
|
|
163
|
+
- Or manually (not recommended):
|
|
104
164
|
|
|
105
165
|
```ts
|
|
106
|
-
// src/
|
|
107
|
-
import
|
|
108
|
-
|
|
166
|
+
// src/main.tsx
|
|
167
|
+
if (import.meta.env.DEV) {
|
|
168
|
+
const testModules = {
|
|
169
|
+
'./app.twd.test.ts': () => import('./app.twd.test'),
|
|
170
|
+
'./another-test-file.twd.test.ts': () => import('./another-test-file.twd.test'),
|
|
171
|
+
};
|
|
172
|
+
const { initViteLoadTests } = await import('twd-js');
|
|
173
|
+
initViteLoadTests(testModules);
|
|
174
|
+
}
|
|
109
175
|
```
|
|
110
176
|
|
|
111
|
-
|
|
177
|
+
4. **Run your app and open the TWD sidebar** to see and run your tests in the browser.
|
|
112
178
|
|
|
113
|
-
|
|
114
|
-
import "./loadTests";
|
|
115
|
-
```
|
|
179
|
+
## Writing Tests
|
|
116
180
|
|
|
117
|
-
|
|
181
|
+
### Test Structure
|
|
118
182
|
|
|
119
|
-
|
|
183
|
+
TWD uses a familiar testing structure with `describe`, `it`, `beforeEach`, and other common testing functions:
|
|
120
184
|
|
|
121
|
-
|
|
185
|
+
```ts
|
|
186
|
+
import { describe, it, itSkip, itOnly, beforeEach, twd, userEvent } from "twd-js";
|
|
122
187
|
|
|
123
|
-
TWD provides a CLI to easily set up a mock service worker for API/request mocking in your app. You do **not** need to manually register the service worker in your app—TWD handles this automatically when you use `twd.initRequestMocking()` in your tests.
|
|
124
188
|
|
|
125
|
-
|
|
189
|
+
describe("User authentication", () => {
|
|
190
|
+
beforeEach(() => {
|
|
191
|
+
// Reset state before each test
|
|
192
|
+
});
|
|
193
|
+
it("should login successfully", async () => {
|
|
194
|
+
twd.visit("/login");
|
|
195
|
+
// Your test logic here
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
itSkip("skipped test", () => {
|
|
199
|
+
// This test will be skipped
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
itOnly("only this test runs", () => {
|
|
203
|
+
// Only this test will run when .only is present
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Element Selection
|
|
209
|
+
|
|
210
|
+
TWD provides two main methods for selecting elements:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
// Select a single element
|
|
214
|
+
const button = await twd.get("button");
|
|
215
|
+
const input = await twd.get("input#email");
|
|
216
|
+
|
|
217
|
+
// Select multiple elements
|
|
218
|
+
const items = await twd.getAll(".item");
|
|
219
|
+
items[0].should("be.visible");
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Assertions
|
|
223
|
+
|
|
224
|
+
TWD includes a comprehensive set of assertions for testing element states:
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
// Text content
|
|
228
|
+
element.should("have.text", "exact text");
|
|
229
|
+
element.should("contain.text", "partial text");
|
|
230
|
+
element.should("be.empty");
|
|
231
|
+
|
|
232
|
+
// Attributes and values
|
|
233
|
+
element.should("have.attr", "placeholder", "Type here");
|
|
234
|
+
element.should("have.value", "input value");
|
|
235
|
+
element.should("have.class", "active");
|
|
236
|
+
|
|
237
|
+
// Element state
|
|
238
|
+
element.should("be.disabled");
|
|
239
|
+
element.should("be.enabled");
|
|
240
|
+
element.should("be.checked");
|
|
241
|
+
element.should("be.selected");
|
|
242
|
+
element.should("be.focused");
|
|
243
|
+
element.should("be.visible");
|
|
244
|
+
|
|
245
|
+
// Negated assertions
|
|
246
|
+
element.should("not.be.disabled");
|
|
247
|
+
element.should("not.have.text", "wrong text");
|
|
248
|
+
|
|
249
|
+
// URL assertions
|
|
250
|
+
twd.url().should("eq", "http://localhost:3000/contact");
|
|
251
|
+
twd.url().should("contain.url", "/contact");
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### User Interactions
|
|
255
|
+
|
|
256
|
+
TWD integrates with `@testing-library/user-event` for realistic user interactions:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { userEvent } from "twd-js";
|
|
260
|
+
|
|
261
|
+
const user = userEvent.setup();
|
|
262
|
+
const button = await twd.get("button");
|
|
263
|
+
const input = await twd.get("input");
|
|
264
|
+
|
|
265
|
+
// Click interactions
|
|
266
|
+
await user.click(button.el);
|
|
267
|
+
await user.dblClick(button.el);
|
|
268
|
+
|
|
269
|
+
// Typing
|
|
270
|
+
await user.type(input.el, "Hello World");
|
|
271
|
+
|
|
272
|
+
// Form interactions
|
|
273
|
+
await user.selectOptions(selectElement.el, "option-value");
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## API Mocking
|
|
277
|
+
|
|
278
|
+
### Setup
|
|
279
|
+
|
|
280
|
+
TWD provides a CLI to easily set up a mock service worker for API/request mocking in your app. You do **not** need to manually register the service worker in your app—TWD handles this automatically when you use `twd.initRequestMocking()` in your tests.
|
|
126
281
|
|
|
127
282
|
Run the following command in your project root:
|
|
128
283
|
|
|
@@ -135,45 +290,170 @@ npx twd-js init <public-dir> [--save]
|
|
|
135
290
|
|
|
136
291
|
This will copy `mock-sw.js` to your public directory.
|
|
137
292
|
|
|
138
|
-
###
|
|
293
|
+
### Mock Requests
|
|
139
294
|
|
|
140
|
-
|
|
295
|
+
Use `twd.mockRequest()` to define API mocks in your tests:
|
|
141
296
|
|
|
142
297
|
```ts
|
|
143
|
-
import {
|
|
298
|
+
import { twd } from "twd-js";
|
|
144
299
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
300
|
+
// Initialize mocking when loading tests
|
|
301
|
+
// await twd.initRequestMocking();
|
|
302
|
+
|
|
303
|
+
it("fetches user data", async () => {
|
|
304
|
+
// Mock the API request
|
|
305
|
+
twd.mockRequest("getUser", {
|
|
149
306
|
method: "GET",
|
|
150
|
-
url: "https://api.example.com/
|
|
307
|
+
url: "https://api.example.com/user/123",
|
|
151
308
|
response: {
|
|
152
|
-
|
|
309
|
+
id: 123,
|
|
310
|
+
name: "John Doe",
|
|
311
|
+
email: "john@example.com"
|
|
153
312
|
},
|
|
313
|
+
status: 200,
|
|
314
|
+
headers: { "Content-Type": "application/json" }
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Trigger the request in your app
|
|
318
|
+
const button = await twd.get("button[data-testid='load-user']");
|
|
319
|
+
await userEvent.click(button.el);
|
|
320
|
+
|
|
321
|
+
// Wait for the mock to be called
|
|
322
|
+
const rule = await twd.waitForRequest("getUser");
|
|
323
|
+
console.log("Request body:", rule.request);
|
|
324
|
+
|
|
325
|
+
// Clean up mocks after test
|
|
326
|
+
twd.clearRequestMockRules();
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Wait for Requests
|
|
331
|
+
|
|
332
|
+
TWD provides utilities to wait for mocked requests:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
// Wait for a single request
|
|
336
|
+
const rule = await twd.waitForRequest("getUserData");
|
|
337
|
+
|
|
338
|
+
// Wait for multiple requests
|
|
339
|
+
const rules = await twd.waitForRequests(["getUser", "getPosts"]);
|
|
340
|
+
|
|
341
|
+
// Access request data
|
|
342
|
+
console.log("Request body:", rule.request);
|
|
343
|
+
console.log("Response:", rule.response);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## API Reference
|
|
347
|
+
|
|
348
|
+
### Test Functions
|
|
349
|
+
|
|
350
|
+
| Function | Description | Example |
|
|
351
|
+
|----------|-------------|---------|
|
|
352
|
+
| `describe(name, fn)` | Groups related tests | `describe("User login", () => {...})` |
|
|
353
|
+
| `it(name, fn)` | Defines a test case | `it("should login", async () => {...})` |
|
|
354
|
+
| `itOnly(name, fn)` | Runs only this test | `itOnly("focused test", () => {...})` |
|
|
355
|
+
| `itSkip(name, fn)` | Skips this test | `itSkip("broken test", () => {...})` |
|
|
356
|
+
| `beforeEach(fn)` | Runs before each test | `beforeEach(() => {...})` |
|
|
357
|
+
|
|
358
|
+
### TWD Commands
|
|
359
|
+
|
|
360
|
+
| Command | Description | Example |
|
|
361
|
+
|---------|-------------|---------|
|
|
362
|
+
| `twd.get(selector)` | Select single element | `await twd.get("button")` |
|
|
363
|
+
| `twd.getAll(selector)` | Select multiple elements | `await twd.getAll(".item")` |
|
|
364
|
+
| `twd.visit(url)` | Navigate to URL | `twd.visit("/contact")` |
|
|
365
|
+
| `twd.wait(ms)` | Wait for specified time | `await twd.wait(500)` |
|
|
366
|
+
| `twd.url()` | Get URL API for assertions | `twd.url().should("contain.url", "/home")` |
|
|
367
|
+
|
|
368
|
+
### Assertions
|
|
369
|
+
|
|
370
|
+
#### Element Content
|
|
371
|
+
- `have.text` - Exact text match
|
|
372
|
+
- `contain.text` - Partial text match
|
|
373
|
+
- `be.empty` - Element has no text content
|
|
374
|
+
|
|
375
|
+
#### Element Attributes
|
|
376
|
+
- `have.attr` - Has specific attribute value
|
|
377
|
+
- `have.value` - Input/textarea value
|
|
378
|
+
- `have.class` - Has CSS class
|
|
379
|
+
|
|
380
|
+
#### Element State
|
|
381
|
+
- `be.disabled` / `be.enabled` - Form element state
|
|
382
|
+
- `be.checked` - Checkbox/radio state
|
|
383
|
+
- `be.selected` - Option element state
|
|
384
|
+
- `be.focused` - Element has focus
|
|
385
|
+
- `be.visible` - Element is visible
|
|
386
|
+
|
|
387
|
+
#### URL Assertions
|
|
388
|
+
- `eq` - Exact URL match
|
|
389
|
+
- `contain.url` - URL contains substring
|
|
390
|
+
|
|
391
|
+
All assertions can be negated with `not.` prefix (e.g., `not.be.disabled`).
|
|
392
|
+
|
|
393
|
+
## Examples
|
|
394
|
+
|
|
395
|
+
### Basic Form Testing
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
import { describe, it, twd, userEvent } from "twd-js";
|
|
399
|
+
|
|
400
|
+
describe("Contact form", () => {
|
|
401
|
+
it("submits form data", async () => {
|
|
402
|
+
twd.visit("/contact");
|
|
403
|
+
|
|
404
|
+
const user = userEvent.setup();
|
|
405
|
+
const emailInput = await twd.get("input#email");
|
|
406
|
+
const messageInput = await twd.get("textarea#message");
|
|
407
|
+
const submitBtn = await twd.get("button[type='submit']");
|
|
408
|
+
|
|
409
|
+
await user.type(emailInput.el, "test@example.com");
|
|
410
|
+
await user.type(messageInput.el, "Hello world");
|
|
411
|
+
|
|
412
|
+
emailInput.should("have.value", "test@example.com");
|
|
413
|
+
messageInput.should("have.value", "Hello world");
|
|
414
|
+
|
|
415
|
+
await user.click(submitBtn.el);
|
|
154
416
|
});
|
|
155
|
-
const btn = await twd.get("button[data-twd='message-button']");
|
|
156
|
-
await user.click(btn.el);
|
|
157
|
-
await twd.waitForRequest("message");
|
|
158
|
-
const messageText = await twd.get("p[data-twd='message-text']");
|
|
159
|
-
messageText.should("have.text", "Mocked message!");
|
|
160
417
|
});
|
|
161
418
|
```
|
|
162
419
|
|
|
163
|
-
|
|
420
|
+
### API Mocking with Authentication
|
|
164
421
|
|
|
165
|
-
|
|
422
|
+
```ts
|
|
423
|
+
import { describe, it, twd, userEvent } from "twd-js";
|
|
166
424
|
|
|
167
|
-
|
|
425
|
+
describe("Protected routes", () => {
|
|
426
|
+
it("redirects to login when unauthorized", async () => {
|
|
427
|
+
twd.visit("/dashboard");
|
|
428
|
+
await twd.wait(100);
|
|
429
|
+
twd.url().should("contain.url", "/login");
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("loads dashboard with valid session", async () => {
|
|
433
|
+
// Mock authentication check
|
|
434
|
+
twd.mockRequest("authCheck", {
|
|
435
|
+
method: "GET",
|
|
436
|
+
url: "/api/auth/me",
|
|
437
|
+
response: { id: 1, name: "John Doe" }
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
twd.visit("/dashboard");
|
|
441
|
+
await twd.waitForRequest("authCheck");
|
|
442
|
+
|
|
443
|
+
const welcome = await twd.get("h1");
|
|
444
|
+
welcome.should("contain.text", "Welcome, John");
|
|
445
|
+
|
|
446
|
+
twd.clearRequestMockRules();
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
```
|
|
168
450
|
|
|
169
|
-
|
|
451
|
+
For more comprehensive examples, see the [examples](https://github.com/BRIKEV/twd/tree/main/examples) directory in the repository.
|
|
170
452
|
|
|
171
453
|
## Contributing
|
|
172
454
|
|
|
173
455
|
Contributions are welcome! Please open issues or pull requests on [GitHub](https://github.com/BRIKEV/twd).
|
|
174
456
|
|
|
175
|
-
---
|
|
176
|
-
|
|
177
457
|
## License
|
|
178
458
|
|
|
179
459
|
This project is licensed under the [MIT License](./LICENSE).
|