console verify: an older page's long labelled radio still counts from page text when the sheet answers Yes / No; tests and the teaser follow the new sign-in details question and the added App Privacy step

This commit is contained in:
CRTkafa 2026-10-21 05:39:45 +07:00
parent b6ea9159d8
commit 327a85e042
3 changed files with 10 additions and 7 deletions

View file

@ -388,6 +388,9 @@ export function verifyPlayDeclaration(form: Exclude<PlanOnlyFormId, 'play-app-co
: { item: a.question, expected: `${labels[0]} ${wantOn ? 'checked' : 'unchecked'}`, seen: 'no control with that label', status: 'not-found' });
continue;
}
// an older page shows a long labelled radio instead of Yes / No: read that from text when it is there
const older = a.control === 'radio' && a.action !== 'untick' ? (a.altLabels ?? []).find(l => !/^(yes|no)$/i.test(l) && blob.includes(fold(l))) : undefined;
if (older) { rows.push({ item: a.question, expected: want, seen: older, status: 'match' }); continue; }
const plain = !want || /^(yes|no)$/i.test(want) || a.action === 'untick';
if (plain) { rows.push({ item: a.question, expected: a.text, seen: 'not readable from page text', status: 'not-found', note: 'pass the input states: run the plan\'s verify.capture snippet and `--from -`' }); continue; }
const shownAs = a.action === 'type' || a.action === 'choose' ? (blob.includes(fold(want)) ? want : null) : labels.find(l => blob.includes(fold(l))) ?? null;

View file

@ -136,9 +136,9 @@ export const PRIVACY_ANSWERS = [
export const PRIVACY_STEPS = [
' 1. Open App Store Connect > Apps > the app > App Privacy (sidebar): https://appstoreconnect.apple.com/apps',
' 2. First time: click "Get Started" and answer the Data Collection question; then Next. [click: "Get Started"]',
' 5. "Email Address" step 1, purposes: tick and untick the 6 checkboxes as answered, then Next. [click: "Next"]',
' 6. "Email Address" step 2, linked to the user: pick the radio, then Next. [click: "Next"]',
' 8. "Email Address" step 5, tracking: pick the radio, then Save (a draft). Never "Definitions and Examples". The dialog closes; the row shows Edit and the summary. [click: "Save"]',
' 6. "Email Address" step 1, purposes: tick and untick the 6 checkboxes as answered, then Next. [click: "Next"]',
' 7. "Email Address" step 2, linked to the user: pick the radio, then Next. [click: "Next"]',
' 9. "Email Address" step 5, tracking: pick the radio, then Save (a draft). Never "Definitions and Examples". The dialog closes; the row shows Edit and the summary. [click: "Save"]',
];
export const DATA_SAFETY_CSV = [
'PSL_DATA_COLLECTION_COLLECTS_PERSONAL_DATA,,TRUE,REQUIRED,Does your app collect or share any of the required user data types?',

View file

@ -57,7 +57,7 @@ describe('check: guest-play apps that Play may read as a login wall', () => {
describe('plan play-sign-in-details: guest steps', () => {
test('instructions with no sign-in: restricted, one entry without credentials, the box is the owner\'s', () => {
const p = consolePlan('play-sign-in-details', guest);
expect(p.answers.find(a => a.id === 'access')).toMatchObject({ value: true, status: 'certain', label: 'All or some functionality in my app is restricted' });
expect(p.answers.find(a => a.id === 'access')).toMatchObject({ value: true, status: 'certain', label: 'Yes' });
expect(p.answers.find(a => a.id === 'entryName')).toMatchObject({ value: GUEST_ENTRY_NAME, status: 'certain', action: 'type' });
expect(p.answers.find(a => a.id === 'credentials')).toMatchObject({ value: null, status: 'certain', text: 'leave both empty (no account)' });
expect(p.answers.find(a => a.id === 'credentials')!.action).toBeUndefined();
@ -65,7 +65,7 @@ describe('plan play-sign-in-details: guest steps', () => {
expect(p.answers.find(a => a.id === 'fullAccess')).toMatchObject({ status: 'agreement', value: null, control: 'checkbox', label: PLAY_FULL_ACCESS_BOX });
expect(p.answers.find(a => a.id === 'fullAccess')!.action).toBeUndefined();
// the agent presses Add instructions, Add and Save; the box and Send changes for review are the owner's
expect(p.buttons.allowed.map(b => b.button)).toEqual(expect.arrayContaining(['Add instructions', 'Add', 'Save']));
expect(p.buttons.allowed.map(b => b.button)).toEqual(expect.arrayContaining(['Add details', 'Add', 'Save']));
expect(p.buttons.final).toBe('Send changes for review');
expect(p.buttons.neverPress).toContain('Send changes for review');
expect(p.ownerBatch.map(o => [o.when, o.kind])).toEqual([['after', 'agreement'], ['after', 'final']]);
@ -80,7 +80,7 @@ describe('plan play-sign-in-details: guest steps', () => {
test('verify reads the entry name and the steps back, never the box', () => {
const page = `Sign in details\nAll or some functionality in my app is restricted\n${GUEST_ENTRY_NAME}\n${STEPS}`;
const r = verifyPlayDeclaration('play-sign-in-details', guest, page);
expect(r.rows.filter(x => x.status === 'match').map(x => x.item)).toEqual(['Is any functionality restricted (sign-in, membership, location)?', 'Name of the instructions entry', 'Instructions for the reviewer']);
expect(r.rows.filter(x => x.status === 'match').map(x => x.item)).toEqual(['Is any part of your app restricted?', 'Name of the instructions entry', 'Instructions for the reviewer']);
expect(r.rows.some(x => /full access/.test(x.item))).toBe(false);
expect(r.mismatches).toBe(0);
});
@ -93,7 +93,7 @@ describe('plan play-sign-in-details: guest steps', () => {
test('no instructions: the "No restrictions" path stays', () => {
const p = consolePlan('play-sign-in-details', { ...base, reviewAccess: { signInRequired: false } } as ShipConfig);
expect(p.answers.map(a => [a.id, a.value])).toEqual([['access', false]]);
expect(p.answers[0]!.label).toBe('All functionality in my app is available without any access restrictions');
expect(p.answers[0]!.label).toBe('No');
expect(p.buttons.allowed.map(b => b.button)).not.toContain('Add');
expect(p.ownerBatch.map(o => o.kind)).toEqual(['final']);
});