mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-03 06:24:20 +08:00
🐛 fix: 修复 PR 评审发现的 11 项问题
- 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 惯用写法
This commit is contained in:
@@ -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/**'
|
||||
],
|
||||
|
||||
@@ -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 = `<span class="loading"></span> ${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 = `<i class="fas fa-bolt me-2"></i> ${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 `
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="directFetchSsn" id="ssn-${i}"
|
||||
value="${safeSsn}" ${i === 0 ? 'checked' : ''}>
|
||||
<label class="form-check-label" for="ssn-${i}"><code>${safeSsn}</code></label>
|
||||
</div>`;
|
||||
}).join('') + `
|
||||
<button id="directFetchSsnConfirmBtn" class="btn btn-info mt-2">
|
||||
<i class="fas fa-check me-2"></i>${tl('使用选定的 eSIM')}
|
||||
</button>
|
||||
`;
|
||||
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);
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)。请保持本页面开启,前往 <a href="https://www.giffgaff.com/activate" target="_blank" rel="noopener">giffgaff 激活页</a> 手动输入上方激活码并点击 "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 <a href="https://www.giffgaff.com/activate" target="_blank" rel="noopener">giffgaff activation page</a>, 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',
|
||||
};
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user