664 lines
19 KiB
HTML
664 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Cart/Wishlist Safeguard Tests</title>
|
|
<style>
|
|
body {
|
|
font-family: system-ui, -apple-system, sans-serif;
|
|
max-width: 1200px;
|
|
margin: 40px auto;
|
|
padding: 20px;
|
|
background: #f5f5f5;
|
|
}
|
|
h1 {
|
|
color: #202023;
|
|
border-bottom: 3px solid #fcb1d8;
|
|
padding-bottom: 10px;
|
|
}
|
|
.test-section {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin: 20px 0;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
.test-button {
|
|
background: #fcb1d8;
|
|
color: white;
|
|
border: none;
|
|
padding: 10px 20px;
|
|
margin: 5px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-weight: 600;
|
|
}
|
|
.test-button:hover {
|
|
background: #f6ccde;
|
|
}
|
|
.result {
|
|
margin: 10px 0;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
font-family: monospace;
|
|
}
|
|
.success {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
}
|
|
.error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
}
|
|
.info {
|
|
background: #d1ecf1;
|
|
color: #0c5460;
|
|
}
|
|
.status {
|
|
display: inline-block;
|
|
padding: 2px 8px;
|
|
border-radius: 3px;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
}
|
|
.pass {
|
|
background: #28a745;
|
|
color: white;
|
|
}
|
|
.fail {
|
|
background: #dc3545;
|
|
color: white;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>🛡️ Cart/Wishlist System - Safeguard Tests</h1>
|
|
|
|
<div class="test-section">
|
|
<h2>1. Invalid Product Tests</h2>
|
|
<button class="test-button" onclick="testInvalidProduct()">
|
|
Test: No ID
|
|
</button>
|
|
<button class="test-button" onclick="testInvalidPrice()">
|
|
Test: Invalid Price
|
|
</button>
|
|
<button class="test-button" onclick="testMissingName()">
|
|
Test: Missing Name
|
|
</button>
|
|
<button class="test-button" onclick="testMissingImage()">
|
|
Test: Missing Image
|
|
</button>
|
|
<div id="invalid-results"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>2. Type Coercion Tests</h2>
|
|
<button class="test-button" onclick="testStringId()">
|
|
Test: String ID
|
|
</button>
|
|
<button class="test-button" onclick="testNumberId()">
|
|
Test: Number ID
|
|
</button>
|
|
<button class="test-button" onclick="testMixedIds()">
|
|
Test: Mixed IDs
|
|
</button>
|
|
<div id="type-results"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>3. Quantity Boundary Tests</h2>
|
|
<button class="test-button" onclick="testZeroQuantity()">
|
|
Test: Zero Quantity
|
|
</button>
|
|
<button class="test-button" onclick="testNegativeQuantity()">
|
|
Test: Negative Quantity
|
|
</button>
|
|
<button class="test-button" onclick="testMaxQuantity()">
|
|
Test: Max Quantity (999)
|
|
</button>
|
|
<div id="quantity-results"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>4. localStorage Corruption Tests</h2>
|
|
<button class="test-button" onclick="testCorruptedData()">
|
|
Test: Corrupted JSON
|
|
</button>
|
|
<button class="test-button" onclick="testNonArrayData()">
|
|
Test: Non-Array Data
|
|
</button>
|
|
<button class="test-button" onclick="testRecovery()">
|
|
Test: Recovery
|
|
</button>
|
|
<div id="storage-results"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>5. Mathematical Safeguard Tests</h2>
|
|
<button class="test-button" onclick="testStringPrice()">
|
|
Test: String Price
|
|
</button>
|
|
<button class="test-button" onclick="testNaNPrice()">
|
|
Test: NaN Price
|
|
</button>
|
|
<button class="test-button" onclick="testTotalCalculation()">
|
|
Test: Total Calculation
|
|
</button>
|
|
<div id="math-results"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>6. Rapid Operation Tests</h2>
|
|
<button class="test-button" onclick="testRapidAdd()">
|
|
Test: Rapid Add (10x)
|
|
</button>
|
|
<button class="test-button" onclick="testRapidRemove()">
|
|
Test: Rapid Remove
|
|
</button>
|
|
<button class="test-button" onclick="testSimultaneous()">
|
|
Test: Simultaneous Ops
|
|
</button>
|
|
<div id="rapid-results"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>Current Cart State</h2>
|
|
<button class="test-button" onclick="displayCartState()">
|
|
View Cart
|
|
</button>
|
|
<button class="test-button" onclick="clearCart()">Clear Cart</button>
|
|
<div id="cart-state"></div>
|
|
</div>
|
|
|
|
<script>
|
|
// Load shop-system.js functionality
|
|
function log(message, type = "info", containerId) {
|
|
const container = document.getElementById(containerId);
|
|
const div = document.createElement("div");
|
|
div.className = `result ${type}`;
|
|
div.textContent = message;
|
|
container.appendChild(div);
|
|
}
|
|
|
|
function logStatus(test, passed, containerId) {
|
|
const container = document.getElementById(containerId);
|
|
const div = document.createElement("div");
|
|
div.className = "result";
|
|
div.innerHTML = `${test}: <span class="status ${
|
|
passed ? "pass" : "fail"
|
|
}">${passed ? "PASS" : "FAIL"}</span>`;
|
|
container.appendChild(div);
|
|
}
|
|
|
|
// Initialize simple cart system for testing
|
|
const testCart = {
|
|
items: [],
|
|
|
|
addItem(product, quantity = 1) {
|
|
// Validation safeguards
|
|
if (!product || !product.id) {
|
|
return { success: false, error: "Invalid product: missing ID" };
|
|
}
|
|
|
|
const price = parseFloat(product.price);
|
|
if (isNaN(price) || price < 0) {
|
|
return { success: false, error: "Invalid price" };
|
|
}
|
|
|
|
quantity = Math.max(1, parseInt(quantity) || 1);
|
|
|
|
const existing = this.items.find(
|
|
(item) => String(item.id) === String(product.id)
|
|
);
|
|
if (existing) {
|
|
existing.quantity = Math.min(existing.quantity + quantity, 999);
|
|
} else {
|
|
this.items.push({
|
|
id: product.id,
|
|
name: product.name || product.title || "Product",
|
|
price: price,
|
|
imageurl: product.imageurl || "/assets/images/placeholder.jpg",
|
|
quantity: quantity,
|
|
});
|
|
}
|
|
|
|
this.save();
|
|
return { success: true };
|
|
},
|
|
|
|
removeItem(id) {
|
|
this.items = this.items.filter(
|
|
(item) => String(item.id) !== String(id)
|
|
);
|
|
this.save();
|
|
return { success: true };
|
|
},
|
|
|
|
getTotal() {
|
|
return this.items.reduce((sum, item) => {
|
|
const price = parseFloat(item.price) || 0;
|
|
const quantity = parseInt(item.quantity) || 0;
|
|
return sum + price * quantity;
|
|
}, 0);
|
|
},
|
|
|
|
save() {
|
|
try {
|
|
localStorage.setItem("test_cart", JSON.stringify(this.items));
|
|
return true;
|
|
} catch (e) {
|
|
console.error("Save error:", e);
|
|
return false;
|
|
}
|
|
},
|
|
|
|
load() {
|
|
try {
|
|
const data = localStorage.getItem("test_cart");
|
|
this.items = data ? JSON.parse(data) : [];
|
|
|
|
if (!Array.isArray(this.items)) {
|
|
this.items = [];
|
|
}
|
|
|
|
// Sanitize
|
|
this.items = this.items.filter(
|
|
(item) => item && item.id && typeof item.price !== "undefined"
|
|
);
|
|
|
|
return true;
|
|
} catch (e) {
|
|
console.error("Load error:", e);
|
|
localStorage.removeItem("test_cart");
|
|
this.items = [];
|
|
return false;
|
|
}
|
|
},
|
|
|
|
clear() {
|
|
this.items = [];
|
|
localStorage.removeItem("test_cart");
|
|
},
|
|
};
|
|
|
|
// Load cart on page load
|
|
testCart.load();
|
|
|
|
// Test functions
|
|
function testInvalidProduct() {
|
|
const container = document.getElementById("invalid-results");
|
|
container.innerHTML = "";
|
|
|
|
const result1 = testCart.addItem({ name: "Test Product", price: 10.0 });
|
|
logStatus(
|
|
"No ID",
|
|
!result1.success && result1.error.includes("missing ID"),
|
|
"invalid-results"
|
|
);
|
|
|
|
const result2 = testCart.addItem(null);
|
|
logStatus("Null product", !result2.success, "invalid-results");
|
|
|
|
const result3 = testCart.addItem(undefined);
|
|
logStatus("Undefined product", !result3.success, "invalid-results");
|
|
}
|
|
|
|
function testInvalidPrice() {
|
|
const container = document.getElementById("invalid-results");
|
|
|
|
const result1 = testCart.addItem({
|
|
id: "test1",
|
|
name: "Test",
|
|
price: "invalid",
|
|
});
|
|
logStatus(
|
|
'String "invalid" price',
|
|
!result1.success,
|
|
"invalid-results"
|
|
);
|
|
|
|
const result2 = testCart.addItem({
|
|
id: "test2",
|
|
name: "Test",
|
|
price: -10,
|
|
});
|
|
logStatus("Negative price", !result2.success, "invalid-results");
|
|
|
|
const result3 = testCart.addItem({ id: "test3", name: "Test" });
|
|
logStatus("Missing price", !result3.success, "invalid-results");
|
|
}
|
|
|
|
function testMissingName() {
|
|
const container = document.getElementById("invalid-results");
|
|
|
|
const result = testCart.addItem({ id: "test-name", price: 10.0 });
|
|
logStatus(
|
|
"Missing name (uses fallback)",
|
|
result.success,
|
|
"invalid-results"
|
|
);
|
|
|
|
const item = testCart.items.find((i) => i.id === "test-name");
|
|
logStatus(
|
|
'Fallback name is "Product"',
|
|
item && item.name === "Product",
|
|
"invalid-results"
|
|
);
|
|
}
|
|
|
|
function testMissingImage() {
|
|
const container = document.getElementById("invalid-results");
|
|
|
|
const result = testCart.addItem({
|
|
id: "test-img",
|
|
name: "Test",
|
|
price: 10.0,
|
|
});
|
|
logStatus(
|
|
"Missing image (uses placeholder)",
|
|
result.success,
|
|
"invalid-results"
|
|
);
|
|
|
|
const item = testCart.items.find((i) => i.id === "test-img");
|
|
logStatus(
|
|
"Placeholder image set",
|
|
item && item.imageurl.includes("placeholder"),
|
|
"invalid-results"
|
|
);
|
|
}
|
|
|
|
function testStringId() {
|
|
const container = document.getElementById("type-results");
|
|
container.innerHTML = "";
|
|
|
|
testCart.clear();
|
|
const result = testCart.addItem({
|
|
id: "123",
|
|
name: "Test",
|
|
price: 10.0,
|
|
});
|
|
logStatus("String ID accepted", result.success, "type-results");
|
|
|
|
const found = testCart.items.find(
|
|
(i) => String(i.id) === String("123")
|
|
);
|
|
logStatus("String ID comparison works", !!found, "type-results");
|
|
}
|
|
|
|
function testNumberId() {
|
|
const container = document.getElementById("type-results");
|
|
|
|
testCart.clear();
|
|
const result = testCart.addItem({ id: 456, name: "Test", price: 10.0 });
|
|
logStatus("Number ID accepted", result.success, "type-results");
|
|
|
|
const found = testCart.items.find((i) => String(i.id) === String(456));
|
|
logStatus("Number ID comparison works", !!found, "type-results");
|
|
}
|
|
|
|
function testMixedIds() {
|
|
const container = document.getElementById("type-results");
|
|
|
|
testCart.clear();
|
|
testCart.addItem({ id: "789", name: "String ID", price: 10.0 });
|
|
testCart.addItem({ id: 789, name: "Number ID", price: 20.0 });
|
|
|
|
const stringItem = testCart.items.find((i) => String(i.id) === "789");
|
|
const numberItem = testCart.items.find(
|
|
(i) => String(i.id) === String(789)
|
|
);
|
|
|
|
logStatus(
|
|
"Mixed IDs both findable",
|
|
stringItem && numberItem,
|
|
"type-results"
|
|
);
|
|
logStatus(
|
|
"Treated as same ID (merged)",
|
|
testCart.items.length === 1,
|
|
"type-results"
|
|
);
|
|
}
|
|
|
|
function testZeroQuantity() {
|
|
const container = document.getElementById("quantity-results");
|
|
container.innerHTML = "";
|
|
|
|
testCart.clear();
|
|
const result = testCart.addItem(
|
|
{ id: "q1", name: "Test", price: 10.0 },
|
|
0
|
|
);
|
|
const item = testCart.items.find((i) => i.id === "q1");
|
|
|
|
logStatus(
|
|
"Zero quantity (enforced to 1)",
|
|
item && item.quantity === 1,
|
|
"quantity-results"
|
|
);
|
|
}
|
|
|
|
function testNegativeQuantity() {
|
|
const container = document.getElementById("quantity-results");
|
|
|
|
testCart.clear();
|
|
const result = testCart.addItem(
|
|
{ id: "q2", name: "Test", price: 10.0 },
|
|
-5
|
|
);
|
|
const item = testCart.items.find((i) => i.id === "q2");
|
|
|
|
logStatus(
|
|
"Negative quantity (enforced to 1)",
|
|
item && item.quantity === 1,
|
|
"quantity-results"
|
|
);
|
|
}
|
|
|
|
function testMaxQuantity() {
|
|
const container = document.getElementById("quantity-results");
|
|
|
|
testCart.clear();
|
|
testCart.addItem({ id: "q3", name: "Test", price: 10.0 }, 500);
|
|
testCart.addItem({ id: "q3", name: "Test", price: 10.0 }, 500);
|
|
|
|
const item = testCart.items.find((i) => i.id === "q3");
|
|
logStatus(
|
|
"Max quantity cap (999)",
|
|
item && item.quantity === 999,
|
|
"quantity-results"
|
|
);
|
|
}
|
|
|
|
function testCorruptedData() {
|
|
const container = document.getElementById("storage-results");
|
|
container.innerHTML = "";
|
|
|
|
localStorage.setItem("test_cart", "{invalid json}");
|
|
const loaded = testCart.load();
|
|
|
|
logStatus(
|
|
"Corrupted data recovery",
|
|
loaded && testCart.items.length === 0,
|
|
"storage-results"
|
|
);
|
|
}
|
|
|
|
function testNonArrayData() {
|
|
const container = document.getElementById("storage-results");
|
|
|
|
localStorage.setItem("test_cart", '{"not":"an array"}');
|
|
testCart.load();
|
|
|
|
logStatus(
|
|
"Non-array data (converted to [])",
|
|
Array.isArray(testCart.items),
|
|
"storage-results"
|
|
);
|
|
}
|
|
|
|
function testRecovery() {
|
|
const container = document.getElementById("storage-results");
|
|
|
|
localStorage.setItem(
|
|
"test_cart",
|
|
'[{"id":"valid","name":"Test","price":10,"quantity":1}]'
|
|
);
|
|
testCart.load();
|
|
|
|
logStatus(
|
|
"Valid data recovery",
|
|
testCart.items.length === 1,
|
|
"storage-results"
|
|
);
|
|
}
|
|
|
|
function testStringPrice() {
|
|
const container = document.getElementById("math-results");
|
|
container.innerHTML = "";
|
|
|
|
testCart.clear();
|
|
testCart.addItem({ id: "m1", name: "Test", price: "10.50" });
|
|
|
|
const total = testCart.getTotal();
|
|
logStatus("String price calculation", total === 10.5, "math-results");
|
|
}
|
|
|
|
function testNaNPrice() {
|
|
const container = document.getElementById("math-results");
|
|
|
|
// This should be rejected during add
|
|
const result = testCart.addItem({ id: "m2", name: "Test", price: NaN });
|
|
logStatus("NaN price rejected", !result.success, "math-results");
|
|
}
|
|
|
|
function testTotalCalculation() {
|
|
const container = document.getElementById("math-results");
|
|
|
|
testCart.clear();
|
|
testCart.addItem({ id: "t1", name: "Item 1", price: "10.50" }, 2);
|
|
testCart.addItem({ id: "t2", name: "Item 2", price: 5.25 }, 3);
|
|
|
|
const total = testCart.getTotal();
|
|
const expected = 10.5 * 2 + 5.25 * 3;
|
|
|
|
logStatus(
|
|
"Total calculation accurate",
|
|
Math.abs(total - expected) < 0.01,
|
|
"math-results"
|
|
);
|
|
log(
|
|
`Expected: $${expected.toFixed(2)}, Got: $${total.toFixed(2)}`,
|
|
"info",
|
|
"math-results"
|
|
);
|
|
}
|
|
|
|
function testRapidAdd() {
|
|
const container = document.getElementById("rapid-results");
|
|
container.innerHTML = "";
|
|
|
|
testCart.clear();
|
|
const start = Date.now();
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
testCart.addItem({ id: `rapid${i}`, name: `Item ${i}`, price: 10.0 });
|
|
}
|
|
|
|
const duration = Date.now() - start;
|
|
logStatus(
|
|
"Rapid 10x add operations",
|
|
testCart.items.length === 10,
|
|
"rapid-results"
|
|
);
|
|
log(`Completed in ${duration}ms`, "info", "rapid-results");
|
|
}
|
|
|
|
function testRapidRemove() {
|
|
const container = document.getElementById("rapid-results");
|
|
|
|
testCart.clear();
|
|
for (let i = 0; i < 5; i++) {
|
|
testCart.addItem({ id: `rem${i}`, name: `Item ${i}`, price: 10.0 });
|
|
}
|
|
|
|
const start = Date.now();
|
|
for (let i = 0; i < 5; i++) {
|
|
testCart.removeItem(`rem${i}`);
|
|
}
|
|
|
|
const duration = Date.now() - start;
|
|
logStatus(
|
|
"Rapid 5x remove operations",
|
|
testCart.items.length === 0,
|
|
"rapid-results"
|
|
);
|
|
log(`Completed in ${duration}ms`, "info", "rapid-results");
|
|
}
|
|
|
|
function testSimultaneous() {
|
|
const container = document.getElementById("rapid-results");
|
|
|
|
testCart.clear();
|
|
|
|
// Simulate simultaneous operations
|
|
Promise.all([
|
|
Promise.resolve(
|
|
testCart.addItem({ id: "s1", name: "Item 1", price: 10.0 })
|
|
),
|
|
Promise.resolve(
|
|
testCart.addItem({ id: "s2", name: "Item 2", price: 20.0 })
|
|
),
|
|
Promise.resolve(
|
|
testCart.addItem({ id: "s3", name: "Item 3", price: 30.0 })
|
|
),
|
|
]).then(() => {
|
|
logStatus(
|
|
"Simultaneous operations",
|
|
testCart.items.length === 3,
|
|
"rapid-results"
|
|
);
|
|
});
|
|
}
|
|
|
|
function displayCartState() {
|
|
const container = document.getElementById("cart-state");
|
|
container.innerHTML = "";
|
|
|
|
testCart.load();
|
|
|
|
log(`Total Items: ${testCart.items.length}`, "info", "cart-state");
|
|
log(
|
|
`Total Value: $${testCart.getTotal().toFixed(2)}`,
|
|
"info",
|
|
"cart-state"
|
|
);
|
|
|
|
if (testCart.items.length > 0) {
|
|
testCart.items.forEach((item) => {
|
|
log(
|
|
`${item.name} (ID: ${item.id}) - $${item.price} x ${
|
|
item.quantity
|
|
} = $${(item.price * item.quantity).toFixed(2)}`,
|
|
"info",
|
|
"cart-state"
|
|
);
|
|
});
|
|
} else {
|
|
log("Cart is empty", "info", "cart-state");
|
|
}
|
|
}
|
|
|
|
function clearCart() {
|
|
testCart.clear();
|
|
displayCartState();
|
|
}
|
|
|
|
// Auto-display cart on load
|
|
displayCartState();
|
|
</script>
|
|
</body>
|
|
</html>
|