/** The live UI and real tests import this same module. Broken branches are teaching fixtures. */ export function createLab(variant = 'fixed') { if (!['fixed', 'broken'].includes(variant)) throw new Error('Unknown implementation'); const broken = variant === 'broken'; return { total(priceCents, quantity, discountPercent = 0) { if (!Number.isSafeInteger(priceCents) || priceCents < 0 || !Number.isInteger(quantity) || quantity < 1 || quantity > 100 || !Number.isSafeInteger(priceCents * quantity) || !Number.isFinite(discountPercent) || discountPercent < 0 || discountPercent > 100) throw new RangeError('Некорректная цена, количество или скидка'); if (broken) return Math.round(priceCents * quantity * (1 - discountPercent / 100)) - 1; return Math.round(priceCents * quantity * (1 - discountPercent / 100)); }, validateEmail(value) { if (typeof value !== 'string') return false; const email = broken ? value : value.trim(); return broken ? email.includes('@') : /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); }, async loadCustomer(fetcher, url, signal) { const response = await fetcher(url, { signal }); if (!broken && !response.ok) throw new Error(`HTTP ${response.status}`); const customer = await response.json(); if (!broken && (typeof customer.name !== 'string' || typeof customer.id !== 'number')) throw new TypeError('Некорректный клиент'); return customer; }, async saveOrder(client, email, cents) { if (!this.validateEmail(email)) throw new TypeError('Укажите корректный email'); return client.post('/orders', { email: broken ? email : email.trim(), totalCents: broken ? cents / 100 : cents }); }, debounceSearch(callback, delay = 300) { let timer = null; const pending = new Set(); const run = (query) => { if (!broken && timer !== null) { clearTimeout(timer); pending.delete(timer); } const id = setTimeout(() => { pending.delete(id); callback(query); }, delay); timer = id; pending.add(id); }; run.cancel = () => { for (const id of pending) clearTimeout(id); pending.clear(); timer = null; }; return run; }, mountContactForm(root) { root.innerHTML = '
'; const form = root.querySelector('form'), input = root.querySelector('input'), status = root.querySelector('[role=status]'); const submit = (event) => { event.preventDefault(); const valid = this.validateEmail(input.value); if (!valid && !broken) { status.textContent = 'Укажите корректный email'; input.setAttribute('aria-invalid', 'true'); input.focus(); return; } input.removeAttribute('aria-invalid'); status.textContent = 'Сохранено: ' + input.value.trim(); }; form.addEventListener('submit', submit); return () => { form.removeEventListener('submit', submit); root.replaceChildren(); }; }, mountDisclosure(root) { root.innerHTML = '