Student starter code (30% baseline)
index.html- Main HTML pagescript.js- JavaScript logicstyles.css- Styling and layoutpackage.json- Dependenciessetup.sh- Setup scriptREADME.md- Instructions (below)💡 Download the ZIP, extract it, and follow the instructions below to get started!
By completing this activity, you will:
# Install dependencies
npm install --legacy-peer-deps
# Start the development server
npm start
What You'll Experience:
This activity transforms a working-but-slow app into a production-ready, well-tested portfolio piece.
✅ Fully Functional:
⚠️ Needs Optimization (Your TODOs):
Goal: Transform this working-but-slow app into a production-ready, well-tested portfolio piece!
# Run all tests
npm test
# Run tests in watch mode (auto-rerun on changes)
npm run test:watch
# Generate coverage report
npm run test:coverage
File: App.js (line ~26)
The ProductCard component re-renders unnecessarily every time the parent updates. Fix this:
// TODO: Wrap ProductCard with React.memo()
const ProductCard = React.memo(({ product, onPress }) => {
// Component code...
});
Success Criteria:
Why: React.memo prevents re-renders when props haven't changed, improving list scrolling performance.
File: App.js (line ~29 and ~60)
Add performance monitoring to measure component render times:
const ProductCard = React.memo(({ product, onPress }) => {
console.time(`ProductCard-${product.id}`);
// Component JSX...
console.timeEnd(`ProductCard-${product.id}`);
return <TouchableOpacity>...</TouchableOpacity>;
});
Success Criteria:
<5ms after optimizationsExpected Output: Console logs showing render times (should be <5ms after optimizations).
File: App.js (line ~35)
Add image optimization props to reduce memory usage:
<Image
source={{ uri: product.image }}
style={styles.image}
resizeMode="cover"
defaultSource={require('./assets/placeholder.png')} // Optional: add placeholder
/>
Success Criteria:
Why: Proper resizeMode and placeholders improve perceived performance.
File: App.js (line ~79)
The filteredProducts calculation runs on every render. Use useMemo to cache results:
import { useMemo } from 'react';
const filteredProducts = useMemo(() => {
return products.filter((product) => {
if (filter === 'inStock') return product.inStock;
if (filter === 'outOfStock') return !product.inStock;
return true;
});
}, [products, filter]); // Only recalculate when products or filter changes
Success Criteria:
Why: Prevents unnecessary filtering on every render, especially important for large datasets.
File: App.js (line ~88)
The totalValue calculation is expensive and runs on every render:
const totalValue = useMemo(() => {
return filteredProducts.reduce(
(sum, product) => sum + parseFloat(product.price),
0
);
}, [filteredProducts]);
Success Criteria:
Why: Aggregation operations can be expensive for large arrays.
File: App.js (line ~138)
Add performance props to the FlatList for smooth scrolling:
<FlatList
data={filteredProducts}
renderItem={renderProduct}
keyExtractor={(item) => item.id}
contentContainerStyle={styles.list}
// Performance optimizations:
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
initialNumToRender={10}
windowSize={10}
getItemLayout={(data, index) => ({
length: 104, // Card height (80 + 12 padding + 12 margin)
offset: 104 * index,
index,
})}
/>
Success Criteria:
Why: These props tell React Native to:
removeClippedSubviews)maxToRenderPerBatch)getItemLayout)Create a new file: __tests__/App.test.js
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import App from '../App';
describe('Activity 18: Quality Assurance', () => {
// Your tests go here
});
Success Criteria:
Test that the app renders without crashing:
it('renders without crashing', () => {
const { getByText } = render(<App />);
expect(getByText('Quality Assurance Demo')).toBeTruthy();
});
it('shows loading state initially', () => {
const { getByText } = render(<App />);
expect(getByText('Loading Products...')).toBeTruthy();
});
Success Criteria:
Test that filter buttons work correctly:
it('filters products by stock status', async () => {
const { getByText, queryAllByText } = render(<App />);
// Wait for products to load
await waitFor(() => {
expect(queryAllByText(/Product \d+/).length).toBeGreaterThan(0);
});
// Tap "In Stock" filter
fireEvent.press(getByText('In Stock'));
// TODO: Verify that only in-stock products are shown
// HINT: Count visible product cards and check their stock status
});
Success Criteria:
Test that tapping a product calls the handler:
it('calls handleProductPress when product is tapped', async () => {
const { queryAllByText } = render(<App />);
// Wait for products to load
await waitFor(() => {
expect(queryAllByText(/Product \d+/).length).toBeGreaterThan(0);
});
// Mock console.log to verify it was called
const consoleSpy = jest.spyOn(console, 'log');
// Tap first product
const firstProduct = queryAllByText(/Product \d+/)[0];
fireEvent.press(firstProduct.parent);
// Verify console.log was called
expect(consoleSpy).toHaveBeenCalledWith(
'Product pressed:',
expect.stringContaining('Product')
);
consoleSpy.mockRestore();
});
Success Criteria:
# Run tests
npm test
# Expected output:
# PASS __tests__/App.test.js
# Activity 18: Quality Assurance
# ✓ renders without crashing (XX ms)
# ✓ shows loading state initially (XX ms)
# ✓ filters products by stock status (XX ms)
# ✓ calls handleProductPress when product is tapped (XX ms)
#
# Test Suites: 1 passed, 1 total
# Tests: 4 passed, 4 total
Success Criteria:
After completing all optimizations, your app should meet these benchmarks:
| Metric | Before Optimization | After Optimization | Target |
|---|---|---|---|
| ProductCard render | 15-25ms | 2-5ms | <10ms |
| Initial load time | 2-3s | 1-1.5s | <2s |
| Scroll FPS | 30-45fps | 55-60fps | >55fps |
| Test coverage | 0% | 80%+ | >80% |
For students who want to go beyond:
Add a performance dashboard showing:
Expand test suite to 95%+ coverage:
Implement performance budgets:
Show in Portfolio: Create a "Performance Optimization Case Study" document showing before/after metrics, explaining each optimization decision and its impact.
When showcasing this project:
Demonstrate Performance Impact
Show Test Coverage
npm run test:coverage and screenshot the reportExplain Decisions
Connect to Real Projects
npm install --legacy-peer-depsjest-expo is in your devDependenciesReact.memo()useMemo dependencies array includes all variables used insidemaxToRenderPerBatch if rendering is slowgetItemLayout height matches actual card heightAfter completing this activity:
Before submitting, ensure:
<10ms render timesnpm run test:coverage)Need Help? Review Concept 18: Performance and Testing, or ask in the course discussion forum!