// Run on the local contact preview with agent-browser eval. All requests are mocked. (async function () { if (!location.hostname.match(/^(127\.0\.0\.1|localhost)$/)) throw new Error('Use local preview only'); var form = document.querySelector('.ct-form'); var submit = form.querySelector('.ct-submit'); var status = form.querySelector('.ct-submit-status'); var originalFetch = window.fetch; var calls = []; var mode = 'reject'; var results = []; function assert(condition, name) { if (!condition) throw new Error(name); results.push(name); } window.fetch = async function (url, options) { calls.push({url: String(url), method: options.method || 'GET', body: options.body || ''}); if (!options.method) { if (mode === 'token-fail') throw new Error('network'); return {ok: true, json: async function () { return {code: 1, data: 'test-single-use-token'}; }}; } if (mode === 'network-fail') throw new Error('network'); await new Promise(function (resolve) { setTimeout(resolve, 10); }); return {ok: true, json: async function () { return mode === 'success' ? {code: 1} : {code: 0, msg: '验证码不正确'}; }}; }; function fill() { form.elements.title.value = '本地界面测试'; form.elements.name.value = '测试'; form.elements.phone.value = '0851-12345678'; form.elements.content.value = '本地模拟数据,不会向线上发送。'; if (form.elements.captcha) form.elements.captcha.value = 'test'; } function send() { form.dispatchEvent(new Event('submit', {bubbles: true, cancelable: true})); } async function settled() { for (var i = 0; i < 100; i++) { if (form.getAttribute('aria-busy') !== 'true') return; await new Promise(function (resolve) { setTimeout(resolve, 5); }); } throw new Error('Form did not settle'); } try { form.reset(); send(); assert(calls.length === 0 && document.activeElement === form.elements.title, 'Empty form: focus first error, no request'); form.querySelector('[data-topic]').click(); assert(form.elements.title.value === form.querySelector('[data-topic]').dataset.topic, 'Topic button fills title'); fill(); form.elements.phone.value = '123'; send(); assert(calls.length === 0 && form.elements.phone.getAttribute('aria-invalid') === 'true', 'Invalid phone blocked'); fill(); send(); send(); assert(submit.disabled, 'Submitting state disables button'); await settled(); assert(calls.filter(function (c) { return c.method === 'POST'; }).length === 1, 'Duplicate submit sends one POST'); var body = new URLSearchParams(calls.find(function (c) { return c.method === 'POST'; }).body); assert(body.get('__token__') === 'test-single-use-token' && body.get('phone') === '0851-12345678', 'Uses fresh token and expected backend fields'); assert(status.dataset.state === 'error' && form.elements.content.value.length > 0 && !submit.disabled, 'Server rejection preserves fields and permits retry'); if (form.elements.captcha) assert(form.elements.captcha.value === '', 'Rejected submission refreshes captcha'); calls = []; fill(); mode = 'success'; send(); await settled(); assert(status.dataset.state === 'success' && form.elements.name.value === '' && form.elements.content.value === '', 'Confirmed success clears fields and announces result'); assert(calls.length === 2 && calls[0].method === 'GET', 'Retry obtains a new token'); calls = []; fill(); mode = 'network-fail'; send(); await settled(); assert(status.dataset.state === 'error' && status.textContent.includes('无法确认提交结果') && form.elements.content.value.length > 0, 'Unknown POST outcome preserves data without automatic retry'); assert(calls.length === 2 && !submit.disabled, 'Network failure releases button'); calls = []; fill(); mode = 'token-fail'; send(); await settled(); assert(calls.length === 1 && status.dataset.state === 'error', 'Token fetch failure never sends POST'); return {passed: results.length, checks: results, network: 'Mocked locally; no real messages sent'}; } finally { window.fetch = originalFetch; } })();