twd-js 0.1.1 → 0.2.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 CHANGED
@@ -3,12 +3,26 @@
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)
5
5
  [![license](https://img.shields.io/github/license/brikev/twd.svg)](./LICENSE)
6
+ [![Maintainability](https://qlty.sh/gh/BRIKEV/projects/twd/maintainability.svg)](https://qlty.sh/gh/BRIKEV/projects/twd)
7
+ [![Code Coverage](https://qlty.sh/gh/BRIKEV/projects/twd/coverage.svg)](https://qlty.sh/gh/BRIKEV/projects/twd)
6
8
 
7
9
  > ⚠️ This is a **beta release** – expect frequent updates and possible breaking changes.
8
10
 
9
- TWD (Testing Web Development) is a tool designed to help integrating testing while developing web applications. It aims to streamline the testing process and make it easier for developers to write and run tests as they build their applications.
10
11
 
11
- Right now we only support React, but we plan to add support for other frameworks in the future.
12
+ TWD (Testing Web Development) is a library designed to seamlessly integrate testing into your web development workflow. It streamlines the process of writing, running, and managing tests directly in your application, with a modern UI and powerful mocking capabilities.
13
+
14
+ Currently, TWD supports React, with plans to add more frameworks soon.
15
+
16
+ ---
17
+
18
+ ## Features
19
+
20
+ - 🧪 **In-browser test runner** with a beautiful sidebar UI
21
+ - ⚡ **Instant feedback** as you develop
22
+ - 🔥 **Mock Service Worker** integration for API/request mocking
23
+ - 📝 **Simple, readable test syntax** (inspired by popular test frameworks)
24
+ - 🧩 **Automatic test discovery** with Vite support
25
+ - 🛠️ **Works with React** (support for more frameworks coming)
12
26
 
13
27
  ## Installation
14
28
 
@@ -25,74 +39,132 @@ yarn add twd-js
25
39
  pnpm add twd-js
26
40
  ```
27
41
 
28
- ## How to use
29
42
 
30
- Add the our React Sidebar component to your application:
43
+ ## Quick Start
44
+
45
+ 1. **Add the TWD Sidebar to your React app:**
46
+
47
+ ```tsx
48
+ import { StrictMode } from 'react';
49
+ import { createRoot } from 'react-dom/client';
50
+ import App from './App';
51
+ import './index.css';
52
+ import { TWDSidebar } from 'twd-js';
53
+
54
+ createRoot(document.getElementById('root')!).render(
55
+ <StrictMode>
56
+ <App />
57
+ <TWDSidebar />
58
+ </StrictMode>,
59
+ );
60
+ ```
61
+
62
+ 2. **Write your tests:**
63
+
64
+ Create files ending with `.twd.test.ts` (or any extension you prefer):
65
+
66
+ ```ts
67
+ // src/app.twd.test.ts
68
+ import { describe, it, twd } from "twd-js";
69
+
70
+ beforeEach(() => {
71
+ // Reset state before each test
72
+ });
73
+
74
+ describe("App interactions", () => {
75
+ it("clicks the button", async () => {
76
+ twd.visit("/");
77
+ const btn = await twd.get("button");
78
+ btn.click();
79
+ const message = await twd.get("#message");
80
+ message.should("have.text", "Hello");
81
+ });
82
+ });
83
+ ```
84
+
85
+ 3. **Auto-load your tests:**
86
+
87
+ - With Vite:
88
+
89
+ ```ts
90
+ // src/loadTests.ts
91
+ const modules = import.meta.glob("./**/*.twd.test.ts", { eager: true });
92
+ // No need to export anything
93
+ ```
94
+
95
+ - Or manually:
96
+
97
+ ```ts
98
+ // src/loadTests.ts
99
+ import "./app.twd.test";
100
+ import "./another-test-file.twd.test";
101
+ ```
102
+
103
+ Import `loadTests.ts` in your main entry (e.g., `main.tsx`):
31
104
 
32
- ```tsx
33
- import { StrictMode } from 'react'
34
- import { createRoot } from 'react-dom/client'
35
- import './index.css'
36
- import { TWDSidebar } from 'twd-js'
37
- import router from './routes.ts'
38
- import { RouterProvider } from 'react-router'
105
+ ```tsx
106
+ import './loadTests';
107
+ ```
39
108
 
40
- createRoot(document.getElementById('root')!).render(
41
- <StrictMode>
42
- <RouterProvider router={router} />
43
- <TWDSidebar />
44
- </StrictMode>,
45
- )
109
+ 4. **Run your app and open the TWD sidebar** to see and run your tests in the browser.
110
+
111
+ ---
112
+
113
+ ## Mock Service Worker (API Mocking)
114
+
115
+
116
+ 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.
117
+
118
+ ### Install the mock service worker
119
+
120
+ Run the following command in your project root:
121
+
122
+ ```bash
123
+ npx twd-mock init <public-dir> [--save]
46
124
  ```
47
125
 
48
- Then, create test files with the `twd.test.ts` or any extension you want. For example:
126
+ - Replace `<public-dir>` with the path to your app's public/static directory (e.g., `public/` or `dist/`).
127
+ - Use `--save` to print a registration snippet for your app.
49
128
 
50
- ```ts
51
- // src/app.twd.test.ts
52
- import { describe, it, twd } from "twd-js";
129
+ This will copy `mock-sw.js` to your public directory.
53
130
 
54
- beforeEach(() => {
55
- console.log("Reset state before each test");
56
- });
57
131
 
58
- describe("App interactions", () => {
59
- it("clicks the button", async () => {
60
- twd.visit("/"); // Visit the root URL
61
- const btn = await twd.get("button");
62
- btn.click();
63
- const message = await twd.get("#message");
64
- // have.text
65
- const haveText = await twd.get("#message");
66
- haveText.should("have.text", "Hello");
132
+ ### How to use request mocking in your tests
133
+
134
+ Just call `await twd.initRequestMocking()` at the start of your test, then use `twd.mockRequest` to define your mocks. Example:
135
+
136
+ ```ts
137
+ it("fetches a message", async () => {
138
+ await twd.initRequestMocking();
139
+ await twd.mockRequest("message", {
140
+ method: "GET",
141
+ url: "https://api.example.com/message",
142
+ response: {
143
+ value: "Mocked message!",
144
+ },
67
145
  });
146
+ const btn = await twd.get("button[data-twd='message-button']");
147
+ btn.click();
148
+ await twd.waitForRequest("message");
149
+ const messageText = await twd.get("p[data-twd='message-text']");
150
+ messageText.should("have.text", "Mocked message!");
68
151
  });
69
152
  ```
70
153
 
71
- After you create your test you need to load them in your application. You can do this by creating a `loadTests.ts` file and importing all your test files there:
154
+ ---
72
155
 
73
- ```ts
74
- // src/loadTests.ts
75
- import "./app.twd.test";
76
- import "./another-test-file.twd.test";
77
- // Import other test files here
78
- ```
156
+ ## More Usage Examples
79
157
 
80
- Or if you're using vite you can use Vite's `import.meta.glob` to automatically import all test files in a directory:
158
+ See the [examples](https://github.com/BRIKEV/twd/tree/main/examples) directory for more scenarios and advanced usage.
81
159
 
82
- ```ts
83
- // This automatically imports all files ending with .twd.test.ts
84
- const modules = import.meta.glob("./**/*.twd.test.ts", { eager: true });
160
+ ---
85
161
 
86
- // You don't need to export anything; simply importing this in App.tsx
87
- // will cause the test files to execute and register their tests.
88
- ```
162
+ ## Contributing
89
163
 
90
- Then, import the `loadTests.ts` file in your main application file (e.g., `main.tsx` or `App.tsx`):
164
+ Contributions are welcome! Please open issues or pull requests on [GitHub](https://github.com/BRIKEV/twd).
91
165
 
92
- ```tsx
93
- import './loadTests' // Import test files
94
- ```
166
+ ---
95
167
 
96
- Finally, run your application and open the TWD sidebar to see and run your tests.
168
+ ## License
97
169
 
98
- You can check the [examples](https://github.com/BRIKEV/twd/tree/main/examples) directory for more usage scenarios.
170
+ This project is licensed under the [MIT License](./LICENSE).
@@ -1,2 +1,2 @@
1
1
  import { AnyAssertion } from '../twd-types';
2
- export declare const runAssertion: (el: Element, name: AnyAssertion, ...args: any[]) => void;
2
+ export declare const runAssertion: (el: Element, name: AnyAssertion, ...args: any[]) => string;
package/dist/cli.js ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+
8
+ const [, , command, targetDir, ...flags] = process.argv;
9
+
10
+ if (command !== "init") {
11
+ console.error("Usage: npx twd-mock init <public-dir> [--save]");
12
+ process.exit(1);
13
+ }
14
+
15
+ if (!targetDir) {
16
+ console.error("❌ You must provide a target public dir");
17
+ process.exit(1);
18
+ }
19
+
20
+ const save = flags.includes("--save");
21
+ const src = path.join(__dirname, "../dist/mock-sw.js");
22
+ const dest = path.resolve(process.cwd(), targetDir, "mock-sw.js");
23
+
24
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
25
+ fs.copyFileSync(src, dest);
26
+
27
+ console.log(`✅ mock-sw.js copied to ${dest}`);
28
+ if (save) {
29
+ console.log("💡 Remember to register it in your app:");
30
+ console.log(`
31
+ if ("serviceWorker" in navigator) {
32
+ navigator.serviceWorker.register("/mock-sw.js?v=1");
33
+ }
34
+ `);
35
+ }
@@ -2,7 +2,7 @@ export type Rule = {
2
2
  method: string;
3
3
  url: string | RegExp;
4
4
  response: unknown;
5
- alias?: string;
5
+ alias: string;
6
6
  executed?: boolean;
7
7
  request?: unknown;
8
8
  status?: number;
@@ -15,6 +15,11 @@ export interface Options {
15
15
  status?: number;
16
16
  headers?: Record<string, string>;
17
17
  }
18
+ /**
19
+ * Initialize the mocking service worker.
20
+ * Call this once before using `mockRequest` or `waitFor`.
21
+ */
22
+ export declare const initRequestMocking: () => Promise<void>;
18
23
  /**
19
24
  * Mock a network request.
20
25
  *
@@ -37,10 +42,19 @@ export interface Options {
37
42
  * });
38
43
  * ```
39
44
  */
40
- export declare const mockRequest: (alias: string, options: Options) => void;
45
+ export declare const mockRequest: (alias: string, options: Options) => Promise<void>;
41
46
  /**
42
47
  * Wait for a mocked request to be made.
43
48
  * @param alias The alias of the mock rule to wait for
44
49
  * @returns The matched rule (with body if applicable)
45
50
  */
46
- export declare const waitFor: (alias: string) => Promise<Rule>;
51
+ export declare const waitForRequest: (alias: string) => Promise<Rule>;
52
+ /**
53
+ * Get the current list of request mock rules.
54
+ * @returns The current list of request mock rules.
55
+ */
56
+ export declare const getRequestMockRules: () => Rule[];
57
+ /**
58
+ * Clear all request mock rules.
59
+ */
60
+ export declare const clearRequestMockRules: () => void;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * All supported assertion names for the `should` function in url command
3
+ *
4
+ * @example
5
+ * twd.url().should("contain.url", "/new-page");
6
+ * twd.url().should("eq", "http://localhost:3000/new-page");
7
+ */
8
+ export type URLAssertionName = "eq" | "contain.url";
9
+ /**
10
+ * Negatable assertion names (e.g., 'not.have.text').
11
+ */
12
+ export type Negatable<T extends string> = T | `not.${T}`;
13
+ /**
14
+ * All assertion names, including negated ones.
15
+ */
16
+ export type AnyURLAssertion = Negatable<URLAssertionName>;
17
+ export type URLCommandAPI = {
18
+ location: Location;
19
+ should: (name: AnyURLAssertion, value: string) => URLCommandAPI | string;
20
+ };
21
+ /**
22
+ * Argument types for each assertion.
23
+ */
24
+ export type URLAssertionArgs = {
25
+ (name: "contain.url", urlPart: string): URLCommandAPI;
26
+ (name: "not.contain.url", urlPart: string): URLCommandAPI;
27
+ };
28
+ /**
29
+ * URL command for assertions on the current URL.
30
+ * @returns URLCommandAPI
31
+ */
32
+ declare const urlCommand: () => URLCommandAPI;
33
+ export default urlCommand;
@@ -0,0 +1 @@
1
+ function i(t,n,s){return s.find(e=>e.method===t&&(typeof e.url=="string"?e.url===n:new RegExp(e.url).test(n)))}function r(t,n,s){t.forEach(e=>e.postMessage({type:"EXECUTED",alias:n.alias,request:s}))}let l=[];self.addEventListener("fetch",t=>{const{method:n}=t.request,s=t.request.url,e=i(n,s,l);e&&(console.log("Mock hit:",e.alias,n,s),t.respondWith((async()=>{let a=null;try{a=await t.request.clone().text()}catch{}return self.clients.matchAll().then(o=>{r(o,e,a)}),new Response(JSON.stringify(e.response),{status:e.status||200,headers:e.headers||{"Content-Type":"application/json"}})})()))});self.addEventListener("message",t=>{const{type:n,rule:s}=t.data||{};n==="ADD_RULE"&&(l=l.filter(e=>e.alias!==s.alias),l.push(s),console.log("Rule added:",s))});
@@ -50,16 +50,25 @@ export type ArgsFor<A extends AnyAssertion> = A extends `not.${infer Base extend
50
50
  */
51
51
  export type ShouldFn = {
52
52
  (name: "have.text", expected: string): TWDElemAPI;
53
+ (name: "not.have.text", expected: string): TWDElemAPI;
53
54
  (name: "contain.text", expected: string): TWDElemAPI;
55
+ (name: "not.contain.text", expected: string): TWDElemAPI;
54
56
  (name: "be.empty"): TWDElemAPI;
57
+ (name: "not.be.empty"): TWDElemAPI;
55
58
  (name: "have.attr", attr: string, value: string): TWDElemAPI;
59
+ (name: "not.have.attr", attr: string, value: string): TWDElemAPI;
56
60
  (name: "have.value", value: string): TWDElemAPI;
61
+ (name: "not.have.value", value: string): TWDElemAPI;
57
62
  (name: "be.disabled"): TWDElemAPI;
63
+ (name: "not.be.disabled"): TWDElemAPI;
58
64
  (name: "be.enabled"): TWDElemAPI;
65
+ (name: "not.be.enabled"): TWDElemAPI;
59
66
  (name: "be.checked"): TWDElemAPI;
60
67
  (name: "not.be.checked"): TWDElemAPI;
61
68
  (name: "be.selected"): TWDElemAPI;
69
+ (name: "not.be.selected"): TWDElemAPI;
62
70
  (name: "be.focused"): TWDElemAPI;
71
+ (name: "not.be.focused"): TWDElemAPI;
63
72
  (name: "be.visible"): TWDElemAPI;
64
73
  (name: "not.be.visible"): TWDElemAPI;
65
74
  (name: "have.class", className: string): TWDElemAPI;
@@ -122,7 +131,7 @@ export interface TWDElemAPI {
122
131
  text: () => string;
123
132
  /**
124
133
  * Asserts something about the element.
125
- * @param anyAssertion The assertion to run.
134
+ * @param name The name of the assertion.
126
135
  * @param args Arguments for the assertion.
127
136
  * @returns The same API for chaining.
128
137
  *
package/dist/twd.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { Options, Rule } from './commands/mockResponses';
1
+ import { Options, Rule } from './commands/mockBridge';
2
2
  import { TWDElemAPI } from './twd-types';
3
+ import { URLCommandAPI } from './commands/url';
3
4
  /**
4
5
  * Stores the function to run before each test.
5
6
  */
@@ -94,7 +95,49 @@ interface TWDAPI {
94
95
  *
95
96
  * ```
96
97
  */
97
- waitFor: (alias: string) => Promise<Rule>;
98
+ waitForRequest: (alias: string) => Promise<Rule>;
99
+ /**
100
+ * URL-related assertions.
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * twd.url().should("eq", "http://localhost:3000/contact");
105
+ * twd.url().should("contain.url", "/contact");
106
+ *
107
+ * ```
108
+ */
109
+ url: () => URLCommandAPI;
110
+ /**
111
+ * Initializes request mocking (registers the service worker).
112
+ * Must be called before using `twd.mockRequest()`.
113
+ *
114
+ * @example
115
+ * ```ts
116
+ * await twd.initRequestMocking();
117
+ *
118
+ * ```
119
+ */
120
+ initRequestMocking: () => Promise<void>;
121
+ /**
122
+ * Clears all request mock rules.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * twd.clearRequestMockRules();
127
+ *
128
+ * ```
129
+ */
130
+ clearRequestMockRules: () => void;
131
+ /**
132
+ * Gets all current request mock rules.
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * const rules = twd.getRequestMockRules();
137
+ * console.log(rules);
138
+ * ```
139
+ */
140
+ getRequestMockRules: () => Rule[];
98
141
  }
99
142
  /**
100
143
  * Mini Cypress-style helpers for DOM testing.