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'
+ });
});
});
});