Refine vault defaults and account settings

This commit is contained in:
2026-10-13 14:20:17 +09:00
parent 8a8df417aa
commit 50e54bc4c5
10 changed files with 351 additions and 75 deletions

View File

@@ -300,12 +300,10 @@ try {
.getByRole('button', { name: '저장', exact: true })
.boundingBox();
assert.equal(mobileInput.height, mobileSave.height);
await page.getByRole('switch', { name: '다른 기기에서 발견 허용' }).waitFor();
assert.equal(
await page
.locator('.discovery-setting')
.evaluate((el) => getComputedStyle(el).borderBottomWidth),
'0px',
await settings.getByRole('switch', { name: '다른 기기에서 발견 허용' }).count(),
0,
'발견 허용 설정을 표시하지 않음',
);
const settingsBounds = await settings.boundingBox();
assert(Math.abs(settingsBounds.y + settingsBounds.height - 900) < 2, '모바일 설정 하단 시트');
@@ -381,12 +379,30 @@ try {
assert.equal(await page.locator('.name-count').innerText(), '9/40');
await page.getByRole('button', { name: '저장', exact: true }).click();
await page.locator('.footer-device-name').filter({ hasText: '나의 테스트 기기' }).waitFor();
await page.locator('.footer-device-summary').click();
await page.getByLabel('다른 기기에서 발견 허용').uncheck();
const oldSession = await page.evaluate(async () => {
const session = await (
await fetch('/api/guest/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{}',
})
).json();
const response = await fetch('/api/device/me', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': session.csrfToken },
body: JSON.stringify({ discoverable: false }),
});
return { deviceId: session.device.id, ok: response.ok };
});
assert.equal(oldSession.ok, true);
await page.locator('.footer-device-summary').filter({ hasText: '발견 꺼짐' }).waitFor();
await page.getByLabel('다른 기기에서 발견 허용').check();
await page.reload();
await page.locator('.footer-device-summary').filter({ hasText: '발견 허용' }).waitFor();
await page.getByRole('button', { name: '대화상자 닫기', exact: true }).click();
assert.equal(await page.locator('.footer-device-name').innerText(), '나의 테스트 기기');
assert.equal(
await page.evaluate(async () => (await (await fetch('/api/device/snapshot')).json()).device.id),
oldSession.deviceId,
);
await page.getByRole('button', { name: '다른 기기 연결', exact: true }).click();
assert.equal(await page.locator('.connection-choice').count(), 2);
assert.equal(await page.getByRole('checkbox', { name: '참여할 때 내 승인 받기' }).count(), 0);
@@ -436,7 +452,7 @@ try {
await page.waitForFunction(() => document.querySelectorAll('.peer-card').length === 12);
assert.deepEqual(errors, []);
console.log(
'PASS: 실제 12대 자동 발견, 4/3/2열과 줄바꿈, 푸터의 내 기기 상태·이름/발견 설정, 선택 전 전송 UI 없음, 고정 크기 PC 모달·모바일 하단 시트, 하나의 전송 버튼과 파일/링크 대체, Escape, QR 참여 시 모달 자동 닫힘, 방 안의 기기 격리·나가기 후 주변 복귀.',
'PASS: 실제 12대 자동 발견, 4/3/2열과 줄바꿈, 푸터의 기기명 설정·발견 옵션 숨김·이전 비공개 세션의 재접속 후 발견 복구, 선택 전 전송 UI 없음, 고정 크기 PC 모달·모바일 하단 시트, 하나의 전송 버튼과 파일/링크 대체, Escape, QR 참여 시 모달 자동 닫힘, 방 안의 기기 격리·나가기 후 주변 복귀.',
);
} finally {
for (const socket of sockets) socket.disconnect();

View File

@@ -80,10 +80,95 @@ try {
await auth.getByLabel('비밀번호', { exact: true }).fill('1234');
await auth.getByRole('button', { name: '로그인', exact: true }).click();
await page.locator('.account-workspace').waitFor();
assert.equal(
await page.getByRole('tab', { name: '받은 항목', exact: true }).getAttribute('aria-selected'),
'true',
'보관함 기본 화면은 받은 항목',
);
assert.equal(await page.locator('.account-heading p').count(), 0);
await page.locator('.scope-note').filter({ hasText: '항목별 최대 7일 보관' }).waitFor();
return await page.locator('.footer-device-name').innerText();
}
const senderName = await register(sender, 'alice@example.com', 'Alice');
await register(receiver, 'bob@example.com', 'Bob');
for (const [page, email, device] of [
[sender, 'alice@example.com', 'desktop'],
[receiver, 'bob@example.com', 'mobile'],
]) {
await page.getByRole('button', { name: '계정 설정', exact: true }).click();
const preferences = page.getByRole('dialog', { name: '계정 설정', exact: true });
await preferences.waitFor();
await page.keyboard.press('Shift+Tab');
assert.equal(
await preferences
.getByRole('button', { name: '비밀번호 변경', exact: true })
.evaluate((el) => document.activeElement === el),
true,
'포커스가 모달 밖으로 나가지 않음',
);
await page.keyboard.press('Tab');
assert.equal(await preferences.locator('select,input').count(), 0, '항목 목록을 먼저 표시');
assert.equal(await preferences.locator('.preference-item').count(), 2);
assert.equal(
await preferences.locator('.preference-item').first().locator('p').innerText(),
email,
);
const viewport = page.viewportSize();
const bounds = await preferences.boundingBox();
assert(bounds.x >= 0 && bounds.x + bounds.width <= viewport.width);
if (device === 'mobile')
assert(
Math.abs(bounds.y + bounds.height - viewport.height) < 2,
'계정 설정은 모바일 하단 시트',
);
await page.screenshot({ path: `tests/e2e/account-settings-${device}.png` });
await preferences.getByRole('button', { name: '비밀번호 변경', exact: true }).click();
await preferences.getByLabel('현재 비밀번호', { exact: true }).waitFor();
assert.equal(
await preferences
.getByLabel('현재 비밀번호', { exact: true })
.evaluate((el) => document.activeElement === el),
false,
'입력창 자동 포커스 없음',
);
const headerBounds = await preferences.locator('header').boundingBox();
const formBounds = await preferences.locator('form').boundingBox();
assert(formBounds.y >= headerBounds.y + headerBounds.height + 16, '제목과 변경 폼 간격');
for (const field of ['현재 비밀번호', '새 비밀번호', '새 비밀번호 확인']) {
const input = preferences.getByLabel(field, { exact: true });
if (device === 'mobile')
assert.equal(await input.evaluate((el) => getComputedStyle(el).fontSize), '16px');
await input.fill('draft1234');
const inputBounds = await input.boundingBox();
assert(
inputBounds.x >= bounds.x && inputBounds.x + inputBounds.width <= bounds.x + bounds.width,
);
}
await page.screenshot({ path: `tests/e2e/account-password-${device}.png` });
await preferences.getByRole('button', { name: '계정 설정 목록으로', exact: true }).click();
await preferences.getByRole('button', { name: '이메일 변경', exact: true }).click();
assert.equal(
await preferences.getByLabel('현재 비밀번호', { exact: true }).inputValue(),
'',
'항목 전환 시 비밀번호 초안 제거',
);
await preferences.getByLabel('새 이메일', { exact: true }).fill('new@example.com');
await page.screenshot({ path: `tests/e2e/account-email-${device}.png` });
await page.keyboard.press('Escape');
await preferences.waitFor({ state: 'hidden' });
await page.getByRole('button', { name: '계정 설정', exact: true }).click();
await preferences.getByRole('button', { name: '이메일 변경', exact: true }).click();
assert.equal(
await preferences.getByLabel('새 이메일', { exact: true }).inputValue(),
'',
'모달 닫기 시 개인정보 초안 제거',
);
await preferences.getByRole('button', { name: '계정 설정 닫기', exact: true }).click();
assert.equal(
await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth),
true,
);
}
async function navigate(page, view) {
if (view === '기기') return page.getByRole('button', { name: 'hop. 홈', exact: true }).click();
await page.locator('.login-button').click();
@@ -220,7 +305,7 @@ try {
assert(!(await receiver.locator('body').innerText()).includes('https://example.com/saved'));
assert.deepEqual(errors, []);
console.log(
'PASS: 1234 가입·인증·로그인, 로그인 기기 수신의 날짜별 기록, 직접 링크/파일 저장과 메모 수정, 보관함 자료의 수락 후 자동 열기/다운로드, 파일 재업로드 없음, 7일 만료, 삭제/복원, 로그아웃 권한 격리, 모바일 화면. 실제 Resend 발송은 포함하지 않음.',
'PASS: 받은 항목 기본 화면·보관 안내, PC/모바일 계정 설정 목록·항목별 폼·간격·16px 입력·초안 제거·키보드 탐색, 가입·인증·로그인, 날짜별 기록, 직접 링크/파일 저장과 메모 수정, 보관함 자료의 수락 후 자동 열기/다운로드, 파일 재업로드 없음, 7일 만료, 삭제/복원, 로그아웃 권한 격리. 실제 Resend 발송은 포함하지 않음.',
);
} catch (error) {
console.error(error);