twd-js 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # twd
1
+ # TWD
2
2
 
3
3
  [![CI](https://github.com/BRIKEV/twd/actions/workflows/ci.yml/badge.svg)](https://github.com/BRIKEV/twd/actions/workflows/ci.yml)
4
4
  [![npm version](https://img.shields.io/npm/v/twd-js.svg)](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
- import { TWDSidebar } from "twd-js";
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 { initTests, twd, TWDSidebar } = await import('twd-js');
76
+ const { createRoot } = await import('react-dom/client');
77
+ // You need to pass the test modules, the sidebar component, and createRoot function
78
+ initTests(testModules, <TWDSidebar open={true} position="left" />, createRoot);
79
+ // if you want to use mock requests, you can initialize it here
80
+ twd.initRequestMocking()
81
+ .then(() => {
82
+ console.log("Request mocking initialized");
83
+ })
84
+ .catch((err) => {
85
+ console.error("Error initializing request mocking:", err);
86
+ });
87
+ }
51
88
 
52
89
  createRoot(document.getElementById("root")!).render(
53
90
  <StrictMode>
54
91
  <App />
55
- <TWDSidebar />
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,161 @@ pnpm add twd-js
82
125
 
83
126
  3. **Auto-load your tests:**
84
127
 
85
- - With Vite:
128
+
129
+ - With Vite and the new TWD loader:
86
130
 
87
131
  ```ts
88
- import { twd } from "twd-js";
89
- // src/loadTests.ts
90
- import.meta.glob("./**/*.twd.test.ts", { eager: true });
91
- // Initialize request mocking once
92
- twd
93
- .initRequestMocking()
94
- .then(() => {
95
- console.log("Request mocking initialized");
96
- })
97
- .catch((err) => {
98
- console.error("Error initializing request mocking:", err);
99
- });
100
- // No need to export anything
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 { initTests, twd, TWDSidebar } = await import('twd-js');
144
+ const { createRoot } = await import('react-dom/client');
145
+ // You need to pass the test modules, the sidebar component, and createRoot function
146
+ initTests(testModules, <TWDSidebar open={true} position="left" />, createRoot);
147
+ // Optionally initialize request mocking
148
+ twd.initRequestMocking()
149
+ .then(() => {
150
+ console.log("Request mocking initialized");
151
+ })
152
+ .catch((err) => {
153
+ console.error("Error initializing request mocking:", err);
154
+ });
155
+ }
156
+ // ...rest of your app bootstrap
157
+ createRoot(document.getElementById('root')!).render(
158
+ <StrictMode>
159
+ <RouterProvider router={router} />
160
+ </StrictMode>,
161
+ );
101
162
  ```
102
163
 
103
- - Or manually:
164
+ - Or manually (not recommended):
104
165
 
105
166
  ```ts
106
- // src/loadTests.ts
107
- import "./app.twd.test";
108
- import "./another-test-file.twd.test";
167
+ // src/main.tsx
168
+ if (import.meta.env.DEV) {
169
+ const testModules = {
170
+ './app.twd.test.ts': () => import('./app.twd.test'),
171
+ './another-test-file.twd.test.ts': () => import('./another-test-file.twd.test'),
172
+ };
173
+ const { initTests, TWDSidebar } = await import('twd-js');
174
+ const { createRoot } = await import('react-dom/client');
175
+ initTests(testModules, <TWDSidebar open={true} position="left" />, createRoot);
176
+ }
109
177
  ```
110
178
 
111
- Import `loadTests.ts` in your main entry (e.g., `main.tsx`):
179
+ 4. **Run your app and open the TWD sidebar** to see and run your tests in the browser.
112
180
 
113
- ```tsx
114
- import "./loadTests";
115
- ```
181
+ ## Writing Tests
116
182
 
117
- 4. **Run your app and open the TWD sidebar** to see and run your tests in the browser.
183
+ ### Test Structure
118
184
 
119
- ---
185
+ TWD uses a familiar testing structure with `describe`, `it`, `beforeEach`, and other common testing functions:
120
186
 
121
- ## Mock Service Worker (API Mocking)
187
+ ```ts
188
+ import { describe, it, itSkip, itOnly, beforeEach, twd, userEvent } from "twd-js";
122
189
 
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
190
 
125
- ### Install the mock service worker
191
+ describe("User authentication", () => {
192
+ beforeEach(() => {
193
+ // Reset state before each test
194
+ });
195
+ it("should login successfully", async () => {
196
+ twd.visit("/login");
197
+ // Your test logic here
198
+ });
199
+
200
+ itSkip("skipped test", () => {
201
+ // This test will be skipped
202
+ });
203
+
204
+ itOnly("only this test runs", () => {
205
+ // Only this test will run when .only is present
206
+ });
207
+ });
208
+ ```
209
+
210
+ ### Element Selection
211
+
212
+ TWD provides two main methods for selecting elements:
213
+
214
+ ```ts
215
+ // Select a single element
216
+ const button = await twd.get("button");
217
+ const input = await twd.get("input#email");
218
+
219
+ // Select multiple elements
220
+ const items = await twd.getAll(".item");
221
+ items[0].should("be.visible");
222
+ ```
223
+
224
+ ### Assertions
225
+
226
+ TWD includes a comprehensive set of assertions for testing element states:
227
+
228
+ ```ts
229
+ // Text content
230
+ element.should("have.text", "exact text");
231
+ element.should("contain.text", "partial text");
232
+ element.should("be.empty");
233
+
234
+ // Attributes and values
235
+ element.should("have.attr", "placeholder", "Type here");
236
+ element.should("have.value", "input value");
237
+ element.should("have.class", "active");
238
+
239
+ // Element state
240
+ element.should("be.disabled");
241
+ element.should("be.enabled");
242
+ element.should("be.checked");
243
+ element.should("be.selected");
244
+ element.should("be.focused");
245
+ element.should("be.visible");
246
+
247
+ // Negated assertions
248
+ element.should("not.be.disabled");
249
+ element.should("not.have.text", "wrong text");
250
+
251
+ // URL assertions
252
+ twd.url().should("eq", "http://localhost:3000/contact");
253
+ twd.url().should("contain.url", "/contact");
254
+ ```
255
+
256
+ ### User Interactions
257
+
258
+ TWD integrates with `@testing-library/user-event` for realistic user interactions:
259
+
260
+ ```ts
261
+ import { userEvent } from "twd-js";
262
+
263
+ const user = userEvent.setup();
264
+ const button = await twd.get("button");
265
+ const input = await twd.get("input");
266
+
267
+ // Click interactions
268
+ await user.click(button.el);
269
+ await user.dblClick(button.el);
270
+
271
+ // Typing
272
+ await user.type(input.el, "Hello World");
273
+
274
+ // Form interactions
275
+ await user.selectOptions(selectElement.el, "option-value");
276
+ ```
277
+
278
+ ## API Mocking
279
+
280
+ ### Setup
281
+
282
+ 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
283
 
127
284
  Run the following command in your project root:
128
285
 
@@ -135,45 +292,170 @@ npx twd-js init <public-dir> [--save]
135
292
 
136
293
  This will copy `mock-sw.js` to your public directory.
137
294
 
138
- ### How to use request mocking in your tests
295
+ ### Mock Requests
139
296
 
140
- Just call `await twd.initRequestMocking()` at the start of your test, then use `twd.mockRequest` to define your mocks. Example:
297
+ Use `twd.mockRequest()` to define API mocks in your tests:
141
298
 
142
299
  ```ts
143
- import { describe, it, twd, userEvent } from "twd-js";
300
+ import { twd } from "twd-js";
144
301
 
145
- it("fetches a message", async () => {
146
- twd.visit("/");
147
- const user = userEvent.setup();
148
- await twd.mockRequest("message", {
302
+ // Initialize mocking when loading tests
303
+ // await twd.initRequestMocking();
304
+
305
+ it("fetches user data", async () => {
306
+ // Mock the API request
307
+ twd.mockRequest("getUser", {
149
308
  method: "GET",
150
- url: "https://api.example.com/message",
309
+ url: "https://api.example.com/user/123",
151
310
  response: {
152
- value: "Mocked message!",
311
+ id: 123,
312
+ name: "John Doe",
313
+ email: "john@example.com"
153
314
  },
315
+ status: 200,
316
+ headers: { "Content-Type": "application/json" }
317
+ });
318
+
319
+ // Trigger the request in your app
320
+ const button = await twd.get("button[data-testid='load-user']");
321
+ await userEvent.click(button.el);
322
+
323
+ // Wait for the mock to be called
324
+ const rule = await twd.waitForRequest("getUser");
325
+ console.log("Request body:", rule.request);
326
+
327
+ // Clean up mocks after test
328
+ twd.clearRequestMockRules();
329
+ });
330
+ ```
331
+
332
+ ### Wait for Requests
333
+
334
+ TWD provides utilities to wait for mocked requests:
335
+
336
+ ```ts
337
+ // Wait for a single request
338
+ const rule = await twd.waitForRequest("getUserData");
339
+
340
+ // Wait for multiple requests
341
+ const rules = await twd.waitForRequests(["getUser", "getPosts"]);
342
+
343
+ // Access request data
344
+ console.log("Request body:", rule.request);
345
+ console.log("Response:", rule.response);
346
+ ```
347
+
348
+ ## API Reference
349
+
350
+ ### Test Functions
351
+
352
+ | Function | Description | Example |
353
+ |----------|-------------|---------|
354
+ | `describe(name, fn)` | Groups related tests | `describe("User login", () => {...})` |
355
+ | `it(name, fn)` | Defines a test case | `it("should login", async () => {...})` |
356
+ | `itOnly(name, fn)` | Runs only this test | `itOnly("focused test", () => {...})` |
357
+ | `itSkip(name, fn)` | Skips this test | `itSkip("broken test", () => {...})` |
358
+ | `beforeEach(fn)` | Runs before each test | `beforeEach(() => {...})` |
359
+
360
+ ### TWD Commands
361
+
362
+ | Command | Description | Example |
363
+ |---------|-------------|---------|
364
+ | `twd.get(selector)` | Select single element | `await twd.get("button")` |
365
+ | `twd.getAll(selector)` | Select multiple elements | `await twd.getAll(".item")` |
366
+ | `twd.visit(url)` | Navigate to URL | `twd.visit("/contact")` |
367
+ | `twd.wait(ms)` | Wait for specified time | `await twd.wait(500)` |
368
+ | `twd.url()` | Get URL API for assertions | `twd.url().should("contain.url", "/home")` |
369
+
370
+ ### Assertions
371
+
372
+ #### Element Content
373
+ - `have.text` - Exact text match
374
+ - `contain.text` - Partial text match
375
+ - `be.empty` - Element has no text content
376
+
377
+ #### Element Attributes
378
+ - `have.attr` - Has specific attribute value
379
+ - `have.value` - Input/textarea value
380
+ - `have.class` - Has CSS class
381
+
382
+ #### Element State
383
+ - `be.disabled` / `be.enabled` - Form element state
384
+ - `be.checked` - Checkbox/radio state
385
+ - `be.selected` - Option element state
386
+ - `be.focused` - Element has focus
387
+ - `be.visible` - Element is visible
388
+
389
+ #### URL Assertions
390
+ - `eq` - Exact URL match
391
+ - `contain.url` - URL contains substring
392
+
393
+ All assertions can be negated with `not.` prefix (e.g., `not.be.disabled`).
394
+
395
+ ## Examples
396
+
397
+ ### Basic Form Testing
398
+
399
+ ```ts
400
+ import { describe, it, twd, userEvent } from "twd-js";
401
+
402
+ describe("Contact form", () => {
403
+ it("submits form data", async () => {
404
+ twd.visit("/contact");
405
+
406
+ const user = userEvent.setup();
407
+ const emailInput = await twd.get("input#email");
408
+ const messageInput = await twd.get("textarea#message");
409
+ const submitBtn = await twd.get("button[type='submit']");
410
+
411
+ await user.type(emailInput.el, "test@example.com");
412
+ await user.type(messageInput.el, "Hello world");
413
+
414
+ emailInput.should("have.value", "test@example.com");
415
+ messageInput.should("have.value", "Hello world");
416
+
417
+ await user.click(submitBtn.el);
154
418
  });
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
419
  });
161
420
  ```
162
421
 
163
- ---
422
+ ### API Mocking with Authentication
164
423
 
165
- ## More Usage Examples
424
+ ```ts
425
+ import { describe, it, twd, userEvent } from "twd-js";
166
426
 
167
- See the [examples](https://github.com/BRIKEV/twd/tree/main/examples) directory for more scenarios and advanced usage.
427
+ describe("Protected routes", () => {
428
+ it("redirects to login when unauthorized", async () => {
429
+ twd.visit("/dashboard");
430
+ await twd.wait(100);
431
+ twd.url().should("contain.url", "/login");
432
+ });
433
+
434
+ it("loads dashboard with valid session", async () => {
435
+ // Mock authentication check
436
+ twd.mockRequest("authCheck", {
437
+ method: "GET",
438
+ url: "/api/auth/me",
439
+ response: { id: 1, name: "John Doe" }
440
+ });
441
+
442
+ twd.visit("/dashboard");
443
+ await twd.waitForRequest("authCheck");
444
+
445
+ const welcome = await twd.get("h1");
446
+ welcome.should("contain.text", "Welcome, John");
447
+
448
+ twd.clearRequestMockRules();
449
+ });
450
+ });
451
+ ```
168
452
 
169
- ---
453
+ For more comprehensive examples, see the [examples](https://github.com/BRIKEV/twd/tree/main/examples) directory in the repository.
170
454
 
171
455
  ## Contributing
172
456
 
173
457
  Contributions are welcome! Please open issues or pull requests on [GitHub](https://github.com/BRIKEV/twd).
174
458
 
175
- ---
176
-
177
459
  ## License
178
460
 
179
461
  This project is licensed under the [MIT License](./LICENSE).
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './twd';
2
2
  export { TWDSidebar } from './ui/TWDSidebar';
3
+ export { initTests } from './initializers/initTests';
3
4
  export { expect } from 'chai';
4
5
  export { userEvent } from './proxies/userEvent';
@@ -0,0 +1,22 @@
1
+ interface Options {
2
+ Component: React.ReactNode;
3
+ createRoot: (el: HTMLElement) => {
4
+ render: (el: React.ReactNode) => void;
5
+ };
6
+ }
7
+ /**
8
+ * Initialize the TWD sidebar.
9
+ * @param options - Options for initializing the sidebar.
10
+ * @example
11
+ * ```ts
12
+ * import { initSidebar } from 'twd-js';
13
+ *
14
+ * // Initialize the sidebar (e.g., in your main app file)
15
+ * initSidebar({
16
+ * Component: <TWDSidebar open={true} position="left" />,
17
+ * createRoot,
18
+ * });
19
+ * ```
20
+ */
21
+ export declare const initSidebar: (options: Options) => void;
22
+ export {};
@@ -0,0 +1,32 @@
1
+ /**
2
+ * A record of test module paths to their loader functions.
3
+ * Each function returns a promise that resolves when the module is loaded.
4
+ * This is typically used with Vite's `import.meta.glob` to dynamically import test modules.
5
+ * @example
6
+ * ```ts
7
+ * const testModules = {
8
+ * './test1.twd.test.ts': () => import('./test1.twd.test.ts'),
9
+ * './test2.twd.test.ts': () => import('./test2.twd.test.ts'),
10
+ * };
11
+ * ```
12
+ */
13
+ type TestModule = Record<string, () => Promise<unknown>>;
14
+ /**
15
+ * Initialize Vite test loading.
16
+ * @param testModules - The test modules to load.
17
+ * @param component - The React component to render the sidebar.
18
+ * @param createRoot - Function to create a React root.
19
+ * @example
20
+ * ```ts
21
+ * if (import.meta.env.DEV) {
22
+ * const testModules = import.meta.glob("./example.twd.test.ts");
23
+ * const { initTests, TWDSidebar } = await import('twd-js');
24
+ * const { createRoot } = await import('react-dom/client');
25
+ * await initTests(testModules, <TWDSidebar open={true} position="left" />, createRoot);
26
+ * }
27
+ * ```
28
+ */
29
+ export declare const initTests: (testModules: TestModule, Component: React.ReactNode, createRoot: (el: HTMLElement) => {
30
+ render: (el: React.ReactNode) => void;
31
+ }) => Promise<void>;
32
+ export {};
package/dist/mock-sw.js CHANGED
@@ -1 +1 @@
1
- function i(s,t,l){return l.find(e=>{const n=e.method.toLowerCase()===s.toLowerCase();if(e.urlRegex){const r=new RegExp(e.url);return n&&r.test(t)}const a=e.url===t||t.includes(e.url);return n&&a})}function c(s,t,l){s.forEach(e=>e.postMessage({type:"EXECUTED",alias:t.alias,request:l}))}let o=[];self.addEventListener("fetch",s=>{const{method:t}=s.request,l=s.request.url,e=i(t,l,o);e&&(console.log("Mock hit:",e.alias,t,l),s.respondWith((async()=>{let n=null;try{n=await s.request.clone().text()}catch{}return self.clients.matchAll().then(a=>{c(a,e,n)}),new Response(JSON.stringify(e.response),{status:e.status||200,headers:e.headers||{"Content-Type":"application/json"}})})()))});self.addEventListener("message",s=>{const{type:t,rule:l}=s.data||{};t==="ADD_RULE"&&(o=o.filter(e=>e.alias!==l.alias),o.push(l),console.log("Rule added:",l)),t==="CLEAR_RULES"&&(o=[],console.log("All rules cleared"))});
1
+ function c(t,s,n){return n.find(e=>{const l=e.method.toLowerCase()===t.toLowerCase();if(e.urlRegex){const r=new RegExp(e.url);return l&&r.test(s)}const a=e.url===s||s.includes(e.url);return l&&a})}function i(t,s,n){t.forEach(e=>e.postMessage({type:"EXECUTED",alias:s.alias,request:n}))}let o=[];const u=async t=>{const{method:s}=t.request,n=t.request.url,e=c(s,n,o);e&&(console.log("Mock hit:",e.alias,s,n),t.respondWith((async()=>{let l=null;try{l=await t.request.clone().text()}catch{}return self.clients.matchAll().then(a=>{i(a,e,l)}),new Response(JSON.stringify(e.response),{status:e.status||200,headers:e.headers||{"Content-Type":"application/json"}})})()))},d=t=>{const{type:s,rule:n}=t.data||{};s==="ADD_RULE"&&(o=o.filter(e=>e.alias!==n.alias),o.push(n),console.log("Rule added:",n)),s==="CLEAR_RULES"&&(o=[],console.log("All rules cleared"))};self.addEventListener("fetch",u);self.addEventListener("message",d);