diff --git a/background.js b/background.js index dfd2bee..5b0646c 100644 --- a/background.js +++ b/background.js @@ -4385,10 +4385,16 @@ async function skipStep(step) { if (step === 1) { const latestState = await getState(); - const step2Status = latestState.stepStatuses?.[2]; - if (!isStepDoneStatus(step2Status) && step2Status !== 'running') { - await setStepStatus(2, 'skipped'); - await addLog('步骤 1 已跳过,步骤 2 也已同时跳过。', 'warn'); + const skippedSteps = []; + for (let linkedStep = 2; linkedStep <= 5; linkedStep += 1) { + const linkedStatus = latestState.stepStatuses?.[linkedStep]; + if (!isStepDoneStatus(linkedStatus) && linkedStatus !== 'running') { + await setStepStatus(linkedStep, 'skipped'); + skippedSteps.push(linkedStep); + } + } + if (skippedSteps.length) { + await addLog(`步骤 1 已跳过,步骤 ${skippedSteps.join('、')} 也已同时跳过。`, 'warn'); } } diff --git a/tests/background-skip-step-linking.test.js b/tests/background-skip-step-linking.test.js new file mode 100644 index 0000000..7c0e450 --- /dev/null +++ b/tests/background-skip-step-linking.test.js @@ -0,0 +1,160 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); + +const source = fs.readFileSync('background.js', 'utf8'); + +function extractFunction(name) { + const markers = [`async function ${name}(`, `function ${name}(`]; + const start = markers + .map((marker) => source.indexOf(marker)) + .find((index) => index >= 0); + if (start < 0) { + throw new Error(`missing function ${name}`); + } + + let parenDepth = 0; + let signatureEnded = false; + let braceStart = -1; + for (let i = start; i < source.length; i += 1) { + const ch = source[i]; + if (ch === '(') { + parenDepth += 1; + } else if (ch === ')') { + parenDepth -= 1; + if (parenDepth === 0) { + signatureEnded = true; + } + } else if (ch === '{' && signatureEnded) { + braceStart = i; + break; + } + } + + let depth = 0; + let end = braceStart; + for (; end < source.length; end += 1) { + const ch = source[end]; + if (ch === '{') depth += 1; + if (ch === '}') { + depth -= 1; + if (depth === 0) { + end += 1; + break; + } + } + } + + return source.slice(start, end); +} + +test('skipStep cascades from step 1 to step 5 when downstream steps are pending', async () => { + const bundle = [ + extractFunction('isStepDoneStatus'), + extractFunction('skipStep'), + ].join('\n'); + + const statuses = { + 1: 'pending', + 2: 'pending', + 3: 'pending', + 4: 'pending', + 5: 'pending', + 6: 'pending', + 7: 'pending', + 8: 'pending', + 9: 'pending', + 10: 'pending', + }; + + const events = { + setStepStatusCalls: [], + logs: [], + }; + + const api = new Function(` +const STEP_IDS = [1,2,3,4,5,6,7,8,9,10]; +${bundle} +return { skipStep }; +`)(); + + globalThis.ensureManualInteractionAllowed = async () => ({ + stepStatuses: { ...statuses }, + }); + globalThis.getState = async () => ({ + stepStatuses: { ...statuses }, + }); + globalThis.setStepStatus = async (step, status) => { + events.setStepStatusCalls.push({ step, status }); + statuses[step] = status; + }; + globalThis.addLog = async (message, level) => { + events.logs.push({ message, level }); + }; + + const result = await api.skipStep(1); + + assert.deepStrictEqual(result, { ok: true, step: 1, status: 'skipped' }); + assert.deepStrictEqual(events.setStepStatusCalls, [ + { step: 1, status: 'skipped' }, + { step: 2, status: 'skipped' }, + { step: 3, status: 'skipped' }, + { step: 4, status: 'skipped' }, + { step: 5, status: 'skipped' }, + ]); + assert.equal(events.logs[0]?.message, '步骤 1 已跳过'); + assert.equal(events.logs[1]?.message, '步骤 1 已跳过,步骤 2、3、4、5 也已同时跳过。'); +}); + +test('skipStep from step 1 skips only unfinished steps up to step 5', async () => { + const bundle = [ + extractFunction('isStepDoneStatus'), + extractFunction('skipStep'), + ].join('\n'); + + const statuses = { + 1: 'pending', + 2: 'completed', + 3: 'running', + 4: 'pending', + 5: 'manual_completed', + 6: 'pending', + 7: 'pending', + 8: 'pending', + 9: 'pending', + 10: 'pending', + }; + + const events = { + setStepStatusCalls: [], + logs: [], + }; + + const api = new Function(` +const STEP_IDS = [1,2,3,4,5,6,7,8,9,10]; +${bundle} +return { skipStep }; +`)(); + + globalThis.ensureManualInteractionAllowed = async () => ({ + stepStatuses: { ...statuses }, + }); + globalThis.getState = async () => ({ + stepStatuses: { ...statuses }, + }); + globalThis.setStepStatus = async (step, status) => { + events.setStepStatusCalls.push({ step, status }); + statuses[step] = status; + }; + globalThis.addLog = async (message, level) => { + events.logs.push({ message, level }); + }; + + await api.skipStep(1); + + assert.deepStrictEqual(events.setStepStatusCalls, [ + { step: 1, status: 'skipped' }, + { step: 4, status: 'skipped' }, + ]); + assert.equal(events.logs.some(({ message }) => message === '步骤 1 已跳过,步骤 4 也已同时跳过。'), true); +}); diff --git a/项目完整链路说明.md b/项目完整链路说明.md index b2a8a95..19eccbb 100644 --- a/项目完整链路说明.md +++ b/项目完整链路说明.md @@ -195,6 +195,10 @@ 2. 等待注册入口页内容脚本就绪 3. 标记 Step 1 完成 +补充: + +- 手动点击“跳过步骤 1”时,会级联将步骤 2~5 标记为跳过;其中已完成或正在运行的步骤不会被改写。 + ### Step 2 文件: diff --git a/项目文件结构说明.md b/项目文件结构说明.md index 7fe76d3..583bdf0 100644 --- a/项目文件结构说明.md +++ b/项目文件结构说明.md @@ -138,6 +138,7 @@ - `tests/background-navigation-utils-module.test.js`:测试导航工具模块已接入且导出工厂。 - `tests/background-panel-bridge-module.test.js`:测试面板桥接模块已接入且导出工厂。 - `tests/background-signup-flow-module.test.js`:测试注册页辅助模块已接入且导出工厂。 +- `tests/background-skip-step-linking.test.js`:测试手动跳过步骤 1 时,会级联跳过步骤 2~5,并仅跳过其中未完成且未运行的步骤。 - `tests/background-signup-step2-branching.test.js`:测试在 Gmail / 2925 模式下,已有兼容别名邮箱时应直接复用,不应再次重生成。 - `tests/background-step-modules.test.js`:测试步骤模块文件都已由后台入口加载。 - `tests/background-step5-submit-short-circuit.test.js`:测试步骤 5 会把生成好的资料直接转发给内容脚本,并依赖完成信号收尾。