From 75da15edffe51214f2e9db6d746ea097a7e0a7ff Mon Sep 17 00:00:00 2001 From: Abner <22141172+Silentely@users.noreply.github.com> Date: Thu, 28 May 2026 20:12:19 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=E4=BF=AE=E5=A4=8D=20PR?= =?UTF-8?q?=20=E8=AF=84=E5=AE=A1=E5=8F=91=E7=8E=B0=E7=9A=84=2011=20?= =?UTF-8?q?=E9=A1=B9=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - data.data 空值保护:data?.data?.eSims 防止 TypeError - directFetchMode 移至成功路径设置,失败时重置为 false - SMS 卡片切换时隐藏 directFetchSection,避免双内联区同时可见 - modeBadge 直取模式下隐藏"设备更换"徽标 - tl() 文案迁移至 LITERAL_TRANSLATIONS,修复英文环境回退中文 - "拉取中"状态类型从 success 改为 info - SSN radio 查询限定到 picker 容器内 - jest.config 补全 .spec.js 排除规则 - innerHTML 改用 DOM API 构建(pullBtn + renderSsnPicker) - 测试 fail() 替换为 expect().rejects 惯用写法 --- jest.config.js | 2 + src/giffgaff/js/giffgaff-app.js | 60 ++++++++++++++++-------- src/giffgaff/js/modules/esim-service.js | 6 +-- src/giffgaff/js/modules/ui-controller.js | 3 +- src/js/modules/i18n-data.js | 12 ++--- tests/giffgaff/direct-fetch.test.js | 20 +++----- 6 files changed, 59 insertions(+), 44 deletions(-) diff --git a/jest.config.js b/jest.config.js index a495c6b..6250fa5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -32,7 +32,9 @@ module.exports = { '!src/js/modules/**/*.test.js', '!src/js/modules/**/*.spec.js', '!src/giffgaff/js/modules/**/*.test.js', + '!src/giffgaff/js/modules/**/*.spec.js', '!src/simyo/js/modules/**/*.test.js', + '!src/simyo/js/modules/**/*.spec.js', '!**/node_modules/**', '!**/dist/**' ], diff --git a/src/giffgaff/js/giffgaff-app.js b/src/giffgaff/js/giffgaff-app.js index 6399459..224acc7 100644 --- a/src/giffgaff/js/giffgaff-app.js +++ b/src/giffgaff/js/giffgaff-app.js @@ -340,7 +340,9 @@ class GiffgaffApp { const handler = () => { const smsSection = document.getElementById('smsInlineSection'); const manualBlock = document.getElementById('manualBlock'); + const directSection = document.getElementById('directFetchSection'); if (manualBlock) manualBlock.style.display = 'none'; + if (directSection) directSection.style.display = 'none'; if (smsSection) { smsSection.style.display = 'block'; smsSection.scrollIntoView({behavior:'smooth', block:'center'}); @@ -468,8 +470,12 @@ class GiffgaffApp { try { pullBtn.disabled = true; - pullBtn.innerHTML = ` ${tl('拉取中...')}`; - uiController.showStatus(statusEl, t('giffgaff.directFetch.status.fetching'), 'success'); + pullBtn.replaceChildren(); + const spinner = document.createElement('span'); + spinner.className = 'loading'; + pullBtn.appendChild(spinner); + pullBtn.append(' ' + tl('拉取中...')); + uiController.showStatus(statusEl, t('giffgaff.directFetch.status.fetching'), 'info'); await esimService.directFetchFlow(preselectedSsn); uiController.showStatus(statusEl, t('giffgaff.directFetch.status.success'), 'success'); @@ -479,6 +485,7 @@ class GiffgaffApp { uiController.showESimResult(); }, 800); } catch (error) { + stateManager.set('directFetchMode', false); if (error.code === 'MULTIPLE_ESIMS') { this.renderSsnPicker(error.candidates); uiController.showStatus(statusEl, t('giffgaff.directFetch.errors.multipleNeedPick'), 'info'); @@ -489,7 +496,11 @@ class GiffgaffApp { } } finally { pullBtn.disabled = false; - pullBtn.innerHTML = ` ${tl('拉取我的 eSIM')}`; + pullBtn.replaceChildren(); + const icon = document.createElement('i'); + icon.className = 'fas fa-bolt me-2'; + pullBtn.appendChild(icon); + pullBtn.append(' ' + tl('拉取我的 eSIM')); } } @@ -500,26 +511,37 @@ class GiffgaffApp { const container = document.getElementById('directFetchSsnPicker'); if (!container) return; - container.innerHTML = candidates.map((ssn, i) => { - const safeSsn = HtmlSanitizer.escapeHtml(ssn); - return ` -
- - -
`; - }).join('') + ` - - `; + container.replaceChildren(); + candidates.forEach((ssn, i) => { + const row = document.createElement('div'); + row.className = 'form-check'; + const input = document.createElement('input'); + input.className = 'form-check-input'; + input.type = 'radio'; + input.name = 'directFetchSsn'; + input.id = `ssn-${i}`; + input.value = String(ssn); + if (i === 0) input.checked = true; + const label = document.createElement('label'); + label.className = 'form-check-label'; + label.htmlFor = input.id; + const code = document.createElement('code'); + code.textContent = String(ssn); + label.appendChild(code); + row.append(input, label); + container.appendChild(row); + }); + const confirmBtn = document.createElement('button'); + confirmBtn.id = 'directFetchSsnConfirmBtn'; + confirmBtn.className = 'btn btn-info mt-2'; + confirmBtn.textContent = tl('使用选定的 eSIM'); + container.appendChild(confirmBtn); container.style.display = 'block'; - const confirmBtn = document.getElementById('directFetchSsnConfirmBtn'); - if (confirmBtn && !confirmBtn.__bound) { + if (!confirmBtn.__bound) { confirmBtn.__bound = true; confirmBtn.addEventListener('click', () => { - const selected = document.querySelector('input[name="directFetchSsn"]:checked')?.value; + const selected = container.querySelector('input[name="directFetchSsn"]:checked')?.value; if (!selected) return; container.style.display = 'none'; this.handleDirectFetchPull(selected); diff --git a/src/giffgaff/js/modules/esim-service.js b/src/giffgaff/js/modules/esim-service.js index 83be1a4..94e740c 100644 --- a/src/giffgaff/js/modules/esim-service.js +++ b/src/giffgaff/js/modules/esim-service.js @@ -409,7 +409,7 @@ export class ESimService { throw new Error(errorObj?.message || errorObj?.error || JSON.stringify(errorObj)); } - return data.data.eSims || []; + return data?.data?.eSims || []; } /** @@ -417,7 +417,6 @@ export class ESimService { * @param {string|null} preselectedSsn - 已选定的 SSN(多选场景) */ async directFetchFlow(preselectedSsn = null) { - stateManager.set('directFetchMode', true); const list = await this.fetchExistingESims(); if (list.length === 0) { @@ -445,7 +444,8 @@ export class ESimService { stateManager.setState({ esimSSN: ssn, esimDeliveryStatus: 'DOWNLOADABLE', - lpaString: tokenResult.lpaString + lpaString: tokenResult.lpaString, + directFetchMode: true }); return { success: true, ssn, lpaString: tokenResult.lpaString }; diff --git a/src/giffgaff/js/modules/ui-controller.js b/src/giffgaff/js/modules/ui-controller.js index d57ab25..b22a80d 100644 --- a/src/giffgaff/js/modules/ui-controller.js +++ b/src/giffgaff/js/modules/ui-controller.js @@ -174,7 +174,8 @@ export class UIController { } if (this.elements.modeBadge) { - this.elements.modeBadge.style.display = state.isDeviceChange ? 'inline-flex' : 'none'; + this.elements.modeBadge.style.display = + state.isDeviceChange && !state.directFetchMode ? 'inline-flex' : 'none'; } // Access Token diff --git a/src/js/modules/i18n-data.js b/src/js/modules/i18n-data.js index 3ef3473..80dd3cb 100644 --- a/src/js/modules/i18n-data.js +++ b/src/js/modules/i18n-data.js @@ -466,10 +466,6 @@ export const TRANSLATIONS = { 'giffgaff.directFetch.errors.empty': '未在您的账户上发现可下载的 eSIM。如确认已在官方 App 完成 10 英镑付款,请联系 Giffgaff 客服核实订单状态。', 'giffgaff.directFetch.errors.multipleNeedPick': '检测到多个可下载的 eSIM,请选择一个继续', 'giffgaff.directFetch.errors.generic': '直取 eSIM 失败:{message}', - '已购用户获取eSIM(仅限新购)': '已购用户获取eSIM(仅限新购)', - '拉取中...': '拉取中...', - '拉取我的 eSIM': '拉取我的 eSIM', - '使用选定的 eSIM': '使用选定的 eSIM', 'simyo.meta.title': 'Simyo eSIM 工具', 'simyo.meta.description': 'Simyo eSIM 工具,支持设备更换、邮箱验证码、二维码生成与安装确认,帮助您快速管理荷兰 Simyo eSIM。', @@ -730,10 +726,6 @@ export const TRANSLATIONS = { 'giffgaff.directFetch.errors.empty': 'No downloadable eSIM found on your account. If you have completed the £10 payment in the official app, please contact Giffgaff support to verify your order.', 'giffgaff.directFetch.errors.multipleNeedPick': 'Multiple downloadable eSIMs detected — please pick one to continue', 'giffgaff.directFetch.errors.generic': 'Direct fetch failed: {message}', - '已购用户获取eSIM(仅限新购)': 'Paid user eSIM fetch (new-purchase only)', - '拉取中...': 'Fetching...', - '拉取我的 eSIM': 'Fetch my eSIM', - '使用选定的 eSIM': 'Use selected eSIM', 'simyo.meta.title': 'Simyo eSIM Tool', 'simyo.meta.description': 'Simyo eSIM toolkit with device change, email verification, QR generation, and installation confirmation for Dutch Simyo users.', @@ -939,4 +931,8 @@ export const LITERAL_TRANSLATIONS = { '是否继续操作?': 'Continue anyway?', '已为您预留 eSIM(状态 RESERVED)。请保持本页面开启,前往 giffgaff 激活页 手动输入上方激活码并点击 "Activate your SIM",随后确认 "Yes, I want to replace my SIM"。完成后返回本页点击"获取 eSIM Token"继续。': 'Your eSIM has been reserved (status: RESERVED). Keep this page open, go to the giffgaff activation page, enter the activation code above and click "Activate your SIM", then confirm "Yes, I want to replace my SIM". Return here and click "Fetch eSIM token" to continue.', 'SIM 交换服务窗口:英国时间 04:30 至 21:30。您仍可浏览信息,部分操作可能失败。': 'SIM swap window: UK time 04:30–21:30. You can still browse, but some operations may fail.', + '已购用户获取eSIM(仅限新购)': 'Paid user eSIM fetch (new-purchase only)', + '拉取中...': 'Fetching...', + '拉取我的 eSIM': 'Fetch my eSIM', + '使用选定的 eSIM': 'Use selected eSIM', }; diff --git a/tests/giffgaff/direct-fetch.test.js b/tests/giffgaff/direct-fetch.test.js index 6bd4731..c14a3c9 100644 --- a/tests/giffgaff/direct-fetch.test.js +++ b/tests/giffgaff/direct-fetch.test.js @@ -149,13 +149,10 @@ describe('ESimService - directFetchFlow', () => { }) }); - try { - await esimService.directFetchFlow(); - fail('应抛出 MULTIPLE_ESIMS 错误'); - } catch (error) { - expect(error.code).toBe('MULTIPLE_ESIMS'); - expect(error.candidates).toEqual(['A', 'B']); - } + await expect(esimService.directFetchFlow()).rejects.toMatchObject({ + code: 'MULTIPLE_ESIMS', + candidates: ['A', 'B'] + }); }); test('多 eSIM 已选定 preselectedSsn 时直接使用该 ssn', async () => { @@ -191,12 +188,9 @@ describe('ESimService - directFetchFlow', () => { }) }); - try { - await esimService.directFetchFlow(); - fail('应抛出 EMPTY_LIST 错误'); - } catch (error) { - expect(error.code).toBe('EMPTY_LIST'); - } + await expect(esimService.directFetchFlow()).rejects.toMatchObject({ + code: 'EMPTY_LIST' + }); }); }); });