Skip to content

Commit b73134f

Browse files
committed
feat: add tutorial examples
1 parent 1a3d274 commit b73134f

9 files changed

Lines changed: 428 additions & 0 deletions

File tree

.cursor/rules/twd.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# TWD (Test While Developing) Context
2+
3+
## Overview
4+
TWD is an in-browser test runner. Tests run in the browser (not Node.js). Syntax is similar to Jest/Cypress.
5+
6+
## Core Rules
7+
8+
1. Imports — use in every TWD test file:
9+
import { twd, userEvent, screenDom, expect } from "twd-js";
10+
import { describe, it, beforeEach, afterEach } from "twd-js/runner";
11+
12+
2. File naming: *.twd.test.ts or *.twd.test.tsx
13+
14+
3. Async/Await:
15+
- twd.get() and twd.getAll() are async. Always await them.
16+
- userEvent methods (click, type) are async. Always await them.
17+
- Test functions passed to it() should be async.
18+
19+
4. Assertions:
20+
- Use .should(assertion, value) on elements from twd.get().
21+
- Common: "have.text", "contain.text", "be.visible", "have.value", "have.class", "be.disabled", "have.attr".
22+
- Use Chai expect for non-element assertions.
23+
24+
## Common Patterns
25+
26+
### Basic test structure
27+
import { twd, userEvent, screenDom, expect } from "twd-js";
28+
import { describe, it, beforeEach } from "twd-js/runner";
29+
30+
describe("Feature Name", () => {
31+
beforeEach(() => {
32+
twd.clearRequestMockRules();
33+
twd.clearComponentMocks();
34+
});
35+
it("should perform an action", async () => { /* test logic */ });
36+
});
37+
38+
### Selecting elements
39+
Preferred: screenDom (Testing Library)
40+
const heading = screenDom.getByRole("heading", { name: "Welcome" });
41+
const submitBtn = screenDom.getByRole("button", { name: "Submit" });
42+
const emailInput = screenDom.getByLabelText("Email Address");
43+
// For modals/portals: use screenDomGlobal instead
44+
45+
Fallback: twd.get() with CSS selectors
46+
const container = await twd.get(".custom-container");
47+
48+
### Interactions (userEvent)
49+
const user = userEvent.setup();
50+
await user.click(btn);
51+
await user.type(input, "text");
52+
// With twd.get(): use .el for raw DOM — await user.click(rawBtn.el);
53+
54+
### Navigation
55+
await twd.visit("/path");
56+
57+
### Assertions
58+
message.should("have.text", "Success");
59+
message.should("contain.text", "saved");
60+
message.should("be.visible"); message.should("not.be.visible");
61+
input.should("have.value", "test@example.com");
62+
message.should("have.class", "success-alert");
63+
button.should("be.disabled"); button.should("be.enabled");
64+
checkbox.should("be.checked");
65+
element.should("have.attr", "type", "submit");
66+
await twd.url().should("contain.url", "/dashboard");
67+
68+
### Mocking requests
69+
Define mocks BEFORE the action that triggers the request.
70+
await twd.mockRequest("getUser", { method: "GET", url: "/api/user", response: { id: 1, name: "John" }, status: 200 });
71+
await twd.waitForRequest("getUser");
72+
73+
### Component mocking
74+
// In component: wrap with <MockedComponent name="Chart"><Chart /></MockedComponent>
75+
// In test:
76+
twd.mockComponent("Chart", () => <div>Mocked</div>);
77+
78+
### Module stubbing (Sinon)
79+
Tests run in the browser, so use Sinon for stubs/spies.
80+
ESM constraint: named exports (export const foo = ...) are IMMUTABLE and CANNOT be stubbed.
81+
Solution: wrap in an object and export as default.
82+
83+
// hooks/useAuth.ts — CORRECT (stubbable)
84+
import { useAuth0 } from "@auth0/auth0-react";
85+
const useAuth = () => useAuth0();
86+
export default { useAuth };
87+
88+
// hooks/useAuth.ts — WRONG (not stubbable)
89+
export const useAuth = () => useAuth0();
90+
91+
// In test:
92+
import authSession from '../hooks/useAuth';
93+
import Sinon from 'sinon';
94+
Sinon.stub(authSession, 'useAuth').returns({ isAuthenticated: true, ... });
95+
// Clean up in beforeEach: Sinon.restore();
96+
97+
## Do's and Don'ts
98+
DO: await twd.get(); await userEvent actions; use .el when passing twd.get() result to userEvent.
99+
DO: Clear mocks in beforeEach: twd.clearRequestMockRules(); twd.clearComponentMocks();
100+
DO: Mock requests BEFORE twd.visit() or triggering the request.
101+
DON'T: use cy.get or cy.visit (not Cypress); use global describe/it — always import from "twd-js/runner".
102+
DON'T: assume Node.js (fs, path) is available — tests run in browser.
103+
DON'T: try to stub named exports (export const fn = ...) — ESM makes them immutable. Wrap in an object and export default.
104+
DO: use Sinon for module stubs/spies. Always call Sinon.restore() in beforeEach.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,5 @@ dist
137137
# Vite logs files
138138
vite.config.js.timestamp-*
139139
vite.config.ts.timestamp-*
140+
141+
public/mock-sw.js

package-lock.json

Lines changed: 160 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"globals": "^17.1.0",
4646
"jsdom": "^27.4.0",
4747
"npm-run-all": "^4.1.5",
48+
"twd-js": "^1.5.2",
4849
"typescript": "~5.9.3",
4950
"typescript-eslint": "^8.53.1",
5051
"vite": "^7.3.1",

src/main.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ import './index.css'
44
import { RouterProvider } from "react-router";
55
import router from './AppRoutes';
66

7+
// Only load the test sidebar and tests in development mode
8+
if (import.meta.env.DEV) {
9+
const { initTWD } = await import('twd-js/bundled');
10+
const tests = import.meta.glob("./**/*.twd.test.ts");
11+
12+
// Initialize TWD with tests and optional configuration
13+
// Request mocking is automatically initialized by default
14+
initTWD(tests, {
15+
open: false,
16+
position: 'left',
17+
serviceWorker: true, // Enable request mocking (default: true)
18+
serviceWorkerUrl: '/mock-sw.js' // Custom service worker path (default: '/mock-sw.js')
19+
});
20+
}
21+
722
createRoot(document.getElementById('root')!).render(
823
<StrictMode>
924
<RouterProvider router={router} />

src/pages/TodoList/TodoItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export default function TodoItem({ todo }: TodoItemProps) {
2828
variant="destructive"
2929
size="sm"
3030
disabled={isDeleting}
31+
aria-label={`Delete Todo ${todo.title}`}
3132
>
3233
{isDeleting ? "Deleting..." : "Delete"}
3334
</Button>

0 commit comments

Comments
 (0)