twd-js 1.7.3 → 1.8.0-beta.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 +39 -19
- package/dist/bundled.es.js +1 -1
- package/dist/constants/version.d.ts +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/mock-sw.js +1 -1
- package/dist/plugin/twd.d.ts +87 -0
- package/dist/vite-plugin.cjs.js +2 -1
- package/dist/vite-plugin.d.ts +1 -0
- package/dist/vite-plugin.es.js +58 -11
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://qlty.sh/gh/BRIKEV/projects/twd)
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<img src="
|
|
10
|
+
<img src="https://raw.githubusercontent.com/BRIKEV/twd/main/images/twd-skill.gif" alt="TWD running with an AI agent" width="800">
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
TWD 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.
|
|
@@ -36,25 +36,26 @@ pnpm add twd-js
|
|
|
36
36
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
|
-
### React
|
|
39
|
+
### Vite-based projects (React, Vue, Solid, and more)
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
Add the `twd()` plugin to your `vite.config.ts`. The plugin auto-loads the sidebar and discovers test files in dev — no entry-file changes required.
|
|
42
42
|
|
|
43
43
|
```ts
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
// vite.config.ts
|
|
45
|
+
import { defineConfig } from 'vite';
|
|
46
|
+
import react from '@vitejs/plugin-react'; // or vue, solid, etc.
|
|
47
|
+
import { twd } from 'twd-js/vite-plugin';
|
|
48
|
+
|
|
49
|
+
export default defineConfig({
|
|
50
|
+
plugins: [
|
|
51
|
+
react(),
|
|
52
|
+
twd({
|
|
53
|
+
testFilePattern: '/**/*.twd.test.{ts,tsx}',
|
|
54
|
+
open: true,
|
|
55
|
+
position: 'left',
|
|
56
|
+
}),
|
|
57
|
+
],
|
|
58
|
+
});
|
|
58
59
|
```
|
|
59
60
|
|
|
60
61
|
### Set Up Mock Service Worker
|
|
@@ -65,6 +66,25 @@ If you plan to use API mocking, set up the mock service worker:
|
|
|
65
66
|
npx twd-js init public
|
|
66
67
|
```
|
|
67
68
|
|
|
69
|
+
### Non-Vite projects (Angular, Webpack, etc.)
|
|
70
|
+
|
|
71
|
+
If your project doesn't use Vite, initialize TWD manually in your dev entry point:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// Only load the test sidebar and tests in development mode
|
|
75
|
+
if (import.meta.env.DEV) {
|
|
76
|
+
const { initTWD } = await import('twd-js/bundled');
|
|
77
|
+
const tests = import.meta.glob('./**/*.twd.test.ts');
|
|
78
|
+
|
|
79
|
+
initTWD(tests, {
|
|
80
|
+
open: true,
|
|
81
|
+
position: 'left',
|
|
82
|
+
serviceWorker: true, // Enable request mocking (default: true)
|
|
83
|
+
serviceWorkerUrl: '/mock-sw.js', // Custom service worker path (default: '/mock-sw.js')
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
68
88
|
Check the [Framework Integration Guide](https://brikev.github.io/twd/frameworks) for more details.
|
|
69
89
|
|
|
70
90
|
## Writing Tests
|
|
@@ -101,7 +121,7 @@ describe("Hello World Page", () => {
|
|
|
101
121
|
3. **Run your app** - The TWD sidebar will appear automatically in development mode!
|
|
102
122
|
|
|
103
123
|
<p align="center">
|
|
104
|
-
<img src="
|
|
124
|
+
<img src="https://raw.githubusercontent.com/BRIKEV/twd/main/images/twd_side_bar_success.png" alt="TWD Sidebar in action" width="800">
|
|
105
125
|
</p>
|
|
106
126
|
|
|
107
127
|
## Key Concepts
|
|
@@ -155,7 +175,7 @@ Full documentation is available at [twd.dev](https://twd.dev) (coming soon) or i
|
|
|
155
175
|
|
|
156
176
|
Check out our working examples for various frameworks:
|
|
157
177
|
|
|
158
|
-
- **[Examples Directory](
|
|
178
|
+
- **[Examples Directory](https://github.com/BRIKEV/twd/tree/main/examples)** - Local examples for React, Vue, and Astro
|
|
159
179
|
- **[Vue Example](https://github.com/BRIKEV/twd-vue-example)** - Vue 3 with advanced scenarios
|
|
160
180
|
- **[Solid Example](https://github.com/BRIKEV/twd-solid-example)** - Solid.js integration
|
|
161
181
|
- **[Angular Example](https://github.com/BRIKEV/twd-angular-example)** - Angular setup
|
package/dist/bundled.es.js
CHANGED
|
@@ -1289,7 +1289,7 @@ var Jn = (e) => {
|
|
|
1289
1289
|
}
|
|
1290
1290
|
};
|
|
1291
1291
|
window.__testRunner = Gr;
|
|
1292
|
-
var $t = "1.
|
|
1292
|
+
var $t = "1.8.0-beta.1", Zn = () => typeof window < "u" ? (window.__TWD_MOCK_STATE__ || (window.__TWD_MOCK_STATE__ = {
|
|
1293
1293
|
rules: [],
|
|
1294
1294
|
counts: {}
|
|
1295
1295
|
}), window.__TWD_MOCK_STATE__) : {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const TWD_VERSION = "1.
|
|
1
|
+
export declare const TWD_VERSION = "1.8.0-beta.1";
|
package/dist/index.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});var Rl=Object.create,lr=Object.defineProperty,Tl=Object.getOwnPropertyDescriptor,Sl=Object.getOwnPropertyNames,xl=Object.getPrototypeOf,Ol=Object.prototype.hasOwnProperty,P=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),Ml=(e,t)=>{let r={};for(var n in e)lr(r,n,{get:e[n],enumerable:!0});return t||lr(r,Symbol.toStringTag,{value:"Module"}),r},Al=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(var a=Sl(t),o=0,i=a.length,s;o<i;o++)s=a[o],!Ol.call(e,s)&&s!==r&&lr(e,s,{get:(u=>t[u]).bind(null,s),enumerable:!(n=Tl(t,s))||n.enumerable});return e},In=(e,t,r)=>(r=e!=null?Rl(xl(e)):{},Al(t||!e||!e.__esModule?lr(r,"default",{value:e,enumerable:!0}):r,e));const Mt=require("./runner-Dr_mUWED.cjs"),tn=require("./componentMocks-CwMyWCu5.cjs");let oe=require("react"),j=require("react/jsx-runtime");var Il=(e,t,r=2e3,n=50)=>new Promise((a,o)=>{const i=Date.now(),s=()=>{const u=t();if(u)return a(u);if(Date.now()-i>r)return o(new Error(`Timeout waiting for element ${e}`));setTimeout(s,n)};s()}),Nl=(e,t,r=2e3,n=50)=>new Promise((a,o)=>{const i=Date.now(),s=()=>{const u=t();if(u&&u.length>0)return a(Array.from(u));if(Date.now()-i>r)return o(new Error(`Timeout waiting for elements ${e}`));setTimeout(s,n)};s()}),ur=e=>new Promise(t=>setTimeout(t,e)),te=e=>{const t=Mt.handlers.size?Array.from(Mt.handlers.values()).find(r=>r.status==="running"):null;t&&t.logs?.push(e)},kl=(e,t)=>{const r=t?.timeout??2e3,n=t?.interval??50,a=t?.message;return new Promise((o,i)=>{const s=Date.now();let u=!1;const l=async()=>{try{const c=await e();if(u)return;u=!0,te(a?`waitFor: resolved (${a})`:"waitFor: resolved"),o(c)}catch(c){if(u)return;const d=c instanceof Error?c:new Error(String(c));if(Date.now()-s>=r){u=!0;const p=`waitFor timed out after ${r}ms`,f=a?` waiting for: ${a}`:"",g=`
|
|
2
|
-
Last error: ${d.message}`;i(new Error(`${p}${f}.${g}`));return}setTimeout(l,n)}};l()})},rn=(e,t,r,n)=>{if(!e&&!t)throw new Error(n);if(e&&t)throw new Error(n.replace("to be","to not be").replace("to have","to not have").replace("to contain","to not contain"));return r};function ka(e){if(!e.isConnected)return!1;let t=e;for(;t;){const r=getComputedStyle(t);if(r.display==="none"||r.visibility==="hidden"||r.visibility==="collapse")return!1;t=t.parentElement}return!0}var jl={"have.text":{positive:{pass:e=>`Assertion passed: Text is exactly "${e[0]}"`,fail:(e,t)=>`Assertion failed: Expected text to be "${e[0]}", but got "${t}"`},negative:{pass:e=>`Assertion passed: Text is not exactly "${e[0]}"`,fail:(e,t)=>`Assertion failed: Expected text to not be "${e[0]}", but got "${t}"`}},"contain.text":{positive:{pass:e=>`Assertion passed: Text contains "${e[0]}"`,fail:(e,t)=>`Assertion failed: Expected text to contain "${e[0]}", but got "${t}"`},negative:{pass:e=>`Assertion passed: Text does not contain "${e[0]}"`,fail:(e,t)=>`Assertion failed: Expected text to not contain "${e[0]}", but got "${t}"`}},"be.empty":{positive:{pass:()=>"Assertion passed: Text is empty",fail:(e,t)=>`Assertion failed: Expected text to be empty, but got "${t}"`},negative:{pass:()=>"Assertion passed: Text is not empty",fail:(e,t)=>`Assertion failed: Expected text to not be empty, but got "${t}"`}},"have.attr":{positive:{pass:e=>`Assertion passed: Attribute "${e[0]}" is "${e[1]}"`,fail:(e,t)=>`Assertion failed: Expected attribute "${e[0]}" to be "${e[1]}", but got "${t}"`},negative:{pass:e=>`Assertion passed: Attribute "${e[0]}" is not "${e[1]}"`,fail:(e,t)=>`Assertion failed: Expected attribute "${e[0]}" to not be "${e[1]}", but got "${t}"`}},"have.value":{positive:{pass:e=>`Assertion passed: Value is "${e[0]}"`,fail:(e,t)=>`Assertion failed: Expected value to be "${e[0]}", but got "${t}"`},negative:{pass:e=>`Assertion passed: Value is not "${e[0]}"`,fail:(e,t)=>`Assertion failed: Expected value to not be "${e[0]}", but got "${t}"`}},"be.disabled":{positive:{pass:()=>"Assertion passed: Element is disabled",fail:()=>"Assertion failed: Expected element to be disabled"},negative:{pass:()=>"Assertion passed: Element is not disabled",fail:()=>"Assertion failed: Expected element to not be disabled"}},"be.enabled":{positive:{pass:()=>"Assertion passed: Element is enabled",fail:()=>"Assertion failed: Expected element to be enabled"},negative:{pass:()=>"Assertion passed: Element is not enabled",fail:()=>"Assertion failed: Expected element to not be enabled"}},"be.checked":{positive:{pass:()=>"Assertion passed: Element is checked",fail:()=>"Assertion failed: Expected element to be checked"},negative:{pass:()=>"Assertion passed: Element is not checked",fail:()=>"Assertion failed: Expected element to not be checked"}},"be.selected":{positive:{pass:()=>"Assertion passed: Element is selected",fail:()=>"Assertion failed: Expected element to be selected"},negative:{pass:()=>"Assertion passed: Element is not selected",fail:()=>"Assertion failed: Expected element to not be selected"}},"be.focused":{positive:{pass:()=>"Assertion passed: Element is focused",fail:()=>"Assertion failed: Expected element to be focused"},negative:{pass:()=>"Assertion passed: Element is not focused",fail:()=>"Assertion failed: Expected element to not be focused"}},"be.visible":{positive:{pass:()=>"Assertion passed: Element is visible",fail:()=>"Assertion failed: Expected element to be visible"},negative:{pass:()=>"Assertion passed: Element is not visible",fail:()=>"Assertion failed: Expected element to not be visible"}},"be.hidden":{positive:{pass:()=>"Assertion passed: Element is hidden",fail:()=>"Assertion failed: Expected element to be hidden"},negative:{pass:()=>"Assertion passed: Element is not hidden",fail:()=>"Assertion failed: Expected element to not be hidden"}},"have.class":{positive:{pass:e=>`Assertion passed: Element has class "${e[0]}"`,fail:e=>`Assertion failed: Expected element to have class "${e[0]}"`},negative:{pass:e=>`Assertion passed: Element does not have class "${e[0]}"`,fail:e=>`Assertion failed: Expected element to not have class "${e[0]}"`}}},Vr=(e,t,...r)=>{const n=t.startsWith("not."),a=n?t.slice(4):t,o=(e.textContent||"").trim(),i=jl[a];if(!i)throw new Error(`Unknown assertion: ${a}`);const s=n?"negative":"positive";let u,l;switch(a){case"have.text":u=o===r[0],l=o;break;case"contain.text":u=o.includes(r[0]),l=o;break;case"be.empty":u=o.length===0,l=o;break;case"have.attr":l=e.getAttribute(r[0]),u=l===r[1];break;case"have.value":l=e.value,u=l===r[0];break;case"be.disabled":u=e.disabled===!0;break;case"be.enabled":u=e.disabled===!1;break;case"be.checked":u=e.checked===!0;break;case"be.selected":u=e.selected===!0;break;case"be.focused":u=document.activeElement===e;break;case"be.visible":u=ka(e);break;case"be.hidden":u=!ka(e);break;case"have.class":u=e.classList.contains(r[0]);break;default:throw new Error(`Unknown assertion: ${String(a)}`)}return rn(u,n,i[s].pass(r),i[s].fail(r,l))},Tr="1.
|
|
2
|
+
Last error: ${d.message}`;i(new Error(`${p}${f}.${g}`));return}setTimeout(l,n)}};l()})},rn=(e,t,r,n)=>{if(!e&&!t)throw new Error(n);if(e&&t)throw new Error(n.replace("to be","to not be").replace("to have","to not have").replace("to contain","to not contain"));return r};function ka(e){if(!e.isConnected)return!1;let t=e;for(;t;){const r=getComputedStyle(t);if(r.display==="none"||r.visibility==="hidden"||r.visibility==="collapse")return!1;t=t.parentElement}return!0}var jl={"have.text":{positive:{pass:e=>`Assertion passed: Text is exactly "${e[0]}"`,fail:(e,t)=>`Assertion failed: Expected text to be "${e[0]}", but got "${t}"`},negative:{pass:e=>`Assertion passed: Text is not exactly "${e[0]}"`,fail:(e,t)=>`Assertion failed: Expected text to not be "${e[0]}", but got "${t}"`}},"contain.text":{positive:{pass:e=>`Assertion passed: Text contains "${e[0]}"`,fail:(e,t)=>`Assertion failed: Expected text to contain "${e[0]}", but got "${t}"`},negative:{pass:e=>`Assertion passed: Text does not contain "${e[0]}"`,fail:(e,t)=>`Assertion failed: Expected text to not contain "${e[0]}", but got "${t}"`}},"be.empty":{positive:{pass:()=>"Assertion passed: Text is empty",fail:(e,t)=>`Assertion failed: Expected text to be empty, but got "${t}"`},negative:{pass:()=>"Assertion passed: Text is not empty",fail:(e,t)=>`Assertion failed: Expected text to not be empty, but got "${t}"`}},"have.attr":{positive:{pass:e=>`Assertion passed: Attribute "${e[0]}" is "${e[1]}"`,fail:(e,t)=>`Assertion failed: Expected attribute "${e[0]}" to be "${e[1]}", but got "${t}"`},negative:{pass:e=>`Assertion passed: Attribute "${e[0]}" is not "${e[1]}"`,fail:(e,t)=>`Assertion failed: Expected attribute "${e[0]}" to not be "${e[1]}", but got "${t}"`}},"have.value":{positive:{pass:e=>`Assertion passed: Value is "${e[0]}"`,fail:(e,t)=>`Assertion failed: Expected value to be "${e[0]}", but got "${t}"`},negative:{pass:e=>`Assertion passed: Value is not "${e[0]}"`,fail:(e,t)=>`Assertion failed: Expected value to not be "${e[0]}", but got "${t}"`}},"be.disabled":{positive:{pass:()=>"Assertion passed: Element is disabled",fail:()=>"Assertion failed: Expected element to be disabled"},negative:{pass:()=>"Assertion passed: Element is not disabled",fail:()=>"Assertion failed: Expected element to not be disabled"}},"be.enabled":{positive:{pass:()=>"Assertion passed: Element is enabled",fail:()=>"Assertion failed: Expected element to be enabled"},negative:{pass:()=>"Assertion passed: Element is not enabled",fail:()=>"Assertion failed: Expected element to not be enabled"}},"be.checked":{positive:{pass:()=>"Assertion passed: Element is checked",fail:()=>"Assertion failed: Expected element to be checked"},negative:{pass:()=>"Assertion passed: Element is not checked",fail:()=>"Assertion failed: Expected element to not be checked"}},"be.selected":{positive:{pass:()=>"Assertion passed: Element is selected",fail:()=>"Assertion failed: Expected element to be selected"},negative:{pass:()=>"Assertion passed: Element is not selected",fail:()=>"Assertion failed: Expected element to not be selected"}},"be.focused":{positive:{pass:()=>"Assertion passed: Element is focused",fail:()=>"Assertion failed: Expected element to be focused"},negative:{pass:()=>"Assertion passed: Element is not focused",fail:()=>"Assertion failed: Expected element to not be focused"}},"be.visible":{positive:{pass:()=>"Assertion passed: Element is visible",fail:()=>"Assertion failed: Expected element to be visible"},negative:{pass:()=>"Assertion passed: Element is not visible",fail:()=>"Assertion failed: Expected element to not be visible"}},"be.hidden":{positive:{pass:()=>"Assertion passed: Element is hidden",fail:()=>"Assertion failed: Expected element to be hidden"},negative:{pass:()=>"Assertion passed: Element is not hidden",fail:()=>"Assertion failed: Expected element to not be hidden"}},"have.class":{positive:{pass:e=>`Assertion passed: Element has class "${e[0]}"`,fail:e=>`Assertion failed: Expected element to have class "${e[0]}"`},negative:{pass:e=>`Assertion passed: Element does not have class "${e[0]}"`,fail:e=>`Assertion failed: Expected element to not have class "${e[0]}"`}}},Vr=(e,t,...r)=>{const n=t.startsWith("not."),a=n?t.slice(4):t,o=(e.textContent||"").trim(),i=jl[a];if(!i)throw new Error(`Unknown assertion: ${a}`);const s=n?"negative":"positive";let u,l;switch(a){case"have.text":u=o===r[0],l=o;break;case"contain.text":u=o.includes(r[0]),l=o;break;case"be.empty":u=o.length===0,l=o;break;case"have.attr":l=e.getAttribute(r[0]),u=l===r[1];break;case"have.value":l=e.value,u=l===r[0];break;case"be.disabled":u=e.disabled===!0;break;case"be.enabled":u=e.disabled===!1;break;case"be.checked":u=e.checked===!0;break;case"be.selected":u=e.selected===!0;break;case"be.focused":u=document.activeElement===e;break;case"be.visible":u=ka(e);break;case"be.hidden":u=!ka(e);break;case"have.class":u=e.classList.contains(r[0]);break;default:throw new Error(`Unknown assertion: ${String(a)}`)}return rn(u,n,i[s].pass(r),i[s].fail(r,l))},Tr="1.8.0-beta.1",Dl=()=>typeof window<"u"?(window.__TWD_MOCK_STATE__||(window.__TWD_MOCK_STATE__={rules:[],counts:{}}),window.__TWD_MOCK_STATE__):{rules:[],counts:{}},tt=Dl(),Ce=tt.rules,Ll=100,ja=!1,Bl=async e=>{if(ja){console.warn("[TWD] Request mocking already initialized");return}if("serviceWorker"in navigator){ja=!0;const t=e??"/mock-sw.js";await navigator.serviceWorker.register(`${t}?v=${Tr}`),navigator.serviceWorker.controller||await new Promise(r=>{navigator.serviceWorker.addEventListener("controllerchange",r,{once:!0})}),navigator.serviceWorker.addEventListener("message",r=>{if(r.data?.type==="EXECUTED"){const{alias:n,request:a,hitCount:o}=r.data,i=Ce.find(s=>s.alias===n);i&&(i.executed=!0,i.request=a,i.hitCount=o),tt.counts[n]=o??(tt.counts[n]??0)+1}})}},$l=async(e,t)=>{const r={alias:e,...t,executed:!1},n=Ce.findIndex(a=>a.alias===e);if(n!==-1?Ce[n]=r:Ce.push(r),typeof window.__twdCollectMock=="function"){const a=window.__TWD_STATE__?.handlers,o=a&&[...a.values()].find(i=>i.status==="running");o&&window.__twdCollectMock({alias:e,url:String(t.url),method:t.method,status:t.status||200,response:t.response,responseHeaders:t.responseHeaders,urlRegex:t.urlRegex||!1,testId:o.id})}navigator.serviceWorker.controller?.postMessage({type:"ADD_RULE",rule:r,version:Tr}),await ur(Ll),await Promise.resolve()},Fl=async e=>await Promise.all(e.map(t=>So(t))),So=async(e,t=10,r=100)=>{const n=Ce.find(u=>u.alias===e);if(!n)throw new Error(`Rule ${e} not found`);for(let u=0;u<t;u++){const l=Ce.find(c=>c.alias===e&&c.executed);if(l)return Promise.resolve(l);u<t-1&&await new Promise(c=>setTimeout(c,r))}const a=t*r,o=Ce.filter(u=>u.executed).map(u=>`${u.alias} (${u.method} ${u.url})`).join(", "),i=Ce.filter(u=>!u.executed).map(u=>`${u.alias} (${u.method} ${u.url})`).join(", "),s=[`Rule "${e}" was not executed within ${a}ms.`,` Expected: ${n.method} ${n.url}`,` Executed rules: ${o||"none"}`,` Not executed rules: ${i||"none"}`].join(`
|
|
3
3
|
`);throw console.log(s),new Error(s)},xo=()=>Ce,Oo=()=>{navigator.serviceWorker.controller?.postMessage({type:"CLEAR_RULES",version:Tr}),Ce.length=0;for(const e in tt.counts)delete tt.counts[e]},Ul=e=>tt.counts[e]??0,zl=()=>({...tt.counts}),Vl=(e,t)=>{const r=e.startsWith("not.");switch(r?e.slice(4):e){case"eq":return rn(window.location.href===t,r,`Assertion passed: URL is ${t}`,`Assertion failed: Expected URL to be ${t}, but got ${window.location.href}`);case"contain.url":return rn(window.location.href.includes(t),r,`Assertion passed: URL contains ${t}`,`Assertion failed: Expected URL to contain ${t}, but got ${window.location.href}`);default:throw new Error(`Unknown assertion: ${e}`)}},Hl=()=>({location:window.location,should:async(e,t,r=5)=>{let n="",a;for(let o=0;o<r;o++)try{n=Vl(e,t),te(n);break}catch(i){await new Promise(s=>setTimeout(s,100)),a=i}if(a)throw a;return n}}),Wl=100,Kl=async(e,t)=>{if(te(`visit("${e}")`),window.location.pathname===e||t){const r=`/__dummy_${Math.random().toString(36).slice(2)}`;window.history.pushState({},"",r),window.dispatchEvent(new PopStateEvent("popstate")),await ur(10)}window.history.pushState({},"",e),window.dispatchEvent(new PopStateEvent("popstate")),await ur(Wl)},nn="twd-viewport-styles",an="twd-viewport-iframe",fe=null,ut=null,Gl=()=>{if(fe)return;const{style:e}=document.body;fe={maxWidth:e.maxWidth,maxHeight:e.maxHeight,minHeight:e.minHeight,overflow:e.overflow,margin:e.margin,boxSizing:e.boxSizing,boxShadow:e.boxShadow}},Yl=(e,t)=>{let r=document.getElementById(nn);r||(r=document.createElement("style"),r.id=nn,document.head.appendChild(r));const n=t?`${e} × ${t}`:`${e}`;r.textContent=`
|
|
4
4
|
#twd-viewport-badge {
|
|
5
5
|
position: fixed;
|
package/dist/index.es.js
CHANGED
|
@@ -260,7 +260,7 @@ var Fl = {
|
|
|
260
260
|
throw new Error(`Unknown assertion: ${String(a)}`);
|
|
261
261
|
}
|
|
262
262
|
return on(u, n, i[s].pass(r), i[s].fail(r, l));
|
|
263
|
-
}, Mr = "1.
|
|
263
|
+
}, Mr = "1.8.0-beta.1", Ul = () => typeof window < "u" ? (window.__TWD_MOCK_STATE__ || (window.__TWD_MOCK_STATE__ = {
|
|
264
264
|
rules: [],
|
|
265
265
|
counts: {}
|
|
266
266
|
}), window.__TWD_MOCK_STATE__) : {
|
package/dist/mock-sw.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(){var d=t=>{try{return new RegExp(t),!0}catch{return!1}},f=new Set(["js","mjs","cjs","ts","tsx","jsx","mts","cts","css","scss","sass","less","styl","html","htm","xml","xhtml","vue","svelte","json","yaml","yml","toml","csv","txt","md","mdx","pdf","doc","docx","png","jpg","jpeg","gif","svg","webp","ico","bmp","avif","woff","woff2","ttf","eot","otf","mp3","mp4","webm","ogg","wav","zip","tar","gz","rar","map"]),c=t=>{const s=t.split("?")[0].match(/\.([a-zA-Z0-9]+)$/);return s?f.has(s[1].toLowerCase()):!1},h=(t,s)=>{const i=t.indexOf(s);if(i===-1)return!1;const e=i+s.length,a=t[e];if(a===void 0)return!0;const n=t.indexOf("?");return n!==-1&&e>n?!0:"?#&".includes(a)};function m(t,s,i){return i.find(e=>{const a=e.method.toLowerCase()===t.toLowerCase();if(e.urlRegex&&d(e.url)){const l=new RegExp(e.url);return a&&l.test(s)}if(c(e.url))return a&&s.includes(e.url);const n=e.url===s||h(s,e.url);return a&&n&&!c(s)})}function p(t,s,i,e){t.forEach(a=>a.postMessage({type:"EXECUTED",alias:s.alias,request:i,hitCount:e}))}var y=(t,s,i)=>{const e=![204,205,304].includes(s),a=e?JSON.stringify(t):null;return new Response(a,{status:s,headers:e?i||{"Content-Type":"application/json"}:i||{}})},u="1.
|
|
1
|
+
(function(){var d=t=>{try{return new RegExp(t),!0}catch{return!1}},f=new Set(["js","mjs","cjs","ts","tsx","jsx","mts","cts","css","scss","sass","less","styl","html","htm","xml","xhtml","vue","svelte","json","yaml","yml","toml","csv","txt","md","mdx","pdf","doc","docx","png","jpg","jpeg","gif","svg","webp","ico","bmp","avif","woff","woff2","ttf","eot","otf","mp3","mp4","webm","ogg","wav","zip","tar","gz","rar","map"]),c=t=>{const s=t.split("?")[0].match(/\.([a-zA-Z0-9]+)$/);return s?f.has(s[1].toLowerCase()):!1},h=(t,s)=>{const i=t.indexOf(s);if(i===-1)return!1;const e=i+s.length,a=t[e];if(a===void 0)return!0;const n=t.indexOf("?");return n!==-1&&e>n?!0:"?#&".includes(a)};function m(t,s,i){return i.find(e=>{const a=e.method.toLowerCase()===t.toLowerCase();if(e.urlRegex&&d(e.url)){const l=new RegExp(e.url);return a&&l.test(s)}if(c(e.url))return a&&s.includes(e.url);const n=e.url===s||h(s,e.url);return a&&n&&!c(s)})}function p(t,s,i,e){t.forEach(a=>a.postMessage({type:"EXECUTED",alias:s.alias,request:i,hitCount:e}))}var y=(t,s,i)=>{const e=![204,205,304].includes(s),a=e?JSON.stringify(t):null;return new Response(a,{status:s,headers:e?i||{"Content-Type":"application/json"}:i||{}})},u="1.8.0-beta.1",r=[],o={},g=async t=>{const{method:s}=t.request,i=t.request.url,e=m(s,i,r);e&&(console.log("[TWD] Mock hit:",e.alias,s,i),t.respondWith((async()=>{let a=null;const n=t.request.headers.get("content-type")||"application/json";if(n.includes("application/json"))try{a=await t.request.clone().json()}catch{}else if(n.includes("form"))try{const l=await t.request.clone().formData();a={},l.forEach((x,E)=>{a[E]=x})}catch{}else if(n.includes("text"))try{a=await t.request.clone().text()}catch{}else if(n.includes("octet-stream"))try{a=await t.request.clone().arrayBuffer()}catch{}else if(n.includes("image"))try{a=await t.request.clone().blob()}catch{}else try{a=await t.request.clone().text()}catch{}return o[e.alias]||(o[e.alias]=0),o[e.alias]++,p(await self.clients.matchAll(),e,a,o[e.alias]),e.delay&&e.delay>0&&await new Promise(l=>setTimeout(l,e.delay)),y(e.response,e.status??200,e.responseHeaders)})()))},w=t=>{t!=="1.8.0-beta.1"&&console.warn(`[TWD] ⚠️ Version mismatch detected:
|
|
2
2
|
Client version: ${t}
|
|
3
3
|
Service Worker version: ${u}
|
|
4
4
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
import { TWDTheme } from '../ui/utils/theme';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the TWD Vite plugin.
|
|
5
|
+
*/
|
|
6
|
+
export interface TwdPluginOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Glob pattern used to discover TWD test files via `import.meta.glob`.
|
|
9
|
+
* @default "/**\/*.twd.test.ts"
|
|
10
|
+
*/
|
|
11
|
+
testFilePattern?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Whether the TWD sidebar starts open.
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
open?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Side of the viewport the sidebar is anchored to.
|
|
19
|
+
* @default "left"
|
|
20
|
+
*/
|
|
21
|
+
position?: 'left' | 'right';
|
|
22
|
+
/**
|
|
23
|
+
* Whether to register the Mock Service Worker for request mocking.
|
|
24
|
+
* @default true
|
|
25
|
+
*/
|
|
26
|
+
serviceWorker?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* URL of the Mock Service Worker script served by the app.
|
|
29
|
+
* @default "/mock-sw.js"
|
|
30
|
+
*/
|
|
31
|
+
serviceWorkerUrl?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Optional theme overrides for the sidebar UI.
|
|
34
|
+
*/
|
|
35
|
+
theme?: Partial<TWDTheme>;
|
|
36
|
+
/**
|
|
37
|
+
* Whether to show the search input in the sidebar.
|
|
38
|
+
*/
|
|
39
|
+
search?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* CSS selector for the host element used to mount the sidebar.
|
|
42
|
+
*/
|
|
43
|
+
rootSelector?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Vite plugin that auto-wires TWD into a dev server.
|
|
47
|
+
* It exposes a virtual module that calls `initTWD` with a glob of test files
|
|
48
|
+
* and injects a `<script type="module">` tag into `index.html` via
|
|
49
|
+
* `transformIndexHtml`, so apps don't need a manual TWD entry point.
|
|
50
|
+
*
|
|
51
|
+
* This plugin only runs in development mode (serve) and does not affect Vitest test runs.
|
|
52
|
+
*
|
|
53
|
+
* @param options - Configuration options for the plugin
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { twd } from 'twd-js/vite-plugin';
|
|
57
|
+
*
|
|
58
|
+
* export default defineConfig({
|
|
59
|
+
* plugins: [
|
|
60
|
+
* // ... other plugins
|
|
61
|
+
* twd(),
|
|
62
|
+
* ],
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* // Custom options
|
|
68
|
+
* twd({
|
|
69
|
+
* testFilePattern: '/src/**\/*.twd.test.tsx',
|
|
70
|
+
* position: 'right',
|
|
71
|
+
* open: false,
|
|
72
|
+
* })
|
|
73
|
+
* ```
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* // Disable the Mock Service Worker (e.g. when the project doesn't use request mocking)
|
|
77
|
+
* twd({
|
|
78
|
+
* serviceWorker: false,
|
|
79
|
+
* })
|
|
80
|
+
*
|
|
81
|
+
* // Or point to a custom service worker path
|
|
82
|
+
* twd({
|
|
83
|
+
* serviceWorkerUrl: '/custom/mock-sw.js',
|
|
84
|
+
* })
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export declare function twd(options?: TwdPluginOptions): Plugin;
|
package/dist/vite-plugin.cjs.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const T=require("./index.cjs.js");let m=require("fs"),v=require("path");function p(){return{name:"remove-mock-sw",apply:"build",closeBundle(){try{(0,m.rmSync)((0,v.resolve)("dist/mock-sw.js")),console.log("🧹 Removed mock-sw.js from build")}catch{console.log("🧹 No mock-sw.js found in build")}}}}function f(i={}){const{testFilePattern:e=".twd.test.ts"}=i,o=typeof e=="function"?e:t=>t.endsWith(e);return{name:"twd-hmr",apply:"serve",handleHotUpdate({file:t,server:n}){if(o(t))return n.ws.send({type:"full-reload",path:"*"}),[]}}}var c="virtual:twd/init",d=`\0${c}`,w="/**/*.twd.test.ts",u={open:!0,position:"left",serviceWorker:!0,serviceWorkerUrl:"/mock-sw.js"};function k(i={}){const{testFilePattern:e=w,...o}=i,t=o.serviceWorkerUrl!==void 0,n={...u,...o};let s="/";return{name:"twd",apply:"serve",configResolved(r){if(s=r.base,!t&&s!=="/"){const l=u.serviceWorkerUrl.replace(/^\//,"");n.serviceWorkerUrl=`${s}${l}`}},resolveId(r){return r===c?d:null},load(r){if(r!==d)return null;const l=JSON.stringify(e),a=JSON.stringify(n);return["import { initTWD } from 'twd-js/bundled';",`const tests = import.meta.glob(${l});`,`initTWD(tests, ${a});`].join(`
|
|
2
|
+
`)},transformIndexHtml(){return[{tag:"script",attrs:{type:"module",src:`${s}@id/${c}`},injectTo:"head"}]}}}exports.removeMockServiceWorker=p;exports.twd=k;exports.twdHmr=f;
|
package/dist/vite-plugin.d.ts
CHANGED
package/dist/vite-plugin.es.js
CHANGED
|
@@ -1,33 +1,80 @@
|
|
|
1
|
-
import { rmSync as
|
|
2
|
-
import { resolve as
|
|
3
|
-
function
|
|
1
|
+
import { rmSync as u } from "fs";
|
|
2
|
+
import { resolve as p } from "path";
|
|
3
|
+
function k() {
|
|
4
4
|
return {
|
|
5
5
|
name: "remove-mock-sw",
|
|
6
6
|
apply: "build",
|
|
7
7
|
closeBundle() {
|
|
8
8
|
try {
|
|
9
|
-
|
|
9
|
+
u(p("dist/mock-sw.js")), console.log("🧹 Removed mock-sw.js from build");
|
|
10
10
|
} catch {
|
|
11
11
|
console.log("🧹 No mock-sw.js found in build");
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
const { testFilePattern: e = ".twd.test.ts" } =
|
|
16
|
+
function T(i = {}) {
|
|
17
|
+
const { testFilePattern: e = ".twd.test.ts" } = i, o = typeof e == "function" ? e : (t) => t.endsWith(e);
|
|
18
18
|
return {
|
|
19
19
|
name: "twd-hmr",
|
|
20
20
|
apply: "serve",
|
|
21
|
-
handleHotUpdate({ file:
|
|
22
|
-
if (
|
|
23
|
-
return
|
|
21
|
+
handleHotUpdate({ file: t, server: s }) {
|
|
22
|
+
if (o(t))
|
|
23
|
+
return s.ws.send({
|
|
24
24
|
type: "full-reload",
|
|
25
25
|
path: "*"
|
|
26
26
|
}), [];
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
var c = "virtual:twd/init", d = `\0${c}`, v = "/**/*.twd.test.ts", a = {
|
|
31
|
+
open: !0,
|
|
32
|
+
position: "left",
|
|
33
|
+
serviceWorker: !0,
|
|
34
|
+
serviceWorkerUrl: "/mock-sw.js"
|
|
35
|
+
};
|
|
36
|
+
function y(i = {}) {
|
|
37
|
+
const { testFilePattern: e = v, ...o } = i, t = o.serviceWorkerUrl !== void 0, s = {
|
|
38
|
+
...a,
|
|
39
|
+
...o
|
|
40
|
+
};
|
|
41
|
+
let n = "/";
|
|
42
|
+
return {
|
|
43
|
+
name: "twd",
|
|
44
|
+
apply: "serve",
|
|
45
|
+
configResolved(r) {
|
|
46
|
+
if (n = r.base, !t && n !== "/") {
|
|
47
|
+
const l = a.serviceWorkerUrl.replace(/^\//, "");
|
|
48
|
+
s.serviceWorkerUrl = `${n}${l}`;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
resolveId(r) {
|
|
52
|
+
return r === c ? d : null;
|
|
53
|
+
},
|
|
54
|
+
load(r) {
|
|
55
|
+
if (r !== d) return null;
|
|
56
|
+
const l = JSON.stringify(e), m = JSON.stringify(s);
|
|
57
|
+
return [
|
|
58
|
+
"import { initTWD } from 'twd-js/bundled';",
|
|
59
|
+
`const tests = import.meta.glob(${l});`,
|
|
60
|
+
`initTWD(tests, ${m});`
|
|
61
|
+
].join(`
|
|
62
|
+
`);
|
|
63
|
+
},
|
|
64
|
+
transformIndexHtml() {
|
|
65
|
+
return [{
|
|
66
|
+
tag: "script",
|
|
67
|
+
attrs: {
|
|
68
|
+
type: "module",
|
|
69
|
+
src: `${n}@id/${c}`
|
|
70
|
+
},
|
|
71
|
+
injectTo: "head"
|
|
72
|
+
}];
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
30
76
|
export {
|
|
31
|
-
|
|
32
|
-
|
|
77
|
+
k as removeMockServiceWorker,
|
|
78
|
+
y as twd,
|
|
79
|
+
T as twdHmr
|
|
33
80
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "twd-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0-beta.1",
|
|
4
4
|
"description": "Test While Developing (TWD) - in-browser testing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"react-dom": ">=17.0.0"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
-
"@eslint/js": "^
|
|
97
|
+
"@eslint/js": "^10.0.1",
|
|
98
98
|
"@preact/preset-vite": "^2.10.5",
|
|
99
99
|
"@testing-library/jest-dom": "^6.9.1",
|
|
100
100
|
"@testing-library/react": "^16.3.2",
|
|
@@ -104,15 +104,15 @@
|
|
|
104
104
|
"@vitest/coverage-v8": "^4.1.5",
|
|
105
105
|
"@vitest/eslint-plugin": "^1.6.16",
|
|
106
106
|
"conventional-changelog": "^7.2.0",
|
|
107
|
-
"eslint": "^
|
|
107
|
+
"eslint": "^10.3.0",
|
|
108
108
|
"eslint-config-prettier": "^10.1.8",
|
|
109
109
|
"globals": "^17.6.0",
|
|
110
110
|
"husky": "^9.1.7",
|
|
111
111
|
"jsdom": "^29.1.1",
|
|
112
|
-
"lint-staged": "^
|
|
112
|
+
"lint-staged": "^17.0.2",
|
|
113
113
|
"prettier": "^3.8.3",
|
|
114
114
|
"typescript": "~5.9.3",
|
|
115
|
-
"typescript-eslint": "^8.59.
|
|
115
|
+
"typescript-eslint": "^8.59.2",
|
|
116
116
|
"vite": "^8.0.10",
|
|
117
117
|
"vite-plugin-dts": "^5.0.0",
|
|
118
118
|
"vitepress": "^2.0.0-alpha.12",
|