Modern JavaScript Ecosystem: Complete Developer Guide
Table of Contents
- Evolution of JavaScript
- ECMAScript Features
- Node.js and Server-Side JavaScript
- Package Management
- Build Tools and Bundlers
- Testing Frameworks
- Frontend Frameworks and Libraries
- Development Tools
- Performance and Optimization
- Future of JavaScript
Evolution of JavaScript {#evolution}
JavaScript has evolved from a simple scripting language for web pages to a powerful, versatile language that runs everywhere - from browsers and servers to mobile apps and desktop applications.
Timeline of JavaScript
// 1995: JavaScript created by Brendan Eich at Netscape
// 1997: ECMAScript standardization begins
// 2009: Node.js brings JavaScript to the server
// 2015: ES6/ES2015 - Major language overhaul
// 2020s: Modern JavaScript ecosystem maturity
// The evolution of variable declarations
// ES3 (1999)
var name = 'JavaScript';
// ES6 (2015)
const name = 'JavaScript';
let version = 'ES6';
// Modern features
const config = {
name: 'Modern JavaScript',
features: ['async/await', 'modules', 'classes', 'destructuring'],
ecosystem: new Set(['Node.js', 'React', 'Vue', 'Angular'])
};
JavaScript Everywhere
// Browser (Frontend)
document.addEventListener('DOMContentLoaded', () => {
console.log('JavaScript in the browser');
});
// Server (Node.js)
const express = require('express');
const app = express();
app.listen(3000, () => console.log('JavaScript on the server'));
// Mobile (React Native)
import { View, Text } from 'react-native';
const App = () => <View><Text>JavaScript on mobile</Text></View>;
// Desktop (Electron)
const { app, BrowserWindow } = require('electron');
const createWindow = () => {
const win = new BrowserWindow({ width: 800, height: 600 });
win.loadFile('index.html');
};
// IoT and Embedded (Johnny-Five)
const { Board, Led } = require('johnny-five');
const board = new Board();
board.on('ready', () => {
const led = new Led(13);
led.blink(500);
});
ECMAScript Features {#ecmascript}
ES6/ES2015 Foundation Features
// Arrow Functions
const numbers = [1, 2, 3, 4, 5];
// Traditional function
const doubled = numbers.map(function(n) {
return n * 2;
});
// Arrow function
const doubledArrow = numbers.map(n => n * 2);
// Complex arrow functions
const processUsers = users => users
.filter(user => user.active)
.map(user => ({
id: user.id,
name: user.name.toUpperCase(),
email: user.email.toLowerCase()
}))
.sort((a, b) => a.name.localeCompare(b.name));
// Template Literals
const user = { name: 'Alice', age: 30 };
const greeting = `Hello, ${user.name}! You are ${user.age} years old.`;
// Multi-line strings
const htmlTemplate = `
<div class="user-card">
<h2>${user.name}</h2>
<p>Age: ${user.age}</p>
<p>Status: ${user.age >= 18 ? 'Adult' : 'Minor'}</p>
</div>
`;
// Tagged template literals
function highlight(strings, ...values) {
return strings.reduce((result, string, i) => {
const value = values[i] ? `<mark>${values[i]}</mark>` : '';
return result + string + value;
}, '');
}
const searchTerm = 'JavaScript';
const text = highlight`Welcome to ${searchTerm} programming!`;
// Destructuring
const person = {
name: 'Bob',
age: 25,
address: {
street: '123 Main St',
city: 'Anytown',
country: 'USA'
},
hobbies: ['reading', 'coding', 'gaming']
};
// Object destructuring
const { name, age, address: { city } } = person;
// Array destructuring
const [firstHobby, secondHobby, ...otherHobbies] = person.hobbies;
// Function parameter destructuring
function createUser({ name, email, age = 18 }) {
return {
id: Date.now(),
name,
email,
age,
created: new Date()
};
}
// Spread and Rest Operators
// Spread in arrays
const fruits = ['apple', 'banana'];
const moreFruits = ['orange', 'grape'];
const allFruits = [...fruits, ...moreFruits, 'kiwi'];
// Spread in objects
const defaultConfig = { theme: 'dark', language: 'en' };
const userConfig = { language: 'es', notifications: true };
const finalConfig = { ...defaultConfig, ...userConfig };
// Rest parameters
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
// Classes
class Vehicle {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
getAge() {
return new Date().getFullYear() - this.year;
}
getInfo() {
return `${this.year} ${this.make} ${this.model}`;
}
}
class Car extends Vehicle {
constructor(make, model, year, doors) {
super(make, model, year);
this.doors = doors;
}
getType() {
return this.doors === 2 ? 'Coupe' : 'Sedan';
}
}
// Modules
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export default function calculator() {
return {
add,
multiply,
PI
};
}
// main.js
import calculator, { add, PI } from './math.js';
import * as math from './math.js';
const result = add(5, 3);
const area = PI * 5 * 5;
Modern ES Features (ES2017+)
// Async/Await (ES2017)
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const userData = await response.json();
return userData;
} catch (error) {
console.error('Failed to fetch user data:', error);
throw error;
}
}
// Parallel async operations
async function fetchMultipleUsers(userIds) {
const promises = userIds.map(id => fetchUserData(id));
const users = await Promise.all(promises);
return users;
}
// Sequential vs Parallel
async function sequentialOperations() {
const user1 = await fetchUserData(1); // Wait for this
const user2 = await fetchUserData(2); // Then wait for this
return [user1, user2];
}
async function parallelOperations() {
const [user1, user2] = await Promise.all([
fetchUserData(1), // Start both at the same time
fetchUserData(2)
]);
return [user1, user2];
}
// Object Rest/Spread (ES2018)
const user = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
password: 'secret',
role: 'admin'
};
const { password, ...publicUser } = user;
const updatedUser = {
...publicUser,
lastLogin: new Date(),
status: 'active'
};
// Optional Chaining (ES2020)
const nestedObject = {
user: {
profile: {
address: {
street: '123 Main St'
}
}
}
};
// Old way
const street = nestedObject.user &&
nestedObject.user.profile &&
nestedObject.user.profile.address &&
nestedObject.user.profile.address.street;
// Modern way
const modernStreet = nestedObject.user?.profile?.address?.street;
// Nullish Coalescing (ES2020)
const config = {
theme: null,
debug: false,
timeout: 0
};
// Old way (problematic with falsy values)
const theme = config.theme || 'default'; // 'default'
const debug = config.debug || true; // true (wrong!)
const timeout = config.timeout || 5000; // 5000 (wrong!)
// Modern way (only null/undefined trigger default)
const modernTheme = config.theme ?? 'default'; // 'default'
const modernDebug = config.debug ?? true; // false (correct!)
const modernTimeout = config.timeout ?? 5000; // 0 (correct!)
// BigInt (ES2020)
const largeNumber = 9007199254740991n;
const anotherLarge = BigInt('9007199254740992');
const calculation = largeNumber + anotherLarge;
// Dynamic Imports (ES2020)
async function loadModule(moduleName) {
const module = await import(`./modules/${moduleName}.js`);
return module.default;
}
// Conditional module loading
if (process.env.NODE_ENV === 'development') {
const devTools = await import('./dev-tools.js');
devTools.initialize();
}
// Private Class Fields (ES2022)
class Counter {
#count = 0; // Private field
#maxCount; // Private field
constructor(maxCount = 100) {
this.#maxCount = maxCount;
}
increment() {
if (this.#count < this.#maxCount) {
this.#count++;
}
}
get value() {
return this.#count;
}
#validateCount() { // Private method
return this.#count >= 0 && this.#count <= this.#maxCount;
}
}
// Top-level await (ES2022)
// In a module file
const config = await fetch('/api/config').then(r => r.json());
const database = await connectToDatabase(config.db);
export { database };
Node.js and Server-Side JavaScript {#nodejs}
Node.js Fundamentals
// Built-in modules
const fs = require('fs').promises;
const path = require('path');
const http = require('http');
const util = require('util');
// File system operations
async function fileOperations() {
try {
// Read file
const data = await fs.readFile('config.json', 'utf8');
const config = JSON.parse(data);
// Write file
const newConfig = { ...config, lastUpdated: new Date() };
await fs.writeFile('config.json', JSON.stringify(newConfig, null, 2));
// Directory operations
const files = await fs.readdir('./uploads');
for (const file of files) {
const stats = await fs.stat(path.join('./uploads', file));
console.log(`${file}: ${stats.size} bytes`);
}
} catch (error) {
console.error('File operation failed:', error);
}
}
// HTTP Server
const server = http.createServer(async (req, res) => {
// CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// Route handling
const url = new URL(req.url, `http://${req.headers.host}`);
if (url.pathname === '/api/users' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ users: [] }));
} else if (url.pathname === '/api/users' && req.method === 'POST') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
try {
const userData = JSON.parse(body);
// Process user data
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: Date.now(), ...userData }));
} catch (error) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON' }));
}
});
} else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not found' }));
}
});
server.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
// Express.js Framework
const express = require('express');
const app = express();
// Middleware
app.use(express.json());
app.use(express.static('public'));
// Logging middleware
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
next();
});
// Authentication middleware
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
// Verify token (simplified)
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
}
// Routes
app.get('/api/users', authenticate, async (req, res) => {
try {
const users = await getUsersFromDatabase();
res.json({ users });
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
app.post('/api/users', async (req, res) => {
try {
const { name, email } = req.body;
// Validation
if (!name || !email) {
return res.status(400).json({
error: 'Name and email are required'
});
}
const user = await createUser({ name, email });
res.status(201).json({ user });
} catch (error) {
res.status(500).json({ error: 'Failed to create user' });
}
});
// Error handling middleware
app.use((error, req, res, next) => {
console.error(error.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('Process terminated');
});
});
Streams and Performance
const fs = require('fs');
const { pipeline, Transform } = require('stream');
const { promisify } = require('util');
const pipelineAsync = promisify(pipeline);
// Transform stream for processing large files
class JSONProcessor extends Transform {
constructor() {
super({ objectMode: true });
this.buffer = '';
}
_transform(chunk, encoding, callback) {
this.buffer += chunk.toString();
const lines = this.buffer.split('
');
this.buffer = lines.pop() || '';
for (const line of lines) {
if (line.trim()) {
try {
const data = JSON.parse(line);
// Process data
const processed = {
...data,
processed: true,
timestamp: new Date().toISOString()
};
this.push(JSON.stringify(processed) + '
');
} catch (error) {
console.error('Invalid JSON:', line);
}
}
}
callback();
}
_flush(callback) {
if (this.buffer.trim()) {
try {
const data = JSON.parse(this.buffer);
const processed = {
...data,
processed: true,
timestamp: new Date().toISOString()
};
this.push(JSON.stringify(processed) + '
');
} catch (error) {
console.error('Invalid JSON in buffer:', this.buffer);
}
}
callback();
}
}
// Process large files efficiently
async function processLargeFile(inputPath, outputPath) {
try {
await pipelineAsync(
fs.createReadStream(inputPath),
new JSONProcessor(),
fs.createWriteStream(outputPath)
);
console.log('File processing completed');
} catch (error) {
console.error('Pipeline failed:', error);
}
}
// Memory-efficient CSV processing
const csv = require('csv-parser');
const csvWriter = require('csv-writer');
async function processCsvFile(inputPath, outputPath) {
const writer = csvWriter.createObjectCsvWriter({
path: outputPath,
header: [
{ id: 'id', title: 'ID' },
{ id: 'name', title: 'Name' },
{ id: 'email', title: 'Email' },
{ id: 'processed', title: 'Processed' }
]
});
const processedRecords = [];
return new Promise((resolve, reject) => {
fs.createReadStream(inputPath)
.pipe(csv())
.on('data', (data) => {
// Process each row
const processed = {
...data,
processed: new Date().toISOString()
};
processedRecords.push(processed);
// Write in batches to avoid memory issues
if (processedRecords.length >= 1000) {
writer.writeRecords(processedRecords);
processedRecords.length = 0;
}
})
.on('end', () => {
if (processedRecords.length > 0) {
writer.writeRecords(processedRecords);
}
resolve();
})
.on('error', reject);
});
}
Package Management {#package-management}
npm and package.json
{
"name": "modern-js-app",
"version": "1.0.0",
"description": "A modern JavaScript application",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"build": "webpack --mode production",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"format": "prettier --write src/",
"type-check": "tsc --noEmit",
"prebuild": "npm run lint && npm run test",
"postinstall": "husky install"
},
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.0.0",
"jsonwebtoken": "^9.0.0",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"helmet": "^6.0.1",
"dotenv": "^16.0.3"
},
"devDependencies": {
"nodemon": "^2.0.20",
"jest": "^29.4.0",
"supertest": "^6.3.3",
"eslint": "^8.34.0",
"prettier": "^2.8.4",
"husky": "^8.0.3",
"lint-staged": "^13.1.2",
"@types/node": "^18.14.0",
"typescript": "^4.9.5"
},
"engines": {
"node": ">=16.0.0",
"npm": ">=8.0.0"
},
"lint-staged": {
"*.{js,ts}": [
"eslint --fix",
"prettier --write"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"pre-push": "npm test"
}
}
}
Yarn and Advanced Features
# Yarn workspace configuration
# package.json (root)
{
"private": true,
"workspaces": [
"packages/*",
"apps/*"
],
"scripts": {
"build": "yarn workspaces run build",
"test": "yarn workspaces run test",
"dev": "concurrently "yarn workspace @myapp/api dev" "yarn workspace @myapp/web dev""
}
}
# packages/shared/package.json
{
"name": "@myapp/shared",
"version": "1.0.0",
"main": "dist/index.js",
"dependencies": {
"lodash": "^4.17.21"
}
}
# apps/api/package.json
{
"name": "@myapp/api",
"version": "1.0.0",
"dependencies": {
"@myapp/shared": "1.0.0",
"express": "^4.18.2"
}
}
# Yarn commands
yarn install # Install dependencies
yarn add express # Add dependency
yarn add -D jest # Add dev dependency
yarn workspace @myapp/api add mongoose # Add to specific workspace
yarn workspaces run build # Run script in all workspaces
yarn workspace @myapp/api test # Run script in specific workspace
pnpm - Efficient Package Manager
# pnpm features
pnpm install # Fast, disk-efficient installs
pnpm add express # Add dependency
pnpm run build # Run scripts
pnpm exec eslint . # Execute package binaries
# pnpm workspace (pnpm-workspace.yaml)
packages:
- 'packages/*'
- 'apps/*'
- '!**/test/**'
# Efficient storage and linking
# Creates hard links instead of duplicating files
# Saves significant disk space in monorepos
Package Security and Auditing
# Security auditing
npm audit # Check for vulnerabilities
npm audit fix # Automatically fix issues
npm audit fix --force # Force fixes (may break things)
# Yarn security
yarn audit # Check vulnerabilities
yarn audit --json # JSON output for CI/CD
# Alternative tools
npx audit-ci # Audit in CI/CD pipelines
npx snyk test # Comprehensive security testing
npx depcheck # Find unused dependencies
npx npm-check-updates # Check for package updates
Build Tools and Bundlers {#build-tools}
Webpack Configuration
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
entry: {
main: './src/index.js',
vendor: ['react', 'react-dom', 'lodash']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProduction
? '[name].[contenthash].js'
: '[name].js',
publicPath: '/',
clean: true
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: 'defaults' }],
'@babel/preset-react'
],
plugins: [
'@babel/plugin-proposal-class-properties',
isProduction && 'transform-remove-console'
].filter(Boolean)
}
}
},
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader'
]
},
{
test: /\.scss$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader',
'sass-loader'
]
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: 'asset/resource',
generator: {
filename: 'images/[name].[contenthash][ext]'
}
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[name].[contenthash][ext]'
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html',
minify: isProduction ? {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
} : false
}),
isProduction && new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css'
})
].filter(Boolean),
optimization: {
minimize: isProduction,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
})
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 3000,
hot: true,
historyApiFallback: true,
open: true
},
devtool: isProduction ? 'source-map' : 'eval-source-map',
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils')
}
}
};
};
Vite - Next Generation Build Tool
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist',
sourcemap: true,
minify: 'terser',
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'date-fns']
}
}
}
},
server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components')
}
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`
}
}
}
});
// Lightning fast HMR and build times
// Native ES modules support
// Built-in TypeScript support
// Optimized for modern browsers
Rollup for Library Building
// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import terser from '@rollup/plugin-terser';
import { fileURLToPath } from 'url';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
export default {
input: 'src/index.js',
output: [
{
file: 'dist/my-library.js',
format: 'umd',
name: 'MyLibrary',
globals: {
react: 'React',
'react-dom': 'ReactDOM'
}
},
{
file: 'dist/my-library.esm.js',
format: 'esm'
},
{
file: 'dist/my-library.cjs.js',
format: 'cjs'
}
],
external: ['react', 'react-dom'],
plugins: [
resolve({
browser: true,
preferBuiltins: false
}),
commonjs(),
babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**',
presets: [
['@babel/preset-env', { modules: false }],
'@babel/preset-react'
]
}),
terser()
]
};
Testing Frameworks {#testing}
Jest Testing Framework
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
'<rootDir>/src/**/*.{test,spec}.{js,jsx,ts,tsx}'
],
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/index.js',
'!src/reportWebVitals.js'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
'\.(css|less|scss|sass)$': 'identity-obj-proxy'
},
transform: {
'^.+\.(js|jsx|ts|tsx)$': 'babel-jest'
}
};
// Example test file
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import UserForm from '../UserForm';
import { createUser } from '../api/users';
// Mock API calls
jest.mock('../api/users');
const mockCreateUser = createUser as jest.MockedFunction<typeof createUser>;
describe('UserForm', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders form fields correctly', () => {
render(<UserForm onSubmit={jest.fn()} />);
expect(screen.getByLabelText(/name/i)).toBeInTheDocument();
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /submit/i })).toBeInTheDocument();
});
it('validates required fields', async () => {
const mockOnSubmit = jest.fn();
render(<UserForm onSubmit={mockOnSubmit} />);
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(screen.getByText(/name is required/i)).toBeInTheDocument();
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
});
expect(mockOnSubmit).not.toHaveBeenCalled();
});
it('submits form with valid data', async () => {
const mockUser = { id: 1, name: 'John Doe', email: 'john@example.com' };
mockCreateUser.mockResolvedValueOnce(mockUser);
const mockOnSubmit = jest.fn();
render(<UserForm onSubmit={mockOnSubmit} />);
fireEvent.change(screen.getByLabelText(/name/i), {
target: { value: 'John Doe' }
});
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'john@example.com' }
});
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(mockCreateUser).toHaveBeenCalledWith({
name: 'John Doe',
email: 'john@example.com'
});
expect(mockOnSubmit).toHaveBeenCalledWith(mockUser);
});
});
it('handles API errors gracefully', async () => {
mockCreateUser.mockRejectedValueOnce(new Error('API Error'));
render(<UserForm onSubmit={jest.fn()} />);
fireEvent.change(screen.getByLabelText(/name/i), {
target: { value: 'John Doe' }
});
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'john@example.com' }
});
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(screen.getByText(/failed to create user/i)).toBeInTheDocument();
});
});
});
// Utility functions for testing
export const renderWithProviders = (ui, options = {}) => {
const { theme = 'light', ...renderOptions } = options;
function Wrapper({ children }) {
return (
<ThemeProvider theme={theme}>
<Router>
{children}
</Router>
</ThemeProvider>
);
}
return render(ui, { wrapper: Wrapper, ...renderOptions });
};
// Custom hooks testing
import { renderHook, act } from '@testing-library/react';
import useCounter from '../useCounter';
describe('useCounter', () => {
it('should initialize with default value', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
it('should increment count', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});
Playwright for E2E Testing
// playwright.config.js
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure'
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] }
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] }
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] }
},
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] }
}
],
webServer: {
command: 'npm start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI
}
});
// Example E2E test
import { test, expect } from '@playwright/test';
test.describe('User Management', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('should create a new user', async ({ page }) => {
// Navigate to user creation form
await page.click('text=Add User');
await expect(page).toHaveURL('/users/new');
// Fill form
await page.fill('[data-testid=user-name]', 'John Doe');
await page.fill('[data-testid=user-email]', 'john@example.com');
// Submit form
await page.click('[data-testid=submit-button]');
// Verify success
await expect(page.locator('.success-message')).toContainText(
'User created successfully'
);
// Verify user appears in list
await page.goto('/users');
await expect(page.locator('[data-testid=user-list]')).toContainText('John Doe');
});
test('should handle form validation', async ({ page }) => {
await page.click('text=Add User');
// Try to submit empty form
await page.click('[data-testid=submit-button]');
// Check validation messages
await expect(page.locator('.error-message')).toContainText('Name is required');
await expect(page.locator('.error-message')).toContainText('Email is required');
});
test('should be responsive on mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
// Check mobile navigation
await page.click('[data-testid=mobile-menu-button]');
await expect(page.locator('[data-testid=mobile-menu]')).toBeVisible();
// Check form is usable on mobile
await page.click('text=Add User');
await page.fill('[data-testid=user-name]', 'Mobile User');
await page.fill('[data-testid=user-email]', 'mobile@example.com');
await page.click('[data-testid=submit-button]');
await expect(page.locator('.success-message')).toBeVisible();
});
});
This comprehensive guide covers the modern JavaScript ecosystem from language features to tooling and testing. The JavaScript ecosystem continues to evolve rapidly, with new tools and frameworks emerging regularly. The key is to understand the fundamentals and choose the right tools for your specific project needs.
Remember that while the ecosystem offers many choices, it's important to balance innovation with stability, especially in production applications. Start with well-established tools and gradually adopt newer technologies as they mature and provide clear benefits to your development workflow.