fix(plus): 增强 checkout 免费试用校验

(cherry picked from commit c52f5058a4b5b68902bccb4d1f099d00c471b7bc)
This commit is contained in:
朴圣佑
2026-04-26 22:22:44 +08:00
committed by QLHazyCoder
parent 75e259ae5c
commit aa4e52b9a7
6 changed files with 652 additions and 59 deletions
+227
View File
@@ -180,6 +180,75 @@ return { findAddressSearchInput, isNonAddressSearchInput };
assert.equal(await api.findAddressSearchInput(), addressInput);
});
test('getCheckoutAmountSummary reads non-zero today due amount', () => {
const label = createElement({ tagName: 'DIV', text: '今日应付金额' });
const amount = createElement({ tagName: 'DIV', text: '€19.33' });
const row = createElement({ tagName: 'DIV', text: '今日应付金额 €19.33' });
label.parentElement = row;
amount.parentElement = row;
row.children = [label, amount];
row.parentElement = null;
const bundle = [
extractFunction('isVisibleElement'),
extractFunction('normalizeText'),
extractFunction('parseLocalizedAmount'),
extractFunction('getTextAfterTodayDueLabel'),
extractFunction('getVisibleControls'),
extractFunction('getCheckoutAmountSummary'),
'return { getCheckoutAmountSummary };',
].join('\n');
const api = new Function('window', 'document', bundle)(
{
getComputedStyle: () => ({ display: 'block', visibility: 'visible' }),
},
{
querySelectorAll: () => [label, amount, row],
}
);
const summary = api.getCheckoutAmountSummary();
assert.equal(summary.hasTodayDue, true);
assert.equal(summary.isZero, false);
assert.equal(summary.amount, 19.33);
assert.equal(summary.rawAmount, '€19.33');
});
test('getCheckoutAmountSummary accepts zero today due amount', () => {
const label = createElement({ tagName: 'DIV', text: '今日应付金额' });
const amount = createElement({ tagName: 'DIV', text: '€0.00' });
const row = createElement({ tagName: 'DIV', text: '今日应付金额 €0.00' });
label.parentElement = row;
amount.parentElement = row;
row.children = [label, amount];
row.parentElement = null;
const bundle = [
extractFunction('isVisibleElement'),
extractFunction('normalizeText'),
extractFunction('parseLocalizedAmount'),
extractFunction('getTextAfterTodayDueLabel'),
extractFunction('getVisibleControls'),
extractFunction('getCheckoutAmountSummary'),
'return { getCheckoutAmountSummary };',
].join('\n');
const api = new Function('window', 'document', bundle)(
{
getComputedStyle: () => ({ display: 'block', visibility: 'visible' }),
},
{
querySelectorAll: () => [label, amount, row],
}
);
const summary = api.getCheckoutAmountSummary();
assert.equal(summary.hasTodayDue, true);
assert.equal(summary.isZero, true);
assert.equal(summary.amount, 0);
});
test('isPayPalPaymentMethodActive requires a selected PayPal control', () => {
const bundle = [
extractFunction('isVisibleElement'),
@@ -234,6 +303,164 @@ return { isPayPalPaymentMethodActive };
assert.equal(api.isPayPalPaymentMethodActive(), true);
});
test('getStructuredAddressFields recognizes Stripe localized address field names', () => {
const bundle = [
extractFunction('isVisibleElement'),
extractFunction('normalizeText'),
extractFunction('getActionText'),
extractFunction('getFieldText'),
extractFunction('getVisibleControls'),
extractFunction('getVisibleTextInputs'),
extractFunction('findInputByFieldText'),
extractFunction('getStructuredAddressFields'),
].join('\n');
const address1 = createInput({ name: 'addressLine1', placeholder: '住所' });
const city = createInput({ name: 'locality', placeholder: '市区町村' });
const region = createInput({ name: 'administrativeArea', placeholder: '辖区' });
const postalCode = createInput({ name: 'postalCode', placeholder: '郵便番号' });
const inputs = [address1, city, region, postalCode];
const documentMock = {
querySelectorAll: (selector) => {
if (selector === 'input, textarea') return inputs;
if (String(selector || '').includes('label[for=')) return [];
return [];
},
};
const windowMock = {
getComputedStyle: () => ({ display: 'block', visibility: 'visible' }),
};
const cssMock = {
escape: (value) => String(value),
};
const api = new Function('window', 'document', 'CSS', `
${bundle}
return { getStructuredAddressFields };
`)(windowMock, documentMock, cssMock);
assert.deepEqual(api.getStructuredAddressFields(), {
address1,
address2: null,
city,
region,
postalCode,
});
});
test('findSubscribeButton prefers the submit subscription button', () => {
const bundle = [
extractFunction('isVisibleElement'),
extractFunction('normalizeText'),
extractFunction('getActionText'),
extractFunction('getSearchText'),
extractFunction('getFieldText'),
extractFunction('getCombinedSearchText'),
extractFunction('getVisibleControls'),
extractFunction('findClickableByText'),
extractFunction('isEnabledControl'),
extractFunction('findSubscribeButton'),
].join('\n');
const submitButton = createElement({
tagName: 'BUTTON',
text: '订阅',
attrs: {
'aria-label': '订阅',
type: 'submit',
},
});
submitButton.type = 'submit';
const genericButton = createElement({
tagName: 'BUTTON',
text: '继续',
attrs: {
type: 'button',
},
});
genericButton.type = 'button';
const documentMock = {
body: {},
documentElement: {},
querySelectorAll: (selector) => {
if (selector === 'button[type="submit"], input[type="submit"]') return [submitButton];
if (selector === 'button, a, [role="button"], input[type="button"], input[type="submit"], [tabindex]') {
return [genericButton, submitButton];
}
if (String(selector || '').includes('label[for=')) return [];
return [];
},
};
const windowMock = {
getComputedStyle: () => ({ display: 'block', visibility: 'visible' }),
};
const cssMock = {
escape: (value) => String(value),
};
const api = new Function('window', 'document', 'CSS', `
${bundle}
return { findSubscribeButton };
`)(windowMock, documentMock, cssMock);
assert.equal(api.findSubscribeButton(), submitButton);
});
test('humanLikeClick submits a detached submit button through its form attribute', async () => {
const bundle = [
'function throwIfStopped() {}',
'function sleep() { return Promise.resolve(); }',
'function summarizeElementForDebug() { return {}; }',
'function log() {}',
extractFunction('normalizeText'),
extractFunction('getActionText'),
extractFunction('getAssociatedForm'),
extractFunction('humanLikeClick'),
].join('\n');
let submittedWith = null;
const form = {
requestSubmit(button) {
submittedWith = button;
},
};
const button = createElement({
tagName: 'BUTTON',
text: '订阅',
attrs: {
form: '_r_l_',
type: 'submit',
'aria-label': '订阅',
},
});
button.type = 'submit';
button.scrollIntoView = () => {};
button.focus = () => {};
button.dispatchEvent = () => true;
button.click = () => {};
const documentMock = {
getElementById: (id) => (id === '_r_l_' ? form : null),
};
const windowMock = {};
class FakeMouseEvent {
constructor(type, init = {}) {
this.type = type;
Object.assign(this, init);
}
}
const api = new Function('window', 'document', 'MouseEvent', 'PointerEvent', 'console', `
${bundle}
return { humanLikeClick };
`)(windowMock, documentMock, FakeMouseEvent, undefined, { log() {} });
await api.humanLikeClick(button);
assert.equal(submittedWith, button);
});
test('selectRegionDropdown opens the state dropdown and clicks the matching option', async () => {
const bundle = [
'function throwIfStopped() {}',