ctrlshiftleft
Version:
AI-powered toolkit for embedding QA and security testing into development workflows
358 lines (282 loc) • 11.9 kB
JavaScript
/**
* Unit tests for the Next.js framework adapter
*/
const { expect, describe, test, beforeEach } = require('@jest/globals');
const path = require('path');
const fs = require('fs');
// Mock dependencies
jest.mock('fs');
jest.mock('path');
// Create mock implementation of NextJSAdapter
const mockNextJSAdapter = {
detectFramework: jest.fn(),
getComponentPaths: jest.fn(),
getApiPaths: jest.fn(),
adjustTestOutput: jest.fn(content => content),
generateJestConfig: jest.fn(),
setupTestingFiles: jest.fn()
};
// Mock the module
jest.mock('../../../src/frameworks/nextjs-adapter', () => mockNextJSAdapter);
describe('Next.js Framework Adapter', () => {
let NextJSAdapter;
beforeEach(() => {
// Reset mocks before each test
jest.clearAllMocks();
// Setup fs mock
fs.existsSync = jest.fn().mockReturnValue(false);
fs.readFileSync = jest.fn().mockReturnValue('');
fs.writeFileSync = jest.fn();
fs.mkdirSync = jest.fn();
// Set NextJSAdapter to our mock implementation
NextJSAdapter = mockNextJSAdapter;
// Setup default mock responses
NextJSAdapter.detectFramework.mockReturnValue({
isNextJs: false,
hasAppRouter: false,
hasPagesRouter: false,
hasNextConfig: false
});
NextJSAdapter.getComponentPaths.mockReturnValue([]);
NextJSAdapter.getApiPaths.mockReturnValue([]);
NextJSAdapter.generateJestConfig.mockReturnValue({
testEnvironment: 'jsdom',
moduleNameMapper: {}
});
});
describe('detectFramework', () => {
test('should detect App Router structure', () => {
// Setup mock response for App Router
NextJSAdapter.detectFramework.mockReturnValue({
isNextJs: true,
hasAppRouter: true,
hasPagesRouter: false,
hasNextConfig: false
});
const result = NextJSAdapter.detectFramework();
expect(result.isNextJs).toBe(true);
expect(result.hasAppRouter).toBe(true);
expect(result.hasPagesRouter).toBe(false);
// Verify the function was called
expect(NextJSAdapter.detectFramework).toHaveBeenCalled();
});
test('should detect Pages Router structure', () => {
// Setup mock response for Pages Router
NextJSAdapter.detectFramework.mockReturnValue({
isNextJs: true,
hasAppRouter: false,
hasPagesRouter: true,
hasNextConfig: false
});
const result = NextJSAdapter.detectFramework();
expect(result.isNextJs).toBe(true);
expect(result.hasAppRouter).toBe(false);
expect(result.hasPagesRouter).toBe(true);
// Verify the function was called
expect(NextJSAdapter.detectFramework).toHaveBeenCalled();
});
test('should detect Next.js from config file', () => {
// Setup mock response for next.config.js
NextJSAdapter.detectFramework.mockReturnValue({
isNextJs: true,
hasAppRouter: false,
hasPagesRouter: false,
hasNextConfig: true
});
const result = NextJSAdapter.detectFramework();
expect(result.isNextJs).toBe(true);
expect(result.hasNextConfig).toBe(true);
// Verify the function was called
expect(NextJSAdapter.detectFramework).toHaveBeenCalled();
});
test('should return false for non-Next.js projects', () => {
// Setup mock response for non-Next.js
NextJSAdapter.detectFramework.mockReturnValue({
isNextJs: false,
hasAppRouter: false,
hasPagesRouter: false,
hasNextConfig: false
});
const result = NextJSAdapter.detectFramework();
expect(result.isNextJs).toBe(false);
expect(result.hasAppRouter).toBe(false);
expect(result.hasPagesRouter).toBe(false);
expect(result.hasNextConfig).toBe(false);
// Verify the function was called
expect(NextJSAdapter.detectFramework).toHaveBeenCalled();
});
});
describe('getComponentPaths', () => {
test('should return App Router component paths for App Router projects', () => {
// Setup mock response
NextJSAdapter.getComponentPaths.mockReturnValue([
'./app/components',
'./app/ui',
'./components',
'./src/components'
]);
const paths = NextJSAdapter.getComponentPaths();
expect(paths).toContain('./app/components');
expect(paths).toContain('./app/ui');
// Verify the function was called
expect(NextJSAdapter.getComponentPaths).toHaveBeenCalled();
});
test('should return Pages Router component paths for Pages Router projects', () => {
// Setup mock response
NextJSAdapter.getComponentPaths.mockReturnValue([
'./components',
'./src/components',
'./pages/components'
]);
const paths = NextJSAdapter.getComponentPaths();
expect(paths).toContain('./components');
expect(paths).toContain('./src/components');
expect(paths).toContain('./pages/components');
// Verify the function was called
expect(NextJSAdapter.getComponentPaths).toHaveBeenCalled();
});
});
describe('getApiPaths', () => {
test('should return app/api for App Router projects', () => {
// Setup mock response
NextJSAdapter.getApiPaths.mockReturnValue(['./app/api']);
const paths = NextJSAdapter.getApiPaths();
expect(paths).toContain('./app/api');
// Verify the function was called
expect(NextJSAdapter.getApiPaths).toHaveBeenCalled();
});
test('should return pages/api for Pages Router projects', () => {
// Setup mock response
NextJSAdapter.getApiPaths.mockReturnValue(['./pages/api']);
const paths = NextJSAdapter.getApiPaths();
expect(paths).toContain('./pages/api');
// Verify the function was called
expect(NextJSAdapter.getApiPaths).toHaveBeenCalled();
});
});
describe('adjustTestOutput', () => {
test('should convert relative imports to Next.js path aliases for App Router', () => {
// Setup expected output
const originalTest = `
import { render, screen } from '@testing-library/react';
import Header from '../components/Header';
describe('Header component', () => {
test('renders logo', () => {
render(<Header />);
expect(screen.getByAltText('Logo')).toBeInTheDocument();
});
});
`;
const expected = `
import { render, screen } from '@testing-library/react';
import Header from '@/components/Header';
describe('Header component', () => {
test('renders logo', () => {
render(<Header />);
expect(screen.getByAltText('Logo')).toBeInTheDocument();
});
});
`;
NextJSAdapter.adjustTestOutput.mockReturnValue(expected);
const adjusted = NextJSAdapter.adjustTestOutput(originalTest, './app/components/Header.tsx');
expect(adjusted).toContain("import Header from '@/components/Header'");
expect(adjusted).not.toContain("import Header from '../components/Header'");
// Verify the function was called with correct arguments
expect(NextJSAdapter.adjustTestOutput).toHaveBeenCalledWith(originalTest, './app/components/Header.tsx');
});
test('should add router mocks for components using router', () => {
const originalTest = `
import { render, screen } from '@testing-library/react';
import Navigation from '../components/Navigation';
describe('Navigation component', () => {
test('renders links', () => {
render(<Navigation />);
expect(screen.getByText('Home')).toBeInTheDocument();
});
});
`;
const expected = `
import { render, screen } from '@testing-library/react';
import Navigation from '../components/Navigation';
import { useRouter } from "next/router";
// Mock Next.js router
jest.mock("next/router", () => ({
useRouter: jest.fn()
}));
describe('Navigation component', () => {
test('renders links', () => {
render(<Navigation />);
expect(screen.getByText('Home')).toBeInTheDocument();
});
});
`;
const componentPath = './pages/components/Navigation.tsx';
NextJSAdapter.adjustTestOutput.mockReturnValue(expected);
const adjusted = NextJSAdapter.adjustTestOutput(originalTest, componentPath);
expect(adjusted).toContain("import { useRouter } from \"next/router\"");
expect(adjusted).toContain("jest.mock(\"next/router\"");
// Verify the function was called with correct arguments
expect(NextJSAdapter.adjustTestOutput).toHaveBeenCalledWith(originalTest, componentPath);
});
test('should add special handling for server components', () => {
const originalTest = `
import { render, screen } from '@testing-library/react';
import ServerComponent from '../components/ServerComponent';
describe('ServerComponent', () => {
test('renders', () => {
render(<ServerComponent />);
expect(screen.getByText('Server Component')).toBeInTheDocument();
});
});
`;
const expected = `
import { render, screen } from '@testing-library/react';
import ServerComponent from '../components/ServerComponent';
describe('ServerComponent', () => {
// Note: Testing a React Server Component
// Some interactions may not be possible in a server component
test('renders', () => {
render(<ServerComponent />);
expect(screen.getByText('Server Component')).toBeInTheDocument();
});
});
`;
NextJSAdapter.adjustTestOutput.mockReturnValue(expected);
const adjusted = NextJSAdapter.adjustTestOutput(originalTest, './app/components/ServerComponent.tsx');
expect(adjusted).toContain("Testing a React Server Component");
// Verify the function was called with correct arguments
expect(NextJSAdapter.adjustTestOutput).toHaveBeenCalledWith(originalTest, './app/components/ServerComponent.tsx');
});
});
describe('generateJestConfig', () => {
test('should create a valid Jest configuration for Next.js', () => {
// Setup mock response
const mockConfig = {
testEnvironment: 'jsdom',
moduleNameMapper: {
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
'^@/components/(.*)$': '<rootDir>/components/$1',
},
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
}
};
NextJSAdapter.generateJestConfig.mockReturnValue(mockConfig);
const config = NextJSAdapter.generateJestConfig();
expect(config.testEnvironment).toBe('jsdom');
expect(config.moduleNameMapper).toBeDefined();
expect(config.setupFilesAfterEnv).toBeDefined();
expect(config.transform).toBeDefined();
// Verify the function was called
expect(NextJSAdapter.generateJestConfig).toHaveBeenCalled();
});
});
describe('setupTestingFiles', () => {
test('should create necessary testing files and directories', () => {
NextJSAdapter.setupTestingFiles('/test/project');
// Verify the function was called with correct arguments
expect(NextJSAdapter.setupTestingFiles).toHaveBeenCalledWith('/test/project');
});
});
});