원클릭으로
test
Write tests for the specified code following AAA pattern, edge case coverage, and TypeScript best practices using bun test.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Write tests for the specified code following AAA pattern, edge case coverage, and TypeScript best practices using bun test.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Analyze a project or workspace to identify repeated patterns and propose abstractions for improved code reuse and maintainability.
Review codebase changes and create well-structured conventional commits with Shortcut ticket links. Use when asked to commit, save changes, or prepare code for review.
Run linting checks and fix all issues idiomatically. Never disables rules or suppresses errors.
Migrate React components from Snowflake SQL queries to NQL using the useNqlQuery hook. Handles query design, case sensitivity, and React dependency optimization.
Create a new PR for the current branch or update an existing one. Follows team PR best practices for titles, descriptions, and checklists.
Analyze and improve a React/TypeScript code file following component composition, hooks, performance, and file organization best practices.
| name | test |
| description | Write tests for the specified code following AAA pattern, edge case coverage, and TypeScript best practices using bun test. |
| argument-hint | [file-or-module] |
Write tests for $ARGUMENTS using the best practices below.
Before writing tests, check if there's an existing test directory structure:
tests/ or __tests__/ directory at the project root or module leveltests/pages/improve/ mirrors src/pages/improve/)@/pages/...)Before writing any tests:
When tests are in a separate directory:
// If test is in tests/pages/component.test.ts
// Update imports from relative to absolute using path aliases:
import { myFunction } from "@/pages/component";
// Instead of: import { myFunction } from "./component";
When testing reducers or state management with Immer:
// Bad - direct mutation
const state = new StateBuilder().build();
state.filters.columnFilters = { name: "test" }; // This will fail!
// Good - use actions
const state = new StateBuilder().build();
const stateWithFilter = reducer(state, {
type: "filters/setColumnFilter",
payload: { column: "name", value: "test" }
});
When testing sort operations:
.sort() on objects sorts by string representation, not propertiesFor complex external types (like Dataset, User, etc.):
as unknown as Type for safer castingas any - prefer as unknown as Type when type casting is necessary// Bad - using any
const mockDataset = { id: "test", name: "Test" } as any;
// Good - complete mock with proper typing
import type { Dataset } from "@narrative.io/data-collaboration-sdk-ts";
function createMockDataset(overrides: Partial<Dataset> = {}): Dataset {
return {
id: "test-dataset",
name: "Test Dataset",
company_id: "test-company",
created_at: "2024-01-01T00:00:00Z",
// ... all required properties
...overrides,
} as Dataset;
}
After writing tests:
bun test tests/path/to/file.test.ts (not bun test frontend/tests/...).only to focus on failing tests during debuggingbun check after fixing tests to ensure no type or lint errors remainTests should follow the same linting rules as production code:
bun check on test files to catch linting errors// Object keys may be reordered by linter
const data = {
name: "test",
value: 10,
status: "active"
};
// After linting might become:
const data = {
name: "test",
status: "active",
value: 10
};
When you need to modify state for test setup:
// Bad - direct mutation
const state = new StateBuilder().build();
state.currentPage = 3;
state.sortColumn = "name";
// Good - create new state object
const state = {
...new StateBuilder().build(),
currentPage: 3,
sortColumn: "name"
};
// Also good - use builder pattern
const state = new StateBuilder()
.withCurrentPage(3)
.withSortColumn("name")
.build();
Structure your test files for maximum clarity:
// 1. Imports
import { describe, test, expect } from "bun:test";
import { functionToTest } from "@/module";
// 2. Test data builders
class TestDataBuilder { ... }
// 3. Mock factories
function createMockObject() { ... }
// 4. Test suites organized by functionality
describe("Module Name", () => {
describe("Feature Group 1", () => {
test("should handle specific case", () => { ... });
});
describe("Feature Group 2", () => { ... });
describe("edge cases", () => { ... });
});
Each test should verify a single behavior or outcome:
// Bad - testing multiple behaviors
test("user service works", () => {
const user = createUser({ name: "John", email: "john@test.com" });
expect(user.id).toBeDefined();
expect(user.isActive).toBe(true);
expect(sendWelcomeEmail(user)).toBe(true);
});
// Good - focused tests
test("should generate unique ID when creating user", () => {
const user = createUser({ name: "John", email: "john@test.com" });
expect(user.id).toBeDefined();
expect(typeof user.id).toBe("string");
});
test("should set new users as active by default", () => {
const user = createUser({ name: "John", email: "john@test.com" });
expect(user.isActive).toBe(true);
});
Test names should clearly describe the scenario and expected outcome:
// Bad
test("error handling", () => {});
test("validates input", () => {});
// Good
test("should throw ValidationError when email format is invalid", () => {});
test("should return false when password is shorter than 8 characters", () => {});
test("should strip whitespace from username before validation", () => {});
test("should calculate compound interest correctly", () => {
// Arrange
const principal = 1000;
const rate = 0.05;
const time = 2;
const frequency = 12;
// Act
const amount = calculateCompoundInterest(principal, rate, time, frequency);
// Assert
expect(amount).toBeCloseTo(1104.94, 2);
});
describe("validateAge", () => {
test("should accept minimum valid age", () => {
expect(validateAge(18)).toBe(true);
});
test("should reject age below minimum", () => {
expect(validateAge(17)).toBe(false);
});
test("should handle zero", () => {
expect(validateAge(0)).toBe(false);
});
test("should handle negative numbers", () => {
expect(validateAge(-1)).toBe(false);
});
test("should handle very large numbers", () => {
expect(validateAge(150)).toBe(false);
});
});
test("should throw TypeError when input is not a number", () => {
expect(() => calculateSquareRoot("abc")).toThrow(TypeError);
expect(() => calculateSquareRoot("abc")).toThrow("Input must be a number");
});
test("should throw RangeError for negative numbers", () => {
expect(() => calculateSquareRoot(-4)).toThrow(RangeError);
expect(() => calculateSquareRoot(-4)).toThrow("Cannot calculate square root of negative number");
});
// Test data builder pattern
class UserBuilder {
private user = {
id: "default-id",
name: "John Doe",
email: "john@example.com",
age: 25,
isActive: true
};
withName(name: string) {
this.user.name = name;
return this;
}
withAge(age: number) {
this.user.age = age;
return this;
}
inactive() {
this.user.isActive = false;
return this;
}
build() {
return { ...this.user };
}
}
// Usage in tests
test("should filter inactive users", () => {
const users = [
new UserBuilder().build(),
new UserBuilder().inactive().build(),
new UserBuilder().withName("Jane").build()
];
const activeUsers = filterActiveUsers(users);
expect(activeUsers).toHaveLength(2);
});
// Create type-safe mock factories
function createMockUser(overrides?: Partial<User>): User {
return {
id: "test-id",
name: "Test User",
email: "test@example.com",
createdAt: new Date(),
...overrides
};
}
test("should update user name", () => {
const user = createMockUser({ name: "Original Name" });
const updated = updateUserName(user, "New Name");
expect(updated.name).toBe("New Name");
});
describe("firstOrDefault", () => {
test("should return first element for non-empty array", () => {
expect(firstOrDefault([1, 2, 3], 0)).toBe(1);
expect(firstOrDefault(["a", "b"], "default")).toBe("a");
});
test("should return default for empty array", () => {
expect(firstOrDefault([], 0)).toBe(0);
expect(firstOrDefault<string>([], "default")).toBe("default");
});
});
test("isValidEmail type guard should narrow type correctly", () => {
const input: unknown = "test@example.com";
if (isValidEmail(input)) {
// TypeScript should know input is string here
expect(input.toLowerCase()).toBe("test@example.com");
} else {
throw new Error("Expected valid email");
}
});
// Bad - testing implementation details
test("should set state when button clicked", () => {
const { result } = renderHook(() => useState(false));
act(() => result.current[1](true));
expect(result.current[0]).toBe(true);
});
// Good - testing user behavior
test("should show success message when form is submitted", async () => {
render(<ContactForm />);
await userEvent.type(screen.getByLabelText(/email/i), "user@example.com");
await userEvent.type(screen.getByLabelText(/message/i), "Hello");
await userEvent.click(screen.getByRole("button", { name: /submit/i }));
expect(await screen.findByText(/thank you/i)).toBeInTheDocument();
});
// Bad - using test IDs or implementation details
const button = screen.getByTestId("submit-button");
const input = container.querySelector(".email-input");
// Good - using accessible queries
const button = screen.getByRole("button", { name: /submit/i });
const input = screen.getByLabelText(/email address/i);
const heading = screen.getByRole("heading", { level: 1 });
test("should display search results after typing", async () => {
render(<SearchComponent />);
const searchInput = screen.getByRole("searchbox");
await userEvent.type(searchInput, "react");
// Wait for debounced search
expect(await screen.findByText(/loading/i)).toBeInTheDocument();
// Wait for results
expect(await screen.findByText(/react basics/i)).toBeInTheDocument();
expect(screen.getByText(/advanced react/i)).toBeInTheDocument();
});
test("should be keyboard navigable", async () => {
render(<Modal isOpen={true} />);
// Focus should be trapped in modal
const closeButton = screen.getByRole("button", { name: /close/i });
const firstInput = screen.getByLabelText(/name/i);
firstInput.focus();
await userEvent.tab();
expect(closeButton).toHaveFocus();
await userEvent.tab();
expect(firstInput).toHaveFocus(); // Focus wrapped around
});
test("should announce form errors to screen readers", async () => {
render(<LoginForm />);
await userEvent.click(screen.getByRole("button", { name: /submit/i }));
const errorMessage = await screen.findByRole("alert");
expect(errorMessage).toHaveTextContent(/email is required/i);
});
describe("formatCurrency", () => {
test.each([
[0, "$0.00"],
[1, "$1.00"],
[99.99, "$99.99"],
[1000, "$1,000.00"],
[1000000, "$1,000,000.00"],
[-50, "-$50.00"]
])("should format %d as %s", (input, expected) => {
expect(formatCurrency(input)).toBe(expected);
});
});
import { afterEach, beforeEach, test, expect, setSystemTime } from "bun:test";
describe("isExpired", () => {
beforeEach(() => {
// Set system time to a known date
setSystemTime(new Date("2024-01-01T12:00:00Z"));
});
afterEach(() => {
// Reset to real time
setSystemTime();
});
test("should return true for past dates", () => {
const pastDate = new Date("2023-12-31T23:59:59Z");
expect(isExpired(pastDate)).toBe(true);
});
test("should return false for future dates", () => {
const futureDate = new Date("2024-01-02T00:00:00Z");
expect(isExpired(futureDate)).toBe(false);
});
});
test("should display fallback UI when child component throws", () => {
const ThrowError = () => {
throw new Error("Test error");
};
render(
<ErrorBoundary fallback={<div>Something went wrong</div>}>
<ThrowError />
</ErrorBoundary>
);
expect(screen.getByText(/something went wrong/i)).toBeInTheDocument();
});
bun check and lint tests like you would any other file - Linting tests is important for readability and maintainabilityany types - Even in tests, maintain type safety with proper mocksbun check - Always verify both tests pass AND types/linting are clean@/ for consistency