Skip to content

Commit cc63a67

Browse files
authored
Feat/add better UI (#23)
* feat: remove monkey patch console.log * feat: improve color scheme when dark mode * docs: update readme * feat: export chai expect and improve assertions * docs: add example with multiple mocks * feat: add prop to display or not the navbar * test: improve tests for twd sidebar * feat: add user-event proxy * test: add tests to proxy user event
1 parent 87eb23a commit cc63a67

13 files changed

Lines changed: 221 additions & 33 deletions

File tree

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,17 @@ pnpm add twd-js
8787
- With Vite:
8888

8989
```ts
90+
import { twd } from "twd-js";
9091
// src/loadTests.ts
91-
const modules = import.meta.glob("./**/*.twd.test.ts", { eager: true });
92+
import.meta.glob("./**/*.twd.test.ts", { eager: true });
93+
// Initialize request mocking once
94+
twd.initRequestMocking()
95+
.then(() => {
96+
console.log("Request mocking initialized");
97+
})
98+
.catch((err) => {
99+
console.error("Error initializing request mocking:", err);
100+
});
92101
// No need to export anything
93102
```
94103

@@ -135,7 +144,6 @@ Just call `await twd.initRequestMocking()` at the start of your test, then use `
135144

136145
```ts
137146
it("fetches a message", async () => {
138-
await twd.initRequestMocking();
139147
await twd.mockRequest("message", {
140148
method: "GET",
141149
url: "https://api.example.com/message",

examples/my-twd-app/public/mock-sw.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
// This automatically imports all files ending with .twd.test.ts
2-
const modules = import.meta.glob("./**/*.twd.test.ts", { eager: true });
1+
import { twd } from "../../../src";
32

3+
// This automatically imports all files ending with .twd.test.ts
4+
import.meta.glob("./**/*.twd.test.ts", { eager: true });
5+
twd.initRequestMocking()
6+
.then(() => {
7+
console.log("Request mocking initialized");
8+
})
9+
.catch((err) => {
10+
console.error("Error initializing request mocking:", err);
11+
});
412
// You don't need to export anything; simply importing this in App.tsx
513
// will cause the test files to execute and register their tests.

examples/my-twd-app/src/main.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ import { RouterProvider } from 'react-router'
99
createRoot(document.getElementById('root')!).render(
1010
<StrictMode>
1111
<RouterProvider router={router} />
12-
<TWDSidebar />
12+
<TWDSidebar open={true} />
1313
</StrictMode>,
1414
)

examples/my-twd-app/src/twd-tests/app.twd.test.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, itOnly, itSkip, beforeEach, twd } from "../../../../src/twd";
1+
import { describe, it, itOnly, itSkip, beforeEach, twd, expect, userEvent } from "../../../../src";
22

33
beforeEach(() => {
44
console.log("Reset state before each test");
@@ -15,8 +15,11 @@ describe("App interactions", () => {
1515
});
1616

1717
itOnly("only this one runs if present and long text to check the layout", async () => {
18+
const user = userEvent.setup();
1819
const btn = await twd.get("button");
1920
btn.click();
21+
await user.click(btn.el);
22+
await userEvent.click(btn.el);
2023
console.log("Ran only test");
2124
});
2225
describe("Nested describe", () => {
@@ -28,7 +31,6 @@ describe("App interactions", () => {
2831
});
2932

3033
it("fetches a joke", async () => {
31-
await twd.initRequestMocking();
3234
await twd.mockRequest("joke", {
3335
method: "GET",
3436
url: "https://api.chucknorris.io/jokes/random",
@@ -55,9 +57,29 @@ describe("App interactions", () => {
5557
btn.click();
5658
await twd.waitForRequest("joke");
5759
jokeText = await twd.get("p[data-twd='joke-text']");
60+
expect(jokeText.el.textContent).to.equal("Mocked second joke!");
5861
jokeText.should("have.text", "Mocked second joke!");
5962
// console.log(`Joke text: ${jokeText.el.textContent}`);
6063
// jokeText.should('be.disabled');
64+
twd.clearRequestMockRules();
65+
});
66+
67+
it("fetches a third joke to validate if the mocks are cleaned", async () => {
68+
await twd.mockRequest("joke", {
69+
method: "GET",
70+
url: "https://api.chucknorris.io/jokes/random",
71+
response: {
72+
value: "Third Mocked joke!",
73+
},
74+
});
75+
const btn = await twd.get("button[data-twd='joke-button']");
76+
btn.click();
77+
// Wait for the mock fetch to fire
78+
await twd.waitForRequest("joke");
79+
const jokeText = await twd.get("p[data-twd='joke-text']");
80+
// console.log(`Joke text: ${jokeText.el.textContent}`);
81+
jokeText.should("have.text", "Third Mocked joke!");
82+
twd.clearRequestMockRules();
6183
});
6284

6385
it("visit contact page", async () => {
@@ -75,5 +97,6 @@ describe("App interactions", () => {
7597
submitBtn.click();
7698
const rule = await twd.waitForRequest("contactSubmit");
7799
console.log(`Submitted body: ${rule.request}`);
100+
twd.clearRequestMockRules();
78101
});
79102
});

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export * from './twd';
22
export { TWDSidebar } from './ui/TWDSidebar';
3+
export { expect } from 'chai';
4+
export { userEvent } from './proxies/userEvent';

src/proxies/userEvent.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import userEventLib from '@testing-library/user-event';
2+
import { log } from '../utils/log';
3+
4+
type UserEvent = typeof userEventLib;
5+
6+
function createLoggedProxy(obj: any, prefix = 'userEvent') {
7+
return new Proxy(obj, {
8+
get(target, prop, receiver) {
9+
const orig = Reflect.get(target, prop, receiver);
10+
if (typeof orig !== 'function') return orig;
11+
12+
// Special handling for setup
13+
if (prop === 'setup') {
14+
return (...args: any[]) => {
15+
const instance = orig(...args);
16+
// Proxy the returned instance
17+
return createLoggedProxy(instance, `${prefix}.instance`);
18+
};
19+
}
20+
21+
return async (...args: any[]) => {
22+
const result = await orig(...args);
23+
log(`Assertion passed: ${prefix}.${String(prop)} finished`);
24+
return result;
25+
};
26+
},
27+
});
28+
}
29+
30+
export const userEvent: UserEvent = createLoggedProxy(userEventLib);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { describe, it, beforeEach, vi, expect } from 'vitest';
2+
import * as twd from '../../';
3+
import { userEvent } from '../../proxies/userEvent';
4+
import { tests } from '../../twdRegistry';
5+
6+
describe('userEvent', () => {
7+
beforeEach(() => {
8+
vi.clearAllMocks();
9+
});
10+
11+
it('should simulate user events', async () => {
12+
twd.describe('User Events', () => {
13+
twd.it('should log userEvent actions', async () => {
14+
const user = userEvent.setup();
15+
const input = document.createElement('input');
16+
const nonFinded = document.createElement('div');
17+
document.body.appendChild(input);
18+
19+
await user.click(input);
20+
expect(document.activeElement).toBe(input);
21+
22+
await user.type(input, 'Hello');
23+
expect((input as HTMLInputElement).value).toBe('Hello');
24+
});
25+
});
26+
tests[0].status = 'running';
27+
await tests[0].fn();
28+
expect(tests[0].logs).toContainEqual(expect.stringContaining('Assertion passed: userEvent.instance.click finished'));
29+
expect(tests[0].logs).toContainEqual(expect.stringContaining('Assertion passed: userEvent.instance.type finished'));
30+
});
31+
});

src/tests/ui/testListItem.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, expect, vi, suite } from "vitest";
1+
import { describe, it, expect, vi } from "vitest";
22
import userEvent from '@testing-library/user-event';
33
import { render, screen } from '@testing-library/react'
44
import { TestListItem, assertStyles, statusStyles } from "../../ui/TestListItem";

src/tests/ui/twdSidebar.spec.tsx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { describe, it, expect, vi, suite, beforeEach } from "vitest";
2+
import userEvent from '@testing-library/user-event';
3+
import { render, screen } from '@testing-library/react'
4+
import * as twd from '../../';
5+
import { TWDSidebar } from "../../ui/TWDSidebar";
6+
import { clearTests } from "../../twdRegistry";
7+
8+
describe("TWDSidebar", () => {
9+
beforeEach(() => {
10+
// Clear all registered tests before each test case
11+
clearTests();
12+
vi.clearAllMocks();
13+
});
14+
15+
describe("component" , () => {
16+
it("should render TWDSidebar component closed", async () => {
17+
render(<TWDSidebar open={false} />);
18+
const sidebarElement = screen.getByText("TWD");
19+
expect(sidebarElement).toBeInTheDocument();
20+
});
21+
22+
it("should render TWDSidebar component open", async () => {
23+
render(<TWDSidebar open={true} />);
24+
const sidebarElement = screen.getByText("Run All");
25+
expect(sidebarElement).toBeInTheDocument();
26+
});
27+
28+
it("should open the sidebar when clicking the closed sidebar", async () => {
29+
const user = userEvent.setup();
30+
render(<TWDSidebar open={false} />);
31+
const closedSidebarElement = screen.getByText("TWD");
32+
expect(closedSidebarElement).toBeInTheDocument();
33+
// Simulate a click event
34+
await user.click(closedSidebarElement);
35+
const openSidebarElement = screen.getByText("Run All");
36+
expect(openSidebarElement).toBeInTheDocument();
37+
// simulate a click event to close the sidebar
38+
const closeButton = screen.getByText("✖");
39+
await user.click(closeButton);
40+
expect(screen.getByText("TWD")).toBeInTheDocument();
41+
// Run all not visible
42+
expect(screen.queryByText("Run All")).not.toBeInTheDocument();
43+
});
44+
45+
it('execute test when clicking the run button', async () => {
46+
const firstTest = vi.fn();
47+
const secondTest = vi.fn();
48+
twd.describe('Group 1', () => {
49+
twd.it('Test 1.1', firstTest);
50+
twd.itSkip('Test 1.2', secondTest);
51+
});
52+
const user = userEvent.setup()
53+
render(<TWDSidebar open={true} />);
54+
const runAllButton = screen.getByText("Run All");
55+
expect(runAllButton).toBeInTheDocument();
56+
// Simulate a click event
57+
await user.click(runAllButton);
58+
expect(firstTest).toHaveBeenCalled();
59+
expect(secondTest).not.toHaveBeenCalled();
60+
});
61+
62+
it('skip test when there is a test with only', async () => {
63+
const firstTest = vi.fn();
64+
const secondTest = vi.fn();
65+
twd.describe('Group test only', () => {
66+
twd.it('Test no only', firstTest);
67+
twd.itOnly('Test only', secondTest);
68+
});
69+
const user = userEvent.setup()
70+
render(<TWDSidebar open={true} />);
71+
const runAllButton = screen.getByText("Run All");
72+
expect(runAllButton).toBeInTheDocument();
73+
// Simulate a click event
74+
await user.click(runAllButton);
75+
expect(firstTest).not.toHaveBeenCalled();
76+
expect(secondTest).toHaveBeenCalled();
77+
});
78+
79+
it('should handle throw exceptions in tests', async () => {
80+
const errorTest = vi.fn().mockRejectedValue(new Error("Test error"));
81+
twd.describe('Group test error', () => {
82+
twd.it('Test error', errorTest);
83+
});
84+
const user = userEvent.setup()
85+
render(<TWDSidebar open={true} />);
86+
const runAllButton = screen.getByText("Run All");
87+
expect(runAllButton).toBeInTheDocument();
88+
// Simulate a click event
89+
await user.click(runAllButton);
90+
expect(errorTest).toHaveBeenCalled();
91+
const errorLog = screen.getByText(/Test failed: Test error/);
92+
expect(errorLog).toBeInTheDocument();
93+
});
94+
});
95+
});

0 commit comments

Comments
 (0)