99 lines
3.3 KiB
HTML
99 lines
3.3 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="utf-8">
|
||
|
|
<title>QA Eval — SPA Store</title>
|
||
|
|
<style>
|
||
|
|
body { font-family: sans-serif; padding: 20px; margin: 0; }
|
||
|
|
nav { background: #333; padding: 10px 20px; }
|
||
|
|
nav a { color: white; margin-right: 15px; text-decoration: none; cursor: pointer; }
|
||
|
|
nav a:hover { text-decoration: underline; }
|
||
|
|
#app { padding: 20px; }
|
||
|
|
.product { border: 1px solid #ddd; padding: 10px; margin: 10px 0; border-radius: 4px; }
|
||
|
|
.product button { padding: 6px 12px; background: #0066cc; color: white; border: none; cursor: pointer; }
|
||
|
|
.cart-count { background: #cc0000; color: white; padding: 2px 8px; border-radius: 10px; font-size: 12px; }
|
||
|
|
.error { color: red; padding: 10px; }
|
||
|
|
.loading { color: #666; padding: 10px; }
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<nav>
|
||
|
|
<a href="#/home">Home</a>
|
||
|
|
<a href="#/prodcts">Products</a> <!-- BUG 1: broken route — typo "prodcts" instead of "products" -->
|
||
|
|
<a href="#/contact">Contact</a>
|
||
|
|
<span class="cart-count" id="cart-count">0</span>
|
||
|
|
</nav>
|
||
|
|
|
||
|
|
<div id="app">
|
||
|
|
<p>Welcome to SPA Store. Use the navigation above.</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
let cartCount = 0;
|
||
|
|
|
||
|
|
// BUG 2: cart count never resets on route change — stale state
|
||
|
|
function addToCart() {
|
||
|
|
cartCount++;
|
||
|
|
document.getElementById('cart-count').textContent = cartCount;
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderHome() {
|
||
|
|
document.getElementById('app').innerHTML = `
|
||
|
|
<h1>Welcome to SPA Store</h1>
|
||
|
|
<p>Browse our products using the navigation above.</p>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderProducts() {
|
||
|
|
document.getElementById('app').innerHTML = '<p class="loading">Loading products...</p>';
|
||
|
|
|
||
|
|
// BUG 3: async race — shows data briefly, then shows error
|
||
|
|
setTimeout(() => {
|
||
|
|
document.getElementById('app').innerHTML = `
|
||
|
|
<h1>Products</h1>
|
||
|
|
<div class="product">
|
||
|
|
<h3>Widget A</h3>
|
||
|
|
<p>$29.99</p>
|
||
|
|
<button onclick="addToCart()">Add to Cart</button>
|
||
|
|
</div>
|
||
|
|
<div class="product">
|
||
|
|
<h3>Widget B</h3>
|
||
|
|
<p>$49.99</p>
|
||
|
|
<button onclick="addToCart()">Add to Cart</button>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
}, 300);
|
||
|
|
|
||
|
|
setTimeout(() => {
|
||
|
|
document.getElementById('app').innerHTML = '<p class="error">Error: Failed to fetch products from API</p>';
|
||
|
|
}, 1000);
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderContact() {
|
||
|
|
document.getElementById('app').innerHTML = `
|
||
|
|
<h1>Contact Us</h1>
|
||
|
|
<p>Email: support@spastore.example.com</p>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
|
||
|
|
// BUG 4: nav links have no aria-current attribute on active route
|
||
|
|
function router() {
|
||
|
|
const hash = window.location.hash || '#/home';
|
||
|
|
switch (hash) {
|
||
|
|
case '#/home': renderHome(); break;
|
||
|
|
case '#/products': renderProducts(); break;
|
||
|
|
case '#/contact': renderContact(); break;
|
||
|
|
default:
|
||
|
|
document.getElementById('app').innerHTML = '<p>Page not found</p>';
|
||
|
|
}
|
||
|
|
|
||
|
|
// BUG 5: console.warn on every route change — simulates listener leak
|
||
|
|
console.warn('Possible memory leak detected: 11 event listeners added to window');
|
||
|
|
}
|
||
|
|
|
||
|
|
window.addEventListener('hashchange', router);
|
||
|
|
router();
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|