From ddfb2d14ab5f03cae51960891f4b1fc8f88a8f9e Mon Sep 17 00:00:00 2001 From: ttipo Date: Thu, 5 Nov 2026 12:44:43 +0900 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=ED=99=94=EB=A9=B4=20=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=EB=A5=BC=20=EC=82=AC=EC=9A=A9=EC=9E=90=EC=97=90?= =?UTF-8?q?=EA=B2=8C=20=EB=A7=90=ED=95=98=EB=8A=94=20=EB=A7=90=ED=88=AC?= =?UTF-8?q?=EB=A1=9C=20=EA=B3=A0=EC=B9=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 설계 문서처럼 규칙을 적어 둔 문구가 많다는 사용자 지적(2026-11-05) — "메일 서버에 계정이 있는 직원입니다 — 이름과 주소만 보입니다." · "「@도메인」은 그 도메인에서 오는 메일 모두입니다." 같은 글. 화면 상수 · 템플릿 · 문장을 만드는 유틸의 236곳을 무엇이 되는지 · 무엇을 하면 되는지로 다시 썼다: 줄표로 잇지 않고, 「」 대신 [단추 이름], 메일 서버 · 엔진 · 판정 같은 만든 쪽 말을 빼고, 존댓말은 그대로. 한도 · 날짜 · 개수는 빼지 않았다. 그 문구를 그대로 보던 spec 을 함께 맞췄다(lint · type-check · 단위 2,354 · TZ=UTC · build 통과). Co-Authored-By: Claude Opus 5.5 --- .../components/contacts/ContactDialog.spec.ts | 10 +- .../src/components/feedback/StateMessage.vue | 2 +- .../mail/ComposeScheduleDialog.spec.ts | 8 +- .../components/mail/MailNotifyOffer.spec.ts | 2 +- .../mail/MailSelectionToolbar.spec.ts | 4 +- .../mail/MailboxEmptyButton.spec.ts | 2 +- .../src/components/mail/MailboxPanel.spec.ts | 10 +- .../MessageAttachments.large-files.spec.ts | 4 +- .../src/components/mail/MessageList.spec.ts | 4 +- .../src/components/mail/MessageReader.spec.ts | 4 +- .../src/components/mail/MessageReader.vue | 2 +- .../src/components/mail/MessageTable.spec.ts | 2 +- .../components/mail/RecipientField.spec.ts | 4 +- .../mail/TwoFactorGraceNotice.spec.ts | 2 +- .../components/mail/editor/MailEditor.spec.ts | 2 +- .../editor/signature/SignatureToolbar.vue | 2 +- .../settings/LargeFileUnsentList.vue | 2 +- .../components/settings/LoginEventTable.vue | 4 +- .../src/composables/useMailFilterRun.spec.ts | 40 +++---- .../src/pages/LoginPage.restricted.spec.ts | 18 +-- frontend/src/pages/LoginPage.spec.ts | 2 +- frontend/src/pages/LoginPage.vue | 4 +- .../src/pages/contacts/ContactsPage.spec.ts | 20 ++-- .../src/pages/mail/ComposePage.drafts.spec.ts | 2 +- .../mail/ComposePage.large-files.spec.ts | 10 +- .../pages/mail/ComposePage.sendlater.spec.ts | 6 +- frontend/src/pages/mail/ComposePage.spec.ts | 10 +- frontend/src/pages/mail/ComposePage.vue | 2 +- .../pages/mail/MailboxPage.scheduled.spec.ts | 4 +- .../src/pages/mail/MailboxPage.search.spec.ts | 6 +- .../settings/AccountAppPasswordsPage.spec.ts | 12 +- .../settings/AccountLoginHistoryPage.spec.ts | 44 ++++---- .../settings/AccountMailClientsPage.spec.ts | 18 +-- .../settings/AccountPasswordPage.spec.ts | 12 +- .../settings/AccountTwoFactorPage.spec.ts | 12 +- .../src/pages/settings/MailBlocksPage.spec.ts | 12 +- .../src/pages/settings/MailFilterPage.spec.ts | 44 ++++---- .../pages/settings/MailLargeFilesPage.spec.ts | 4 +- .../src/pages/settings/MailNotifyPage.spec.ts | 16 +-- .../pages/settings/MailSendReadPage.spec.ts | 6 +- .../pages/settings/MailSignaturePage.spec.ts | 6 +- .../pages/settings/MailVacationPage.spec.ts | 12 +- frontend/src/pages/share/SharePage.spec.ts | 16 +-- frontend/src/pages/share/SharePage.vue | 2 +- .../src/shared/constants/account-security.ts | 76 ++++++------- frontend/src/shared/constants/account.ts | 12 +- frontend/src/shared/constants/contacts.ts | 40 +++---- frontend/src/shared/constants/large-file.ts | 106 +++++++++--------- frontend/src/shared/constants/mail-block.ts | 14 +-- frontend/src/shared/constants/mail-clients.ts | 68 ++++++----- frontend/src/shared/constants/mail-editor.ts | 2 +- frontend/src/shared/constants/mail-filter.ts | 64 +++++------ frontend/src/shared/constants/mail-notify.ts | 22 ++-- .../src/shared/constants/mail-send-later.ts | 12 +- .../src/shared/constants/mail-settings.ts | 8 +- .../src/shared/constants/mail-signature.ts | 14 +-- .../src/shared/constants/mail-vacation.ts | 14 +-- frontend/src/shared/constants/mail.ts | 16 +-- .../src/shared/constants/mailbox-manage.ts | 6 +- .../shared/constants/session-restriction.ts | 18 +-- .../src/shared/utils/account-security.spec.ts | 16 +-- frontend/src/shared/utils/account-security.ts | 4 +- .../shared/utils/large-file-status.spec.ts | 14 +-- frontend/src/shared/utils/large-file.spec.ts | 8 +- frontend/src/shared/utils/large-file.ts | 2 +- frontend/src/shared/utils/mail-bulk.spec.ts | 6 +- .../src/shared/utils/mail-filter-run.spec.ts | 12 +- frontend/src/shared/utils/mail-html.spec.ts | 2 +- .../src/shared/utils/mail-realtime.spec.ts | 4 +- .../src/shared/utils/mail-send-result.spec.ts | 20 ++-- .../src/shared/utils/mail-vacation.spec.ts | 2 +- .../shared/utils/mailbox-operation.spec.ts | 6 +- frontend/src/shared/utils/share-links.spec.ts | 4 +- frontend/src/stores/toast.store.spec.ts | 4 +- 74 files changed, 503 insertions(+), 503 deletions(-) diff --git a/frontend/src/components/contacts/ContactDialog.spec.ts b/frontend/src/components/contacts/ContactDialog.spec.ts index b4bffaf..40ca319 100644 --- a/frontend/src/components/contacts/ContactDialog.spec.ts +++ b/frontend/src/components/contacts/ContactDialog.spec.ts @@ -154,8 +154,8 @@ describe('ContactDialog', () => { '집', '기타', ]) - expect(wrapper.text()).toContain('5개까지 · 받는 사람 칸에는 맨 위 주소가 먼저 나옵니다') - expect(wrapper.get('#contact-required').text()).toBe('이름이나 이메일 가운데 하나는 있어야 합니다.') + expect(wrapper.text()).toContain('5개까지 넣을 수 있고, 받는 사람 칸에는 맨 위 주소가 먼저 나옵니다.') + expect(wrapper.get('#contact-required').text()).toBe('이름이나 이메일 가운데 하나는 넣어 주세요.') expect(wrapper.get('[data-contact-note-count]').text()).toBe('0 / 1,000') expect(wrapper.findAll('[data-contact-group]').map((input) => input.attributes('data-contact-group'))).toEqual([ 'g1', @@ -224,7 +224,7 @@ describe('ContactDialog', () => { expect(email?.attributes('type')).toBe('text') const addEmail = wrapper.findAll('.entry-rows__add-button')[0] expect(wrapper.get(`#${addEmail?.attributes('aria-describedby')}`).text()).toBe( - '5개까지 · 받는 사람 칸에는 맨 위 주소가 먼저 나옵니다', + '5개까지 넣을 수 있고, 받는 사람 칸에는 맨 위 주소가 먼저 나옵니다.', ) }) @@ -342,7 +342,7 @@ describe('ContactDialog', () => { expect(search?.config.silent).toBe(true) const warning = wrapper.get('[data-contact-duplicate]') expect(warning.text()).toBe( - '이미 주소록에 있는 주소입니다 — 강서윤(거래처). 그래도 저장할 수 있습니다. 그 연락처 보기', + '주소록에 있는 강서윤(거래처)의 주소와 같습니다. 그래도 저장할 수 있습니다. 그 연락처 보기', ) expect(email?.attributes('aria-describedby')).toBe(warning.attributes('id')) expect(email?.attributes('aria-invalid')).toBe('false') @@ -370,7 +370,7 @@ describe('ContactDialog', () => { ) expect((wrapper.get('#contact-name').element as HTMLInputElement).value).toBe('강서윤') - expect(wrapper.get('[data-contact-duplicate]').text()).toContain('이미 주소록에 있는 주소입니다 — 강서윤') + expect(wrapper.get('[data-contact-duplicate]').text()).toContain('주소록에 있는 강서윤') await wrapper.get('#contact-cancel').trigger('click') await flushPromises() expect(isOpen(wrapper, '#contact-leave-dialog')).toBe(false) diff --git a/frontend/src/components/feedback/StateMessage.vue b/frontend/src/components/feedback/StateMessage.vue index 67965a1..a2da0de 100644 --- a/frontend/src/components/feedback/StateMessage.vue +++ b/frontend/src/components/feedback/StateMessage.vue @@ -18,7 +18,7 @@ interface Props { tone?: 'neutral' | 'danger' /** 서버 추적 ID — 앞 8자리를 "문의 코드"로 보인다 */ traceId?: string | null - /** 영역을 세로로 가득 채워 가운데 둔다 (본문 영역의 "메일을 선택하세요") */ + /** 영역을 세로로 가득 채워 가운데 둔다 (본문 영역의 "메일을 선택해 주세요") */ fill?: boolean } diff --git a/frontend/src/components/mail/ComposeScheduleDialog.spec.ts b/frontend/src/components/mail/ComposeScheduleDialog.spec.ts index 77e2b8b..ebf8bc5 100644 --- a/frontend/src/components/mail/ComposeScheduleDialog.spec.ts +++ b/frontend/src/components/mail/ComposeScheduleDialog.spec.ts @@ -57,7 +57,7 @@ describe('ComposeScheduleDialog', () => { expect(day.attributes()).toMatchObject({ min: '2026-10-29', max: '2026-11-05' }) expect((wrapper.get('[data-schedule-time]').element as HTMLSelectElement).value).toBe(String(9 * 60)) expect(wrapper.findAll('[data-schedule-time] option')).toHaveLength(48) - expect(wrapper.get('#compose-schedule-note').text()).toMatch(/^이 PC 의 시간(\(.+\))?으로 보냅니다 · 7일 안에서 고를 수 있습니다\.$/) + expect(wrapper.get('#compose-schedule-note').text()).toMatch(/^이 PC의 시간(\(.+\))?으로 보내고, 7일 안에서 고를 수 있습니다\.$/) expect(document.activeElement).toBe(wrapper.get('[data-schedule-confirm]').element) }) @@ -81,7 +81,7 @@ describe('ComposeScheduleDialog', () => { expect(wrapper.emitted('confirm')).toEqual([[local(10, 31, 14, 30)]]) }) - it('지금부터 5분 뒤 이전이면 "지금부터 5분 뒤 이후의 시각을 고르세요." — 칸이 그 글을 가리키고 넘기지 않는다', async () => { + it('지금부터 5분 뒤 이전이면 "5분 뒤보다 이른 시각은 고를 수 없습니다. 더 늦은 시각을 골라 주세요." — 칸이 그 글을 가리키고 넘기지 않는다', async () => { const { wrapper } = mountAt(local(10, 29, 15)) await wrapper.get('[data-schedule-day]').setValue('2026-10-29') @@ -89,7 +89,7 @@ describe('ComposeScheduleDialog', () => { await wrapper.get('[data-schedule-confirm]').trigger('click') const error = wrapper.get('#compose-schedule-error') - expect(error.text()).toBe('지금부터 5분 뒤 이후의 시각을 고르세요.') + expect(error.text()).toBe('5분 뒤보다 이른 시각은 고를 수 없습니다. 더 늦은 시각을 골라 주세요.') expect(error.attributes('role')).toBe('alert') expect(wrapper.get('[data-schedule-day]').attributes()).toMatchObject({ 'aria-invalid': 'true', @@ -120,7 +120,7 @@ describe('ComposeScheduleDialog', () => { clock.advance(6 * 60_000) await wrapper.get('[data-schedule-confirm]').trigger('click') - expect(wrapper.get('#compose-schedule-error').text()).toBe('지금부터 5분 뒤 이후의 시각을 고르세요.') + expect(wrapper.get('#compose-schedule-error').text()).toBe('5분 뒤보다 이른 시각은 고를 수 없습니다. 더 늦은 시각을 골라 주세요.') expect(wrapper.emitted('confirm')).toBeUndefined() }) diff --git a/frontend/src/components/mail/MailNotifyOffer.spec.ts b/frontend/src/components/mail/MailNotifyOffer.spec.ts index 19889b6..fc4379d 100644 --- a/frontend/src/components/mail/MailNotifyOffer.spec.ts +++ b/frontend/src/components/mail/MailNotifyOffer.spec.ts @@ -138,7 +138,7 @@ describe('MailNotifyOffer (바탕화면 알림 권하기 줄)', () => { expect(useToastStore().toasts).toMatchObject([ { kind: 'error', - message: '브라우저가 알림을 막고 있습니다. 푸는 방법은 설정 › 알림에 있습니다.', + message: '브라우저가 알림을 막고 있습니다. 푸는 방법은 [설정] → [알림]에서 볼 수 있습니다.', action: { label: '설정 열기', to: { name: 'settings-mail-notify' } }, }, ]) diff --git a/frontend/src/components/mail/MailSelectionToolbar.spec.ts b/frontend/src/components/mail/MailSelectionToolbar.spec.ts index 4cdfc21..ebb859b 100644 --- a/frontend/src/components/mail/MailSelectionToolbar.spec.ts +++ b/frontend/src/components/mail/MailSelectionToolbar.spec.ts @@ -401,7 +401,7 @@ describe('여러 통 처리 도구 줄', () => { expect(toastTexts()).toContain('메일 2통을 되돌렸습니다.') }) - it('스팸메일함에서는 「스팸 아님」(강조) — 받은편지함으로 · isJunk: false, "(스팸 아님)" 알림 · 되돌리면 스팸메일함으로 · isJunk: true', async () => { + it('스팸메일함에서는 「스팸 아님」(강조) — 받은편지함으로 · isJunk: false, "스팸이 아닌 메일로 표시해 …" 알림 · 되돌리면 스팸메일함으로 · isJunk: true', async () => { const junked = [summary('j1', { mailboxIds: ['junk'] }), summary('j2', { mailboxIds: ['junk'] })] const { wrapper, store } = await mountView({ boxId: 'junk', messages: junked }) store.toggleCheck('j1') @@ -415,7 +415,7 @@ describe('여러 통 처리 도구 줄', () => { expect(bulkPatches()).toEqual([{ ids: ['j1'], mailboxId: 'inbox', isJunk: false }]) const [toast] = useToastStore().toasts - expect(toast?.message).toBe('메일 1통을 받은편지함으로 옮겼습니다 (스팸 아님).') + expect(toast?.message).toBe('메일 1통을 스팸이 아닌 메일로 표시해 받은편지함으로 옮겼습니다.') if (toast?.action && 'run' in toast.action) toast.action.run() await flushPromises() expect(bulkPatches()[1]).toEqual({ ids: ['j1'], mailboxId: 'junk', isJunk: true }) diff --git a/frontend/src/components/mail/MailboxEmptyButton.spec.ts b/frontend/src/components/mail/MailboxEmptyButton.spec.ts index a84aeaf..76d79b1 100644 --- a/frontend/src/components/mail/MailboxEmptyButton.spec.ts +++ b/frontend/src/components/mail/MailboxEmptyButton.spec.ts @@ -111,7 +111,7 @@ describe('목록 머리의 휴지통 · 스팸메일함 비우기', () => { ]) expect(store.messages).toEqual([]) expect(useToastStore().toasts.map((toast) => [toast.message, toast.action])).toEqual([ - ['휴지통을 비웠습니다 — 메일 2통.', null], + ['메일 2통을 지워 휴지통을 비웠습니다.', null], ]) // 비었으니 단추도 사라진다 expect(wrapper.find('[data-mailbox-empty]').exists()).toBe(false) diff --git a/frontend/src/components/mail/MailboxPanel.spec.ts b/frontend/src/components/mail/MailboxPanel.spec.ts index cf28ea1..d00175c 100644 --- a/frontend/src/components/mail/MailboxPanel.spec.ts +++ b/frontend/src/components/mail/MailboxPanel.spec.ts @@ -216,7 +216,7 @@ describe('MailboxPanel — 메일함 관리', () => { const input = wrapper.get('#mailbox-name-input') expect(input.attributes('maxlength')).toBe('64') expect(document.activeElement).toBe(input.element) - expect(wrapper.get('#mailbox-name-help').text()).toBe('64자까지. 같은 자리에 같은 이름은 쓸 수 없습니다.') + expect(wrapper.get('#mailbox-name-help').text()).toBe('64자까지 쓸 수 있고, 같은 자리에 같은 이름은 쓸 수 없습니다.') // 이름을 비웠으면 누를 수 없다 expect(wrapper.get('#mailbox-name-confirm').attributes('disabled')).toBeDefined() @@ -262,7 +262,7 @@ describe('MailboxPanel — 메일함 관리', () => { expect(useToastStore().toasts).toEqual([]) await wrapper.get('#mailbox-name-input').setValue('거래처 2') - expect(wrapper.get('#mailbox-name-help').text()).toBe('64자까지. 같은 자리에 같은 이름은 쓸 수 없습니다.') + expect(wrapper.get('#mailbox-name-help').text()).toBe('64자까지 쓸 수 있고, 같은 자리에 같은 이름은 쓸 수 없습니다.') }) it('/ 가 든 이름은 보내지 않고 칸 아래에 사전 문구(VAL_001)', async () => { @@ -403,7 +403,7 @@ describe('MailboxPanel — 메일함 관리', () => { expect(row(wrapper, '받은편지함').find('.mailbox-row__count').exists()).toBe(false) }) - it('휴지통 비우기 — 확인(처음 초점 「취소」) 뒤 DELETE /mailboxes/{id}/messages 를 되풀이 · "휴지통을 비웠습니다 — 메일 21통."', async () => { + it('휴지통 비우기 — 확인(처음 초점 「취소」) 뒤 DELETE /mailboxes/{id}/messages 를 되풀이 · "메일 21통을 지워 휴지통을 비웠습니다."', async () => { reply('DELETE /mailboxes/trash/messages', ok({ processed: 20, remaining: 1 }), ok({ processed: 1, remaining: 0 })) const { wrapper } = await mountPanel() @@ -421,7 +421,7 @@ describe('MailboxPanel — 메일함 관리', () => { await wrapper.get('#mailbox-empty-confirm').trigger('click') await flushPromises() expect(sent('DELETE', '/mailboxes/trash/messages')).toHaveLength(2) - expect(toastTexts()).toEqual(['휴지통을 비웠습니다 — 메일 21통.']) + expect(toastTexts()).toEqual(['메일 21통을 지워 휴지통을 비웠습니다.']) // 되돌릴 수 없어 [되돌리기]가 없다 expect(useToastStore().toasts[0]?.action).toBeNull() }) @@ -433,7 +433,7 @@ describe('MailboxPanel — 메일함 관리', () => { await choose(wrapper, '받은편지함', '모두 읽음으로 표시') // 진행 알림은 그 자리에서 결과로 바뀐다 — 까닭(사전 문구 · 문의 코드)은 인터셉터의 알림 expect(useToastStore().toasts.map((toast) => [toast.kind, toast.message])).toEqual([ - ['error', '1,234통 가운데 500통까지 처리했습니다 — 다시 시도해 주세요.'], + ['error', '1,234통 가운데 500통까지 처리하고 멈췄습니다. 다시 시도해 주세요.'], ['error', ERROR_CODE_MESSAGES.MAIL_001], ]) diff --git a/frontend/src/components/mail/MessageAttachments.large-files.spec.ts b/frontend/src/components/mail/MessageAttachments.large-files.spec.ts index 99bd60f..c1e85ce 100644 --- a/frontend/src/components/mail/MessageAttachments.large-files.spec.ts +++ b/frontend/src/components/mail/MessageAttachments.large-files.spec.ts @@ -104,7 +104,7 @@ describe('MessageAttachments — 대용량 첨부', () => { expect(blocked.attributes('aria-disabled')).toBe('true') await blocked.trigger('click') await flushPromises() - expect(view.get('[role="status"]').text()).toBe('1.2 GB · 보안 검사 중 — 끝나면 받을 수 있습니다') + expect(view.get('[role="status"]').text()).toBe('1.2 GB · 보안 검사가 끝나면 받을 수 있습니다') expect(downloads).toEqual([]) expect(view.get('[data-large-file="f2"]').classes()).toContain('large-item--danger') @@ -127,7 +127,7 @@ describe('MessageAttachments — 대용량 첨부', () => { '612 MB · 아직 받지 않음 · 11월 29일까지', ]) expect(view.get('.link-bar__note').text()).toBe( - '링크로 보낸 파일 2개 — 받는 사람 모두 같은 링크로 받습니다. 끊으면 둘 다 받을 수 없습니다.', + '링크로 보낸 파일 2개는 받는 사람 모두 같은 링크로 받습니다. 끊으면 둘 다 받을 수 없습니다.', ) await view.get('[data-link-copy]').trigger('click') diff --git a/frontend/src/components/mail/MessageList.spec.ts b/frontend/src/components/mail/MessageList.spec.ts index e6dcc86..51e5d17 100644 --- a/frontend/src/components/mail/MessageList.spec.ts +++ b/frontend/src/components/mail/MessageList.spec.ts @@ -143,7 +143,7 @@ describe('MessageList (분할 보기 목록)', () => { const box = wrapper.findAll('.message-row [role="checkbox"]')[0] expect(box?.attributes('aria-disabled')).toBe('true') - expect(box?.attributes('title')).toBe('보내기를 기다리는 메일은 고를 수 없습니다 — 열어서 취소하거나 삭제해 주세요.') + expect(box?.attributes('title')).toBe('보내기를 기다리는 메일은 고를 수 없습니다. 메일을 열어 취소하거나 삭제해 주세요.') await box?.trigger('click') await wrapper.findAll('.message-row__main')[0]?.trigger('click', { metaKey: true }) await flushPromises() @@ -175,6 +175,6 @@ describe('MessageList (분할 보기 목록)', () => { // 머리 — 검색 결과 · ‘회의’ · 2통 · 범위 줄(전체일 때 안내) expect(wrapper.get('#message-list-title').text()).toBe('검색 결과') expect(wrapper.get('.search-header__count').text()).toBe('‘회의’ · 2통') - expect(wrapper.get('.search-scope').text()).toContain('휴지통 · 스팸메일함은 빼고 찾습니다') + expect(wrapper.get('.search-scope').text()).toContain('휴지통과 스팸메일함은 빼고 찾습니다') }) }) diff --git a/frontend/src/components/mail/MessageReader.spec.ts b/frontend/src/components/mail/MessageReader.spec.ts index 307918e..dd84415 100644 --- a/frontend/src/components/mail/MessageReader.spec.ts +++ b/frontend/src/components/mail/MessageReader.spec.ts @@ -596,7 +596,7 @@ describe('MessageReader 스팸 신고 · 원문 내려받기 (4단계 — 디자 expect(patches().at(-1)).toEqual({ ids: ['m1'], mailboxId: 'inbox', isJunk: false }) }) - it('스팸메일함의 메일은 「스팸 아님」(강조) — 받은편지함으로 · isJunk: false, "(스팸 아님)" 알림', async () => { + it('스팸메일함의 메일은 「스팸 아님」(강조) — 받은편지함으로 · isJunk: false, "스팸이 아닌 메일로 표시해 …" 알림', async () => { const { wrapper } = await mountWith({ box: 'junk' }) const notSpam = wrapper.get('[data-reader-spam]') expect(notSpam.text()).toBe('스팸 아님') @@ -606,7 +606,7 @@ describe('MessageReader 스팸 신고 · 원문 내려받기 (4단계 — 디자 await flushPromises() expect(patches()).toEqual([{ ids: ['m1'], mailboxId: 'inbox', isJunk: false }]) - expect(useToastStore().toasts[0]?.message).toBe('메일 1통을 받은편지함으로 옮겼습니다 (스팸 아님).') + expect(useToastStore().toasts[0]?.message).toBe('메일 1통을 스팸이 아닌 메일로 표시해 받은편지함으로 옮겼습니다.') }) it('스팸메일함을 모르면 「스팸 신고」를 그리지 않는다', async () => { diff --git a/frontend/src/components/mail/MessageReader.vue b/frontend/src/components/mail/MessageReader.vue index 88b22c8..5a6efb3 100644 --- a/frontend/src/components/mail/MessageReader.vue +++ b/frontend/src/components/mail/MessageReader.vue @@ -281,7 +281,7 @@ onBeforeUnmount(() => { { + it('그룹 이름을 치면 그룹 줄 — 아이콘 + 이름(맞는 글자 굵게) + "고르면 4명을 한꺼번에 넣습니다", 초성도 된다 · 구성원이 없는 그룹은 없다', async () => { const wrapper = mountField([], suggestionsOf([JUNHO], [VENDORS, EMPTY_GROUP])) await typed(wrapper, 'ㄱㄹ') @@ -483,7 +483,7 @@ describe('RecipientField', () => { expect(option.attributes('data-suggestion-group')).toBeDefined() expect(option.find('svg').exists()).toBe(true) expect(option.get('.recipient-suggestions__name strong').text()).toBe('거래') - expect(option.get('.recipient-suggestions__email').text()).toBe('4명 · 고르면 한꺼번에 넣습니다') + expect(option.get('.recipient-suggestions__email').text()).toBe('고르면 4명을 한꺼번에 넣습니다') wrapper.unmount() }) diff --git a/frontend/src/components/mail/TwoFactorGraceNotice.spec.ts b/frontend/src/components/mail/TwoFactorGraceNotice.spec.ts index bc9375d..95460e1 100644 --- a/frontend/src/components/mail/TwoFactorGraceNotice.spec.ts +++ b/frontend/src/components/mail/TwoFactorGraceNotice.spec.ts @@ -53,7 +53,7 @@ describe('TwoFactorGraceNotice', () => { const bar = wrapper.get('[data-two-factor-grace-notice]') expect(bar.attributes('role')).toBe('region') expect(bar.attributes('aria-label')).toBe('2단계 인증 안내') - expect(bar.text()).toBe('11월 30일까지 2단계 인증을 켜 주세요 — 그 뒤에는 켜야 로그인할 수 있습니다.지금 켜기') + expect(bar.text()).toBe('11월 30일까지 2단계 인증을 켜 주세요. 그 뒤에는 켜야 로그인할 수 있습니다.지금 켜기') expect(bar.get('strong').text()).toBe('11월 30일까지 2단계 인증을 켜 주세요') expect(bar.get('[data-two-factor-grace-action]').attributes('href')).toBe('/settings/account/two-factor') expect(bar.findAll('button')).toHaveLength(0) diff --git a/frontend/src/components/mail/editor/MailEditor.spec.ts b/frontend/src/components/mail/editor/MailEditor.spec.ts index 38b0cc9..3177442 100644 --- a/frontend/src/components/mail/editor/MailEditor.spec.ts +++ b/frontend/src/components/mail/editor/MailEditor.spec.ts @@ -50,7 +50,7 @@ describe('MailEditor (서식 편집기)', () => { expect(body.attributes('role')).toBe('textbox') expect(body.attributes('aria-label')).toBe('본문') expect(body.attributes('aria-multiline')).toBe('true') - expect(body.get('p').attributes('data-placeholder')).toBe('내용을 입력하세요') + expect(body.get('p').attributes('data-placeholder')).toBe('내용을 입력해 주세요') expect(view.get('[role="toolbar"]').attributes('aria-label')).toBe('본문 서식') expect(view.emitted('ready')).toHaveLength(1) }) diff --git a/frontend/src/components/mail/editor/signature/SignatureToolbar.vue b/frontend/src/components/mail/editor/signature/SignatureToolbar.vue index 50c82a7..7ba6263 100644 --- a/frontend/src/components/mail/editor/signature/SignatureToolbar.vue +++ b/frontend/src/components/mail/editor/signature/SignatureToolbar.vue @@ -256,7 +256,7 @@ defineExpose({ // 설정 › 대용량 첨부 › 보내지 않은 파일 (디자인 원본 SettingsLargeFiles — 사용량 상자 바로 아래: 공간을 바로 비울 수 있는 곳이라 보낸 링크보다 위) -// 「보내지 않은 파일 3개」 + "1.9 GB · 임시 저장한 메일에 실렸거나 올리다 멈춘 파일 — 지우면 바로 공간이 생깁니다". 없으면 쓰는 쪽이 묶음째 그리지 않는다. +// 「보내지 않은 파일 3개」 + "1.9 GB · 임시 저장한 메일에 실렸거나 올리다 멈춘 파일이라 지우면 바로 공간이 생깁니다". 없으면 쓰는 쪽이 묶음째 그리지 않는다. // 줄(52): 아이콘 · 이름 / 상태(올리다 멈춤 — 갈색 · 다 올림 · 검사 상태) · 크기(멈춘 파일은 "1.2 GB" / "420 MB 올림") · // 남는 기한("11월 29일까지 남음", 멈춘 파일은 "내일 오후 2:41까지 남음" / "마지막으로 올린 뒤 24시간") · [지우기](36, 빨간 글) // [지우기] → 확인 창(처음 초점 「취소」) → DELETE /file-uploads/{id} → "‘…’ 파일을 지웠습니다." — 줄이 사라지고 사용량이 바로 준다(스토어). diff --git a/frontend/src/components/settings/LoginEventTable.vue b/frontend/src/components/settings/LoginEventTable.vue index 70a4a4b..2105844 100644 --- a/frontend/src/components/settings/LoginEventTable.vue +++ b/frontend/src/components/settings/LoginEventTable.vue @@ -4,8 +4,8 @@ // 표(구현은 실제 table): 시각 · 방식 · IP · 기기 · 결과 — 최근 순서 · 20개씩 [더 보기](44) // - 방식: "웹메일" / "메일 프로그램 · IMAP" · "메일 프로그램 · SMTP" + 앱 비밀번호로 들어왔으면 아래 줄 "앱 비밀번호 ‘회사 PC 아웃룩’" // - 기기: 웹메일은 "Chrome · Windows"(모르면 "알 수 없는 브라우저"), 메일 프로그램은 「—」. 나라 · 지역은 보이지 않는다 -// - 결과: 「성공」 · 「성공 · 2단계 인증」 · 「실패 — 비밀번호 틀림」 · 「실패 — 코드 틀림」 · 「실패 — 앱 비밀번호 필요」 — 실패는 빨간 굵은 글자만(뱃지 없음) -// - 빈 상태(실패만): "최근 90일 동안 실패한 로그인이 없습니다" / "[전체]를 누르면 성공한 로그인도 봅니다." +// - 결과: 「성공」 · 「2단계 인증으로 성공」 · 「비밀번호가 틀려 실패」 · 「코드가 틀려 실패」 · 「앱 비밀번호가 필요해 실패」 — 실패는 빨간 굵은 글자만(뱃지 없음) +// - 빈 상태(실패만): "최근 90일 동안 실패한 로그인이 없습니다" / "[전체]를 누르면 성공한 로그인도 볼 수 있습니다." // - 보기를 바꾸면 그 보기로 처음부터 받는다 — 받는 동안 지난 목록은 그대로 두고 aria-busy (login-history.store) import { computed } from 'vue' diff --git a/frontend/src/composables/useMailFilterRun.spec.ts b/frontend/src/composables/useMailFilterRun.spec.ts index 7f5d403..df39300 100644 --- a/frontend/src/composables/useMailFilterRun.spec.ts +++ b/frontend/src/composables/useMailFilterRun.spec.ts @@ -118,19 +118,19 @@ describe('useMailFilterRun', () => { mailStore.listStatus = 'ready' } - it('진행 알림이 남았다가 끝나면 그 자리에서 결과로 — "…메일 37통에 적용했습니다." + [거래처 보기](그 메일함으로)', async () => { + it('진행 알림이 남았다가 끝나면 그 자리에서 결과로 — "…메일 37통을 정리했습니다." + [거래처 보기](그 메일함으로)', async () => { const first = deferred() const second = deferred() replies(first.promise, second.promise) expect(useMailFilterRun().start(rule())).toBe(true) // 첫 응답 전 — 전체는 받은편지함의 메일 수 - expect(shown()).toEqual([['progress', '‘거래처 메일’ 필터를 받은편지함에 적용하는 중 · 0 / 8,412통 확인']]) + expect(shown()).toEqual([['progress', '‘거래처 메일’ 필터로 받은편지함을 정리하는 중 · 0 / 8,412통 확인']]) const id = useToastStore().toasts[0]?.id first.finish(step(4500, 30, 3912, 'c1')) await flushPromises() - expect(shown()).toEqual([['progress', '‘거래처 메일’ 필터를 받은편지함에 적용하는 중 · 4,500 / 8,412통 확인']]) + expect(shown()).toEqual([['progress', '‘거래처 메일’ 필터로 받은편지함을 정리하는 중 · 4,500 / 8,412통 확인']]) second.finish(step(3912, 7, 0, null)) await flushPromises() @@ -139,16 +139,16 @@ describe('useMailFilterRun', () => { expect(toast).toMatchObject({ id, kind: 'success', - message: '‘거래처 메일’ 필터를 받은편지함의 메일 37통에 적용했습니다.', + message: '‘거래처 메일’ 필터로 받은편지함의 메일 37통을 정리했습니다.', action: { label: '거래처 보기', to: { name: 'mail-mailbox', params: { mailboxId: 'vendor' } } }, }) expect(cursors()).toEqual([null, 'c1']) }) it.each([ - ['읽음 · 중요만 하는 필터', rule({ name: '결재 알림', actions: { ...NO_ACTIONS, markAsRead: true, flag: true } }), 12, '‘결재 알림’ 필터를 받은편지함의 메일 12통에 적용했습니다.'], - ['받은편지함으로 옮기며 읽음 표시하는 필터', rule({ actions: { ...NO_ACTIONS, moveToMailboxId: 'inbox', markAsRead: true } }), 3, '‘거래처 메일’ 필터를 받은편지함의 메일 3통에 적용했습니다.'], - ['지금 메일함 목록에 없는 메일함으로 옮기며 읽음 표시하는 필터', rule({ actions: { ...NO_ACTIONS, moveToMailboxId: 'gone', markAsRead: true } }), 3, '‘거래처 메일’ 필터를 받은편지함의 메일 3통에 적용했습니다.'], + ['읽음 · 중요만 하는 필터', rule({ name: '결재 알림', actions: { ...NO_ACTIONS, markAsRead: true, flag: true } }), 12, '‘결재 알림’ 필터로 받은편지함의 메일 12통을 정리했습니다.'], + ['받은편지함으로 옮기며 읽음 표시하는 필터', rule({ actions: { ...NO_ACTIONS, moveToMailboxId: 'inbox', markAsRead: true } }), 3, '‘거래처 메일’ 필터로 받은편지함의 메일 3통을 정리했습니다.'], + ['지금 메일함 목록에 없는 메일함으로 옮기며 읽음 표시하는 필터', rule({ actions: { ...NO_ACTIONS, moveToMailboxId: 'gone', markAsRead: true } }), 3, '‘거래처 메일’ 필터로 받은편지함의 메일 3통을 정리했습니다.'], ['맞는 메일이 없는 옮기는 필터', rule(), 0, '받은편지함에 ‘거래처 메일’ 필터에 맞는 메일이 없습니다.'], ])('%s — 단추 없이 끝난다', async (_label, target, matched, message) => { replies(step(8412, matched, 0, null)) @@ -179,7 +179,7 @@ describe('useMailFilterRun', () => { useMailFilterRun().start(rule({ actions: { ...NO_ACTIONS, markAsRead: true } })) - expect(shown()).toEqual([['progress', '‘거래처 메일’ 필터를 받은편지함에 적용하는 중']]) + expect(shown()).toEqual([['progress', '‘거래처 메일’ 필터로 받은편지함을 정리하는 중']]) first.finish(step(10, 0, 0, null)) await flushPromises() }) @@ -193,7 +193,7 @@ describe('useMailFilterRun', () => { // 진행 알림이 그 자리에서 멈춤 문구로 — 까닭은 오류 알림이 함께 expect(shown()).toEqual([ - ['error', '‘거래처 메일’ 필터 — 8,412통 가운데 4,200통까지 확인하고 멈췄습니다(12통에 적용). 다시 시도해 주세요.'], + ['error', '‘거래처 메일’ 필터로 8,412통 가운데 4,200통까지 확인하고 멈췄습니다. 그동안 12통을 정리했습니다. 다시 시도해 주세요.'], ['error', ERROR_CODE_MESSAGES.MAIL_001], ]) const stopped = withAction() @@ -201,11 +201,11 @@ describe('useMailFilterRun', () => { useToastStore().clear() press(stopped) - expect(shown()).toEqual([['progress', '‘거래처 메일’ 필터를 받은편지함에 적용하는 중 · 4,200 / 8,412통 확인']]) + expect(shown()).toEqual([['progress', '‘거래처 메일’ 필터로 받은편지함을 정리하는 중 · 4,200 / 8,412통 확인']]) await flushPromises() expect(cursors()).toEqual([null, 'c1', 'c1']) - expect(shown()).toEqual([['success', '‘거래처 메일’ 필터를 받은편지함의 메일 37통에 적용했습니다.']]) + expect(shown()).toEqual([['success', '‘거래처 메일’ 필터로 받은편지함의 메일 37통을 정리했습니다.']]) }) it('처음 요청부터 실패하면 "필터는 저장했지만 …" + [다시 시도] — 처음부터 다시 한다', async () => { @@ -215,18 +215,18 @@ describe('useMailFilterRun', () => { await flushPromises() expect(shown()).toEqual([ - ['error', '필터는 저장했지만 받은편지함에 적용하지 못했습니다. 다시 시도해 주세요.'], + ['error', '필터는 저장했지만 받은편지함은 정리하지 못했습니다. 다시 시도해 주세요.'], ['error', ERROR_CODE_MESSAGES.SYS_001], ]) const failed = withAction() expect(failed?.action?.label).toBe('다시 시도') useToastStore().clear() press(failed) - expect(shown()).toEqual([['progress', '‘거래처 메일’ 필터를 받은편지함에 적용하는 중 · 0 / 8,412통 확인']]) + expect(shown()).toEqual([['progress', '‘거래처 메일’ 필터로 받은편지함을 정리하는 중 · 0 / 8,412통 확인']]) await flushPromises() expect(cursors()).toEqual([null, null]) - expect(shown()).toEqual([['success', '‘거래처 메일’ 필터를 받은편지함의 메일 37통에 적용했습니다.']]) + expect(shown()).toEqual([['success', '‘거래처 메일’ 필터로 받은편지함의 메일 37통을 정리했습니다.']]) }) it('다른 창 · PC 에서 적용하는 중이면(429) "다른 창에서 …" 알림 하나만 — 처음 요청이면 단추가 없다', async () => { @@ -236,7 +236,7 @@ describe('useMailFilterRun', () => { await flushPromises() expect(useToastStore().toasts).toMatchObject([ - { kind: 'error', message: '다른 창에서 필터를 받은편지함에 적용하는 중입니다. 끝난 뒤 다시 시도해 주세요.', action: null }, + { kind: 'error', message: '다른 창에서 필터로 받은편지함을 정리하는 중입니다. 끝난 뒤 다시 시도해 주세요.', action: null }, ]) }) @@ -251,7 +251,7 @@ describe('useMailFilterRun', () => { expect(useToastStore().toasts).toHaveLength(1) expect(toast).toMatchObject({ kind: 'error', - message: '다른 창에서 필터를 받은편지함에 적용하는 중입니다. 끝난 뒤 다시 시도해 주세요.', + message: '다른 창에서 필터로 받은편지함을 정리하는 중입니다. 끝난 뒤 다시 시도해 주세요.', action: { label: '다시 시도' }, }) press(toast) @@ -326,9 +326,9 @@ describe('useMailFilterRun', () => { await flushPromises() expect(runs()).toHaveLength(3) - const busy = '다른 필터를 받은편지함에 적용하는 중입니다. 끝난 뒤 다시 시도해 주세요.' + const busy = '다른 필터로 받은편지함을 정리하는 중입니다. 끝난 뒤 다시 시도해 주세요.' expect(shown()).toEqual([ - ['progress', '‘둘’ 필터를 받은편지함에 적용하는 중 · 0 / 8,412통 확인'], + ['progress', '‘둘’ 필터로 받은편지함을 정리하는 중 · 0 / 8,412통 확인'], ['error', busy], ['error', busy], ]) @@ -342,10 +342,10 @@ describe('useMailFilterRun', () => { // 앞 적용이 끝났다 — 바쁨 알림의 [다시 시도]는 멈춘 자리(cursor c1 · 4,200통 · 12통)에서 이어 한다 press(resumed) - expect(shown()).toEqual([['progress', '‘하나’ 필터를 받은편지함에 적용하는 중 · 4,200 / 8,412통 확인']]) + expect(shown()).toEqual([['progress', '‘하나’ 필터로 받은편지함을 정리하는 중 · 4,200 / 8,412통 확인']]) await flushPromises() expect(cursors()).toEqual([null, 'c1', null, 'c1']) - expect(shown()).toEqual([['success', '‘하나’ 필터를 받은편지함의 메일 37통에 적용했습니다.']]) + expect(shown()).toEqual([['success', '‘하나’ 필터로 받은편지함의 메일 37통을 정리했습니다.']]) }) describe('끝난 뒤 개수 · 목록 맞추기 (4단계 오래 걸리는 처리와 같은 결)', () => { diff --git a/frontend/src/pages/LoginPage.restricted.spec.ts b/frontend/src/pages/LoginPage.restricted.spec.ts index 0bfc2c9..6be4a49 100644 --- a/frontend/src/pages/LoginPage.restricted.spec.ts +++ b/frontend/src/pages/LoginPage.restricted.spec.ts @@ -151,9 +151,9 @@ describe('LoginPage — 제한된 세션 (13단계)', () => { expect(wrapper.find('#restricted-password-temporary').exists()).toBe(false) expect(wrapper.find('#restricted-password-code').exists()).toBe(false) expect(wrapper.get('label[for="restricted-password-next"]').text()).toBe('새 비밀번호') - expect(wrapper.text()).toContain('8자 이상 · 임시 비밀번호와 달라야 합니다. 영문 · 숫자 · 기호를 섞으면 더 안전합니다.') + expect(wrapper.text()).toContain('8자 이상이고 임시 비밀번호와 달라야 합니다. 영문, 숫자, 기호를 섞으면 더 안전합니다.') expect(wrapper.get('[data-restricted-submit]').text()).toBe('비밀번호 바꾸고 계속') - expect(wrapper.text()).toContain('메일 프로그램(아웃룩 등)을 연결해 두었다면 바꾼 뒤 다시 연결해야 합니다.') + expect(wrapper.text()).toContain('아웃룩 같은 메일 프로그램을 연결해 두었다면 비밀번호를 바꾼 뒤 다시 연결해 주세요.') expect(document.activeElement?.id).toBe('restricted-password-next') // 비밀번호는 칸 · 반응형 상태에 남기지 않는다 — 로그인 칸은 사라졌다 expect(wrapper.find('#login-password').exists()).toBe(false) @@ -279,7 +279,7 @@ describe('LoginPage — 제한된 세션 (13단계)', () => { expect(wrapper.get('h1').text()).toBe('새 비밀번호를 정해 주세요') expect(wrapper.find('#restricted-password-temporary').exists()).toBe(true) expect(wrapper.get('label[for="restricted-password-code"]').text()).toBe('인증 앱 코드') - expect(wrapper.text()).toContain('로그인할 때 넣은 코드는 다시 쓸 수 없습니다 — 앱에 다음 코드가 뜨면 넣어 주세요.') + expect(wrapper.text()).toContain('로그인할 때 넣은 코드는 다시 쓸 수 없으니, 앱에 다음 코드가 뜨면 넣어 주세요.') expect(urls()).toContain('GET /two-factor') }) @@ -366,16 +366,16 @@ describe('LoginPage — 제한된 세션 (13단계)', () => { it('새 키를 받아 QR · 키를 그린다 — 지금 비밀번호는 묻지 않고(메모리) 문의 줄 · 아래 안내', async () => { const { wrapper } = await atSetupStep({ supportContact: () => ok({ text: CONTACT }) }) - expect(wrapper.get('h1').text()).toBe('2단계 인증을 켜야 합니다') + expect(wrapper.get('h1').text()).toBe('2단계 인증을 켜 주세요') expect(urls()).toContain('POST /two-factor/enrollments') expect(wrapper.find('[data-qr-code]').exists()).toBe(true) expect(wrapper.get('[data-setup-key]').text().replace(/\s/g, '')).toBe(enrollment(1).secret) - expect(wrapper.text()).toContain(`앱에는 「ARA SYSTEM (${USER.email})」로 저장됩니다.`) + expect(wrapper.text()).toContain(`앱에는 ‘ARA SYSTEM (${USER.email})’로 저장됩니다.`) expect(wrapper.get('label[for="restricted-setup-code"]').text()).toBe('앱에 표시된 6자리 코드') expect(wrapper.find('#restricted-setup-password').exists()).toBe(false) expect(wrapper.get('[data-support-line]').text()).toBe(`인증 앱을 쓸 수 없나요?${CONTACT}`) expect(wrapper.get('[data-support-contact]').text()).toBe(CONTACT) - expect(wrapper.text()).toContain('Outlook · 휴대폰 메일 앱은 이 코드를 묻지 않습니다.') + expect(wrapper.text()).toContain('Outlook이나 휴대폰 메일 앱은 이 코드를 묻지 않습니다.') expect(document.activeElement?.id).toBe('restricted-setup-code') }) @@ -428,7 +428,7 @@ describe('LoginPage — 제한된 세션 (13단계)', () => { const { wrapper } = await mountLogin({ activate: () => fail(400, 'AUTH_005') }) expect(wrapper.get('label[for="restricted-setup-password"]').text()).toBe('지금 비밀번호') - expect(wrapper.text()).toContain('새로고침해서 다시 넣어야 합니다.') + expect(wrapper.text()).toContain('새로고침한 뒤에는 지금 비밀번호를 한 번 더 넣어 주세요.') await wrapper.get('#restricted-setup-code').setValue('482913') await submitStep(wrapper) expect(bodyOf('/two-factor/activation')).toEqual([]) @@ -453,7 +453,7 @@ describe('LoginPage — 제한된 세션 (13단계)', () => { await submitStep(wrapper) expect(router.currentRoute.value.name).toBe('login') - expect(wrapper.get('h1').text()).toBe('2단계 인증을 켜야 합니다') + expect(wrapper.get('h1').text()).toBe('2단계 인증을 켜 주세요') expect(useToastStore().toasts.map((toast) => toast.message)).toEqual(['새 비밀번호를 정했습니다.']) expect(wrapper.find('#restricted-setup-password').exists()).toBe(false) expect(document.activeElement?.id).toBe('restricted-setup-code') @@ -491,7 +491,7 @@ describe('LoginPage — 제한된 세션 (13단계)', () => { }) expect(router.currentRoute.value.name).toBe('login') - expect(wrapper.get('h1').text()).toBe('2단계 인증을 켜야 합니다') + expect(wrapper.get('h1').text()).toBe('2단계 인증을 켜 주세요') expect(wrapper.find('#restricted-setup-password').exists()).toBe(false) await wrapper.get('#restricted-setup-code').setValue('482913') await submitStep(wrapper) diff --git a/frontend/src/pages/LoginPage.spec.ts b/frontend/src/pages/LoginPage.spec.ts index a9e1061..41e401a 100644 --- a/frontend/src/pages/LoginPage.spec.ts +++ b/frontend/src/pages/LoginPage.spec.ts @@ -206,7 +206,7 @@ describe('LoginPage — 2단계 인증 코드 단계', () => { expect(input.attributes('maxlength')).toBeUndefined() expect(document.activeElement?.id).toBe('login-code') expect(wrapper.text()).toContain('인증 앱을 쓸 수 없나요? 관리자에게 문의해 주세요.') - expect(wrapper.text()).toContain('Outlook · 휴대폰 메일 앱은 이 코드를 묻지 않습니다.') + expect(wrapper.text()).toContain('Outlook이나 휴대폰 메일 앱은 이 코드를 묻지 않습니다.') }) it('문의 연락처가 있으면(13단계) 「인증 앱을 쓸 수 없나요?」 다음 줄에 연락처', async () => { diff --git a/frontend/src/pages/LoginPage.vue b/frontend/src/pages/LoginPage.vue index b78ff78..727d19d 100644 --- a/frontend/src/pages/LoginPage.vue +++ b/frontend/src/pages/LoginPage.vue @@ -449,7 +449,7 @@ void supportContactStore.ensureLoaded() 로그인 @@ -524,7 +524,7 @@ void supportContactStore.ensureLoaded() :contact="supportContactStore.text" /> diff --git a/frontend/src/pages/contacts/ContactsPage.spec.ts b/frontend/src/pages/contacts/ContactsPage.spec.ts index 9c04d86..45ef922 100644 --- a/frontend/src/pages/contacts/ContactsPage.spec.ts +++ b/frontend/src/pages/contacts/ContactsPage.spec.ts @@ -387,7 +387,7 @@ describe('ContactsPage (주소록 모듈)', () => { const row = app.get('[data-contacts-row="c2"]') expect(row.get('.contacts-row__meta').text()).toBe('(주)한빛상사 · 영업팀') expect(row.get('.contacts-row__email').text()).toBe('jihoon.park@hanbit.example') - expect(app.get('[data-contact-none]').text()).toContain('연락처를 고르세요') + expect(app.get('[data-contact-none]').text()).toContain('연락처를 골라 주세요') await click(row.get('a')) expect(router.currentRoute.value.fullPath).toBe('/contacts/all/c2') @@ -624,11 +624,11 @@ describe('ContactsPage (주소록 모듈)', () => { const { app } = await mountApp('/contacts/directory/people/s5') expect(app.get('#contacts-list-title').text()).toBe('직원') - expect(app.get('.contacts-list__note').text()).toBe('메일 서버에 계정이 있는 직원입니다 — 이름과 주소만 보입니다.') + expect(app.get('.contacts-list__note').text()).toBe('회사 메일을 쓰는 직원이라 이름과 메일 주소만 보입니다.') const detail = app.get('[data-directory-detail="s5"]') expect(detail.get('.contact-detail__subtitle').text()).toBe('사내 직원') expect(detail.findAll('.tool-button').map((button) => button.text())).toEqual(['메일 쓰기', '내 주소록에 추가']) - expect(detail.get('.contact-detail__hint').text()).toContain('사내 목록은 메일 서버의 계정에서 옵니다') + expect(detail.get('.contact-detail__hint').text()).toContain('사내 목록은 이름과 주소만 보이고 여기서 고칠 수 없습니다') await click(detail.get('[data-contact-add-mine]')) expect(isOpen(app, '#contact-dialog')).toBe(true) @@ -693,7 +693,7 @@ describe('ContactsPage (주소록 모듈)', () => { await click(app.get('[data-group-row="g1"] a')) const input = app.get('[data-contacts-search]') expect(input.attributes('aria-label')).toBe('주소록에서 찾기') - expect(input.attributes('placeholder')).toBe('이름 · 주소 · 회사 · 전화번호 — 초성도 됩니다 (ㅂㅈㅎ)') + expect(input.attributes('placeholder')).toBe('이름, 주소, 회사, 전화번호, 초성(ㅂㅈㅎ)으로 찾기') await input.setValue('박') clock.advance(CONTACTS_SEARCH_DELAY_MS - 1) @@ -721,7 +721,7 @@ describe('ContactsPage (주소록 모듈)', () => { await flushPromises() const empty = app.get('[data-contacts-empty="search"]') expect(empty.text()).toContain('‘없는사람’에 맞는 연락처가 없습니다') - expect(empty.text()).toContain('이름 · 주소 · 회사 · 전화번호의 일부나 이름의 초성으로 찾습니다.') + expect(empty.text()).toContain('이름, 주소, 회사, 전화번호의 일부나 이름의 초성으로 찾을 수 있습니다.') // Esc 는 찾을 글을 지운다 — 목록이 다시 그룹 전체로 await input.trigger('keydown', { key: 'Escape' }) @@ -742,7 +742,7 @@ describe('ContactsPage (주소록 모듈)', () => { await click(app.get('[data-contacts-source="directory:person"]')) expect(router.currentRoute.value.fullPath).toBe('/contacts/directory/people') expect((input.element as HTMLInputElement).value).toBe('') - expect(input.attributes('placeholder')).toBe('이름 · 주소 — 초성도 됩니다 (ㅂㅈㅎ)') + expect(input.attributes('placeholder')).toBe('이름, 주소, 초성(ㅂㅈㅎ)으로 찾기') }) it('그룹 ⋯ — 이름 바꾸기 · 그룹에 메일 쓰기 · 그룹 지우기(빨간 글), 구성원이 없으면 메일 쓰기를 끈다 · 지우기는 확인 없이 + [되돌리기]', async () => { @@ -764,7 +764,7 @@ describe('ContactsPage (주소록 모듈)', () => { expect(router.currentRoute.value.fullPath).toBe('/contacts') expect(app.find('[data-group-row="g1"]').exists()).toBe(false) const [toast] = useToastStore().toasts - expect(toast?.message).toBe('‘거래처’ 그룹을 지웠습니다 — 연락처 3명은 그대로 있습니다.') + expect(toast?.message).toBe('‘거래처’ 그룹을 지웠습니다. 그룹에 있던 연락처 3명은 그대로 남아 있습니다.') expect(toast?.action?.label).toBe('되돌리기') if (toast?.action && 'run' in toast.action) toast.action.run() @@ -953,8 +953,8 @@ describe('ContactsPage (주소록 모듈)', () => { const empty = app.get('[data-contacts-empty="first"]') expect(empty.text()).toContain('아직 연락처가 없습니다') - expect(empty.text()).toContain('자주 메일을 주고받는 사람을 넣어 두면 받는 사람 칸에서 바로 찾습니다.') - expect(empty.text()).toContain('메일을 읽다가 ⋯ › 「보낸 사람을 주소록에 추가」로도 넣을 수 있습니다.') + expect(empty.text()).toContain('자주 메일을 주고받는 사람을 넣어 두면 받는 사람 칸에서 바로 찾을 수 있습니다.') + expect(empty.text()).toContain('메일을 읽다가 [⋯] → [보낸 사람을 주소록에 추가]로도 넣을 수 있습니다.') await click(empty.get('button')) expect(isOpen(app, '#contact-dialog')).toBe(true) expect(app.get('#contact-dialog-title').text()).toBe('연락처 추가') @@ -1001,7 +1001,7 @@ describe('ContactsPage (주소록 모듈)', () => { await click(app.get('[data-group-row="g2"] button[data-mailbox-more]')) await click(app.findAll('[role="menuitem"]')[2] ?? app.get('[role="menuitem"]')) expect(useToastStore().toasts.map((toast) => [toast.message, toast.action ?? null])).toEqual([ - ['‘프로젝트 알파’ 그룹을 지웠습니다 — 연락처 1명은 그대로 있습니다.', null], + ['‘프로젝트 알파’ 그룹을 지웠습니다. 그룹에 있던 연락처 1명은 그대로 남아 있습니다.', null], ]) }) diff --git a/frontend/src/pages/mail/ComposePage.drafts.spec.ts b/frontend/src/pages/mail/ComposePage.drafts.spec.ts index 93e13fd..32c894e 100644 --- a/frontend/src/pages/mail/ComposePage.drafts.spec.ts +++ b/frontend/src/pages/mail/ComposePage.drafts.spec.ts @@ -446,7 +446,7 @@ describe('ComposePage — 임시 저장 · 서명 (2단계)', () => { expect(wrapper.get('.compose__send').text()).toBe('보내기') // 꺼진 단추는 aria-disabled (11단계 — 초점은 받는다) · 까닭은 단추 옆 글 expect(wrapper.get('.compose__send').attributes('aria-disabled')).toBe('true') - expect(wrapper.get('#compose-send-why').text()).toBe('보낼 수 없는 파일이 있습니다 — 빨간 줄의 파일을 빼 주세요.') + expect(wrapper.get('#compose-send-why').text()).toBe('보낼 수 없는 파일이 있습니다. 빨간 줄의 파일을 빼 주세요.') expect(wrapper.text()).toContain(ERROR_CODE_MESSAGES.MAIL_004) expect(document.activeElement?.textContent?.trim()).toBe('파일 첨부') expect(useToastStore().toasts).toEqual([]) diff --git a/frontend/src/pages/mail/ComposePage.large-files.spec.ts b/frontend/src/pages/mail/ComposePage.large-files.spec.ts index cdced1f..0ca15e0 100644 --- a/frontend/src/pages/mail/ComposePage.large-files.spec.ts +++ b/frontend/src/pages/mail/ComposePage.large-files.spec.ts @@ -239,9 +239,9 @@ describe('ComposePage — 대용량 첨부', () => { expect(phases(wrapper)).toEqual(['ready', 'waiting-file', 'missing']) expect(lineOf(wrapper, 'ready')).toBe('1.2 GB · 이 임시 저장본에 11월 25일까지 남습니다') - expect(lineOf(wrapper, 'waiting-file')).toContain('올리다 멈춘 파일 — 같은 파일을 다시 고르면 이어서 올립니다') + expect(lineOf(wrapper, 'waiting-file')).toContain('올리다 멈춤 · 160 MB / 612 MB 올림 · 같은 파일을 다시 고르면 이어서 올립니다') expect(wrapper.find('[data-phase="waiting-file"] [data-large-repick]').exists()).toBe(true) - expect(lineOf(wrapper, 'missing')).toBe('설정 › 대용량 첨부에서 지운 파일입니다 — 빼고 다시 올려 주세요.') + expect(lineOf(wrapper, 'missing')).toBe('[설정] → [대용량 첨부]에서 지운 파일입니다. 빼고 다시 올려 주세요.') const send = () => wrapper.get('.compose__send') expect(send().attributes('aria-disabled')).toBe('true') @@ -283,7 +283,7 @@ describe('ComposePage — 대용량 첨부', () => { expect((sent?.data as Record | undefined)?.largeFileIds).toEqual(['lf-1']) expect(calls(requests)).toContain('GET /file-uploads/lf-1') expect(phases(wrapper)).toEqual(['missing']) - expect(lineOf(wrapper, 'missing')).toBe('서버에서 찾을 수 없는 파일입니다 — 빼고 다시 올려 주세요.') + expect(lineOf(wrapper, 'missing')).toBe('파일을 찾을 수 없습니다. 빼고 다시 올려 주세요.') expect(wrapper.get('.compose__send').attributes('aria-disabled')).toBe('true') expect(wrapper.get('#compose-send-why').text()).toBe(SEND_BLOCK_TEXT.unsendable) expect(useToastStore().toasts).toEqual([]) @@ -308,7 +308,7 @@ describe('ComposePage — 대용량 첨부', () => { const lookup = requests.find((request) => request.url === '/share-link-lookups') expect(lookup?.data).toEqual({ urls: [URL1, URL2] }) expect(wrapper.findAll('[data-quoted-link]').map((notice) => notice.text())).toEqual([ - '원래 메일의 대용량 첨부 2개는 인용 속 링크로 함께 갑니다 — 11월 29일까지', + '원래 메일의 대용량 첨부 2개는 인용 속 링크로 함께 갑니다. 11월 29일까지 받을 수 있습니다.', '인용 속 대용량 첨부 링크는 공유가 멈춰 받을 수 없습니다.', ]) // 원래 메일의 대용량 첨부는 다시 싣지 않는다 — 링크 묶음이 없다 @@ -379,7 +379,7 @@ describe('ComposePage — 대용량 첨부', () => { await flushPromises() expect(phases(wrapper)).toEqual(['ready']) - expect(lineOf(wrapper, 'ready')).toBe('2 MB · 보안 검사 중 — 끝나지 않아도 보낼 수 있습니다') + expect(lineOf(wrapper, 'ready')).toBe('2 MB · 보안 검사 중이지만 보낼 수 있습니다') expect(wrapper.find('#compose-send-why').exists()).toBe(false) }) diff --git a/frontend/src/pages/mail/ComposePage.sendlater.spec.ts b/frontend/src/pages/mail/ComposePage.sendlater.spec.ts index a5f89ff..9c69327 100644 --- a/frontend/src/pages/mail/ComposePage.sendlater.spec.ts +++ b/frontend/src/pages/mail/ComposePage.sendlater.spec.ts @@ -216,7 +216,7 @@ describe('ComposePage — 보내기 취소 · 예약 발송 (9단계)', () => { expect(toasts().map((toast) => toast.kind)).toEqual(['error']) }) - it('일부가 받지 못하면 빨간 알림 "보냈지만 1명은 받지 못합니다 — …" 에도 [보내기 취소] (같은 마감)', async () => { + it('일부가 받지 못하면 빨간 알림 "보냈지만 1명은 받지 못합니다. …" 에도 [보내기 취소] (같은 마감)', async () => { const { wrapper } = await mountCompose(server([undoReply([NOBODY])])) await addRecipient(wrapper, 'doyun.lee@arasystem.it.kr') await addRecipient(wrapper, NOBODY.email) @@ -226,7 +226,7 @@ describe('ComposePage — 보내기 취소 · 예약 발송 (9단계)', () => { expect(toasts()).toEqual([ expect.objectContaining({ kind: 'error', - message: '보냈지만 1명은 받지 못합니다 — nobody@arasystem.it.kr (없는 주소)', + message: '보냈지만 1명은 받지 못합니다. nobody@arasystem.it.kr (없는 주소)', action: expect.objectContaining({ label: '보내기 취소' }), expiresAt: NOW + 10_000, }), @@ -369,7 +369,7 @@ describe('ComposePage — 보내기 취소 · 예약 발송 (9단계)', () => { const dialog = wrapper.get('#compose-schedule-title').element.closest('dialog') expect(dialog?.hasAttribute('open')).toBe(true) // 이 PC 의 검사로는 맞는 시각이다(시계가 어긋남) — 가까운 쪽(5분) 한도로 알린다 - expect(wrapper.get('#compose-schedule-error').text()).toBe('지금부터 5분 뒤 이후의 시각을 고르세요.') + expect(wrapper.get('#compose-schedule-error').text()).toBe('5분 뒤보다 이른 시각은 고를 수 없습니다. 더 늦은 시각을 골라 주세요.') expect((wrapper.get('[data-schedule-preset="custom"]').element as HTMLInputElement).checked).toBe(true) const chosen = new Date(preset.at) expect((wrapper.get('[data-schedule-day]').element as HTMLInputElement).value).toBe(formatDate(chosen)) diff --git a/frontend/src/pages/mail/ComposePage.spec.ts b/frontend/src/pages/mail/ComposePage.spec.ts index 273840b..a64c16e 100644 --- a/frontend/src/pages/mail/ComposePage.spec.ts +++ b/frontend/src/pages/mail/ComposePage.spec.ts @@ -319,7 +319,7 @@ describe('ComposePage', () => { expect(useToastStore().toasts).toEqual([ expect.objectContaining({ kind: 'error', - message: '보냈지만 1명은 받지 못했습니다 — nobody@arasystem.it.kr (없는 주소)', + message: '보냈지만 1명은 받지 못했습니다. nobody@arasystem.it.kr (없는 주소)', // 성공 알림과 같은 곳 — 링크가 있으니 10초 (toast.store) action: { label: '보낸편지함 보기', to: { name: 'mail-mailbox', params: { mailboxId: 'sent' } } }, }), @@ -538,7 +538,7 @@ describe('ComposePage', () => { const { wrapper } = await mountCompose('/mail/compose', replyApi) expect(wrapper.get('.compose__attach-hint').text()).toBe( - '파일당 최대 10MB · 합계 30MB · 최대 20개 · 더 큰 파일은 링크로 보냅니다 (파일당 2GB · 10개까지)', + '파일당 최대 10MB · 합계 30MB · 최대 20개 · 더 큰 파일은 파일당 2GB, 10개까지 링크로 보냅니다', ) }) @@ -547,7 +547,7 @@ describe('ComposePage', () => { expect(useMailStore().quota).toBeNull() expect(wrapper.get('.compose__attach-hint').text()).toBe( - '파일당 최대 20MB · 합계 25MB · 최대 20개 · 더 큰 파일은 링크로 보냅니다 (파일당 2GB · 10개까지)', + '파일당 최대 20MB · 합계 25MB · 최대 20개 · 더 큰 파일은 파일당 2GB, 10개까지 링크로 보냅니다', ) }) @@ -1483,7 +1483,7 @@ describe('ComposePage', () => { expect(dialogOpen(wrapper)).toBe(false) expect(blocksUnload()).toBe(false) expect(useToastStore().toasts.map((toast) => [toast.kind, toast.message])).toEqual([ - ['error', '보냈지만 1명은 받지 못했습니다 — nobody@arasystem.it.kr (받는 쪽이 거절)'], + ['error', '보냈지만 1명은 받지 못했습니다. nobody@arasystem.it.kr (받는 쪽이 거절)'], ]) }) @@ -1567,7 +1567,7 @@ describe('ComposePage', () => { await flushPromises() expect(useToastStore().toasts.map((toast) => [toast.kind, toast.message])).toEqual([ - ['error', '보냈지만 1명은 받지 못했습니다 — infra@arasystem.it.kr (지금은 받을 수 없음)'], + ['error', '보냈지만 1명은 받지 못했습니다. infra@arasystem.it.kr (지금은 받을 수 없음)'], ]) expect(router.currentRoute.value.fullPath).toBe('/mail/sent') }) diff --git a/frontend/src/pages/mail/ComposePage.vue b/frontend/src/pages/mail/ComposePage.vue index 6cfb108..f923fc8 100644 --- a/frontend/src/pages/mail/ComposePage.vue +++ b/frontend/src/pages/mail/ComposePage.vue @@ -1256,7 +1256,7 @@ watch(hasMissingLarge, (missing) => { v-model="subject" type="text" class="compose__subject" - placeholder="제목을 입력하세요" + placeholder="제목을 입력해 주세요" autocomplete="off" :maxlength="MAIL_SUBJECT_MAX_LENGTH" :disabled="preparing" diff --git a/frontend/src/pages/mail/MailboxPage.scheduled.spec.ts b/frontend/src/pages/mail/MailboxPage.scheduled.spec.ts index 292e89d..7a68de9 100644 --- a/frontend/src/pages/mail/MailboxPage.scheduled.spec.ts +++ b/frontend/src/pages/mail/MailboxPage.scheduled.spec.ts @@ -126,12 +126,12 @@ describe('예약됨 · 보낸편지함 (9단계)', () => { })) } - it('분할 보기 — 예약한 메일을 보낼 시각 차례로: 받는 사람 · 제목 · 보낼 시각(청록) · 머리 "예약됨 · 2통 · 보낼 시각 차례"', async () => { + it('분할 보기 — 예약한 메일을 보낼 시각 차례로: 받는 사람 · 제목 · 보낼 시각(청록) · 머리 "예약됨 · 2통 · 보낼 시각이 이른 순서"', async () => { const { wrapper } = await mountAt('/mail/scheduled') expect(requests.find((request) => request.url === '/scheduled-messages')?.params).toEqual({ limit: 20 }) expect(wrapper.get('#scheduled-list-title').text()).toBe('예약됨') - expect(wrapper.get('.list-header__count').text()).toBe('2통 · 보낼 시각 차례') + expect(wrapper.get('.list-header__count').text()).toBe('2통 · 보낼 시각이 이른 순서') expect(rows(wrapper)).toEqual([ { who: '받는 사람: 이도윤', diff --git a/frontend/src/pages/mail/MailboxPage.search.spec.ts b/frontend/src/pages/mail/MailboxPage.search.spec.ts index 2cd43f4..c521b6c 100644 --- a/frontend/src/pages/mail/MailboxPage.search.spec.ts +++ b/frontend/src/pages/mail/MailboxPage.search.spec.ts @@ -114,7 +114,7 @@ describe('MailboxPage — 검색 결과', () => { expect(wrapper.text()).toContain('‘회의’에 맞는 메일이 없습니다') expect(wrapper.text()).toContain( - '낱말 단위로 찾습니다. 붙여 쓴 말의 일부(‘서버이전’의 ‘서버’)나 제목 맨 앞의 말머리([공지] · Re:)로는 찾지 못합니다.', + '낱말 단위로 찾습니다. ‘서버이전’의 ‘서버’처럼 붙여 쓴 말의 일부나, 제목 맨 앞의 [공지]나 Re: 같은 말머리로는 찾지 못합니다.', ) await wrapper.get('.search-empty__all').trigger('click') await flushPromises() @@ -124,7 +124,7 @@ describe('MailboxPage — 검색 결과', () => { expect(listRequests().at(-1)?.params).toEqual({ q: '회의', limit: 20 }) // 전체에서 찾았으면 더 넓힐 곳이 없다 — 안내 줄만 expect(wrapper.find('.search-empty__all').exists()).toBe(false) - expect(wrapper.get('.search-scope__hint').text()).toBe('휴지통 · 스팸메일함은 빼고 찾습니다') + expect(wrapper.get('.search-scope__hint').text()).toBe('휴지통과 스팸메일함은 빼고 찾습니다') }) it('범위 줄에서 "전체 메일함"을 누르면 기록을 쌓지 않고 전체에서 다시 찾고, "받은편지함"을 누르면 돌아온다', async () => { @@ -167,7 +167,7 @@ describe('MailboxPage — 검색 결과', () => { useMailStore().rememberSearchOrigin({ mailboxId: 'sent', location: '/mail/sent' }) const { wrapper, router } = await mountAt(`/mail/search?q=${Q}&in=sent`) - expect(wrapper.get('.search-header__close').attributes('title')).toBe('검색 닫기 — 보낸편지함으로') + expect(wrapper.get('.search-header__close').attributes('title')).toBe('검색을 닫고 보낸편지함으로 돌아가기') await wrapper.get('.search-header__close').trigger('click') await flushPromises() diff --git a/frontend/src/pages/settings/AccountAppPasswordsPage.spec.ts b/frontend/src/pages/settings/AccountAppPasswordsPage.spec.ts index 31ef97e..7c47c8a 100644 --- a/frontend/src/pages/settings/AccountAppPasswordsPage.spec.ts +++ b/frontend/src/pages/settings/AccountAppPasswordsPage.spec.ts @@ -147,10 +147,10 @@ describe('AccountAppPasswordsPage', () => { const settings = wrapper.get('[data-mail-client-settings]').text() expect(settings).toContain('mx1.arasystem.it.kr · 포트 993 · SSL/TLS') expect(settings).toContain('mx1.arasystem.it.kr · 포트 465 · SSL/TLS') - expect(settings).toContain('ara.kim@arasystem.it.kr — 메일 주소 전체') - expect(settings).toContain('여기서 만든 앱 비밀번호 — 웹메일 비밀번호가 아닙니다') + expect(settings).toContain('ara.kim@arasystem.it.kr · 메일 주소 전체') + expect(settings).toContain('웹메일 비밀번호가 아닌, 여기서 만든 앱 비밀번호') expect(wrapper.get('[data-app-password-notes]').text()).toContain( - '웹메일 비밀번호를 바꾸면 앱 비밀번호도 모두 지워집니다 — 메일 프로그램마다 새로 만들어 주세요.', + '웹메일 비밀번호를 바꾸면 앱 비밀번호도 모두 지워지니, 메일 프로그램마다 새로 만들어 주세요.', ) }) @@ -214,7 +214,7 @@ describe('AccountAppPasswordsPage', () => { expect(dialogOpen('app-password-create-title')).toBe(true) expect(document.activeElement?.id).toBe('app-password-name') expect(wrapper.get('#app-password-name').attributes('maxlength')).toBe('40') - expect(wrapper.get('#app-password-name-help').text()).toBe('어느 프로그램 · 기기에서 쓰는지 알아볼 이름입니다. 40자까지.') + expect(wrapper.get('#app-password-name-help').text()).toBe('어느 프로그램이나 기기에서 쓰는지 알아볼 수 있게 지어 주세요. 40자까지 쓸 수 있습니다.') await create(wrapper, ' ') expect(wrapper.get('#app-password-name-help').text()).toBe('이름을 넣어 주세요.') @@ -237,7 +237,7 @@ describe('AccountAppPasswordsPage', () => { expect(dialogOpen('app-password-create-title')).toBe(false) expect(dialogOpen('app-password-result-title')).toBe(true) expect(document.querySelector('#app-password-result-message')?.textContent?.trim()).toBe( - '‘집 노트북 메일’ — 메일 프로그램의 비밀번호 칸에 아래 값을 넣어 주세요.', + '‘집 노트북 메일’ 앱 비밀번호입니다. 메일 프로그램의 비밀번호 칸에 아래 값을 넣어 주세요.', ) expect(wrapper.findAll('.result__group').map((group) => group.text())).toEqual([ 'app_', @@ -370,7 +370,7 @@ describe('AccountAppPasswordsPage', () => { replies[0]?.(fail(400, 'VAL_001')) await flushPromises() - expect(wrapper.get('#app-password-name-help').text()).toBe('어느 프로그램 · 기기에서 쓰는지 알아볼 이름입니다. 40자까지.') + expect(wrapper.get('#app-password-name-help').text()).toBe('어느 프로그램이나 기기에서 쓰는지 알아볼 수 있게 지어 주세요. 40자까지 쓸 수 있습니다.') expect(toastMessages()).toEqual([]) }) diff --git a/frontend/src/pages/settings/AccountLoginHistoryPage.spec.ts b/frontend/src/pages/settings/AccountLoginHistoryPage.spec.ts index faf6048..f91c269 100644 --- a/frontend/src/pages/settings/AccountLoginHistoryPage.spec.ts +++ b/frontend/src/pages/settings/AccountLoginHistoryPage.spec.ts @@ -193,7 +193,7 @@ describe('AccountLoginHistoryPage', () => { ]) expect(wrapper.get('[data-sessions-end-others]').text()).toBe('다른 곳 모두 로그아웃') const note = wrapper.get('[data-sessions-note]') - expect(note.text()).toBe('웹메일 로그인만 보입니다. 메일 프로그램은 앱 비밀번호를 지워 끊습니다.') + expect(note.text()).toBe('웹메일 로그인만 보입니다. 메일 프로그램의 연결을 끊으려면 앱 비밀번호를 지워 주세요.') expect(note.get('a').attributes('href')).toBe('/settings/account/app-passwords') }) @@ -204,7 +204,7 @@ describe('AccountLoginHistoryPage', () => { expect(wrapper.get('[data-session="old"] [data-session-device]').text()).toBe('알 수 없는 브라우저') }) - it('줄의 [로그아웃]은 묻지 않고 바로 끊는다 — 「‘Edge · Windows’의 로그인을 끊었습니다.」 · 줄이 빠지고 초점은 다음 줄', async () => { + it('줄의 [로그아웃]은 묻지 않고 바로 끊는다 — 「‘Edge · Windows’에서 로그아웃했습니다.」 · 줄이 빠지고 초점은 다음 줄', async () => { const wrapper = await mountPage() const button = wrapper.findAll('[data-session-end]')[0] ;(button?.element as HTMLButtonElement).focus() @@ -214,7 +214,7 @@ describe('AccountLoginHistoryPage', () => { expect(requests.filter((request) => request.method === 'DELETE').map((request) => request.url)).toEqual([ '/auth/sessions/edge', ]) - expect(toastMessages()).toEqual(['‘Edge · Windows’의 로그인을 끊었습니다.']) + expect(toastMessages()).toEqual(['‘Edge · Windows’에서 로그아웃했습니다.']) expect(sessionRows(wrapper).map((row) => row[0])).toEqual(['Chrome · Windows', 'Safari · iOS']) expect(wrapper.get('[data-sessions-count]').text()).toBe('웹메일 2곳') expect(document.activeElement?.getAttribute('aria-label')).toBe('‘Safari · iOS’ 로그아웃') @@ -225,11 +225,11 @@ describe('AccountLoginHistoryPage', () => { await wrapper.findAll('[data-session-end]')[1]?.trigger('click') await flushPromises() - expect(toastMessages()).toEqual(['‘Safari · iOS’의 로그인을 끊었습니다.']) + expect(toastMessages()).toEqual(['‘Safari · iOS’에서 로그아웃했습니다.']) expect(sessionRows(wrapper)).toHaveLength(2) }) - it('[다른 곳 모두 로그아웃] → 확인 창(처음 초점 「취소」 · 끊을 곳의 이름) → 「다른 곳의 로그인 2곳을 끊었습니다.」 — 지금 이 브라우저만 남는다', async () => { + it('[다른 곳 모두 로그아웃] → 확인 창(처음 초점 「취소」 · 끊을 곳의 이름) → 「다른 2곳에서 로그아웃했습니다.」 — 지금 이 브라우저만 남는다', async () => { const wrapper = await mountPage() await wrapper.get('[data-sessions-end-others]').trigger('click') await flushPromises() @@ -238,9 +238,9 @@ describe('AccountLoginHistoryPage', () => { expect(dialog?.hasAttribute('open')).toBe(true) expect(dialog?.getAttribute('role')).toBe('alertdialog') expect(document.querySelector('#end-other-sessions-message')?.textContent?.trim()).toBe( - '이 브라우저를 뺀 2곳(Edge · Windows, Safari · iOS)의 웹메일 로그인을 끊습니다. 끊긴 곳은 다음 동작에서 바로 로그인 화면으로 돌아갑니다.', + '이 브라우저를 뺀 2곳(Edge · Windows, Safari · iOS)의 웹메일에서 로그아웃합니다. 그곳에서 다음에 무엇이든 누르면 바로 로그인 화면이 나옵니다.', ) - expect(dialog?.textContent).toContain('메일 프로그램은 끊기지 않습니다 — 앱 비밀번호를 지워 끊어 주세요.') + expect(dialog?.textContent).toContain('메일 프로그램은 로그아웃되지 않습니다. 연결을 끊으려면 앱 비밀번호를 지워 주세요.') expect(document.activeElement?.hasAttribute('data-end-others-cancel')).toBe(true) // 데이터를 지우는 일이 아니다 — 청록 주 단추 expect(wrapper.get('[data-end-others-confirm]').classes()).not.toContain('modal-dialog__action--danger') @@ -249,12 +249,12 @@ describe('AccountLoginHistoryPage', () => { await flushPromises() expect(requests.filter((request) => request.method === 'DELETE').map((request) => request.url)).toEqual(['/auth/sessions']) - expect(toastMessages()).toEqual(['다른 곳의 로그인 2곳을 끊었습니다.']) + expect(toastMessages()).toEqual(['다른 2곳에서 로그아웃했습니다.']) expect(dialog?.hasAttribute('open')).toBe(false) expect(sessionRows(wrapper).map((row) => row[0])).toEqual(['Chrome · Windows']) expect(wrapper.find('[data-sessions-end-others]').exists()).toBe(false) expect(wrapper.get('[data-sessions-note]').text()).toBe( - '다른 곳에 로그인된 브라우저가 없습니다. 웹메일 로그인만 보입니다. 메일 프로그램은 앱 비밀번호를 지워 끊습니다.', + '다른 곳에 로그인된 브라우저가 없습니다. 웹메일 로그인만 보입니다. 메일 프로그램의 연결을 끊으려면 앱 비밀번호를 지워 주세요.', ) expect(document.activeElement?.id).toBe('login-sessions-title') }) @@ -273,10 +273,10 @@ describe('AccountLoginHistoryPage', () => { await wrapper.get('[data-sessions-end-others]').trigger('click') await flushPromises() expect(document.querySelector('#end-other-sessions-title')?.textContent?.trim()).toBe( - '이 브라우저를 포함해 모든 곳의 로그인을 끊을까요?', + '이 브라우저를 포함해 모든 곳에서 로그아웃할까요?', ) expect(document.querySelector('#end-other-sessions-message')?.textContent?.trim()).toBe( - '이 브라우저를 알아보지 못해 이 브라우저를 포함한 3곳(Chrome · Windows, Edge · Windows, Safari · iOS)의 웹메일 로그인을 끊습니다. 이 브라우저도 로그아웃되어 로그인 화면으로 돌아갑니다.', + '이 브라우저를 알아보지 못해서 이 브라우저를 포함한 3곳(Chrome · Windows, Edge · Windows, Safari · iOS)의 웹메일에서 로그아웃합니다. 이 브라우저도 로그아웃되어 로그인 화면으로 돌아갑니다.', ) await wrapper.get('[data-end-others-confirm]').trigger('click') @@ -288,7 +288,7 @@ describe('AccountLoginHistoryPage', () => { 'GET /auth/sessions', 'POST /auth/refresh', ]) - expect(toastMessages()).toContain('이 브라우저를 포함한 3곳의 로그인을 끊었습니다.') + expect(toastMessages()).toContain('이 브라우저를 포함해 3곳에서 로그아웃했습니다.') // 빈 목록을 그려 두지 않는다 — 다시 받은 요청이 세션 만료로 이어졌다 expect(wrapper.find('[data-session]').exists()).toBe(false) expect(useAuthStore().sessionExpired).toBe(true) @@ -329,11 +329,11 @@ describe('AccountLoginHistoryPage', () => { expect(wrapper.get('[data-events-hint]').text()).toBe('최근 90일 · 웹메일과 메일 프로그램') expect(eventRows(wrapper)).toEqual([ - [time(0), '웹메일', '211.186.95.252', 'Chrome · Windows', '성공 · 2단계 인증'], - [time(1), '웹메일', '211.186.95.252', 'Chrome · Windows', '실패 — 코드 틀림'], + [time(0), '웹메일', '211.186.95.252', 'Chrome · Windows', '2단계 인증으로 성공'], + [time(1), '웹메일', '211.186.95.252', 'Chrome · Windows', '코드가 틀려 실패'], [time(2), '메일 프로그램 · IMAP앱 비밀번호 ‘회사 PC 아웃룩’', '211.186.95.252', '—알 수 없음', '성공'], - [time(3), '메일 프로그램 · SMTP', '45.133.10.21', '—알 수 없음', '실패 — 앱 비밀번호 필요'], - [time(4), '웹메일', '211.186.95.252', '알 수 없는 브라우저', '실패 — 비밀번호 틀림'], + [time(3), '메일 프로그램 · SMTP', '45.133.10.21', '—알 수 없음', '앱 비밀번호가 필요해 실패'], + [time(4), '웹메일', '211.186.95.252', '알 수 없는 브라우저', '비밀번호가 틀려 실패'], ]) expect(eventRows(wrapper)[0]?.[0]).toBe('오늘 오전 9:30') expect(wrapper.findAll('[data-event-result]').map((cell) => cell.classes().includes('events__result--failure'))).toEqual([ @@ -363,9 +363,9 @@ describe('AccountLoginHistoryPage', () => { expect(all.attributes('aria-pressed')).toBe('false') expect(wrapper.get('[data-events-hint]').text()).toBe('최근 90일 · 실패 4번') expect(eventRows(wrapper).map((row) => row[4])).toEqual([ - '실패 — 코드 틀림', - '실패 — 앱 비밀번호 필요', - '실패 — 비밀번호 틀림', + '코드가 틀려 실패', + '앱 비밀번호가 필요해 실패', + '비밀번호가 틀려 실패', ]) // 같은 보기를 다시 누르면 받지 않는다 @@ -374,7 +374,7 @@ describe('AccountLoginHistoryPage', () => { expect(eventRequests()).toHaveLength(2) }) - it('빈 상태(실패만) — "최근 90일 동안 실패한 로그인이 없습니다" / "[전체]를 누르면 성공한 로그인도 봅니다."', async () => { + it('빈 상태(실패만) — "최근 90일 동안 실패한 로그인이 없습니다" / "[전체]를 누르면 성공한 로그인도 볼 수 있습니다."', async () => { const wrapper = await mountPage({ events: (request) => (request.params.isSuccess === false ? ok(pageOf([], null, 0)) : ok(pageOf(EVENTS))), }) @@ -383,7 +383,7 @@ describe('AccountLoginHistoryPage', () => { expect(wrapper.find('[data-login-event]').exists()).toBe(false) expect(wrapper.get('[data-events-empty]').text()).toContain('최근 90일 동안 실패한 로그인이 없습니다') - expect(wrapper.get('[data-events-empty]').text()).toContain('[전체]를 누르면 성공한 로그인도 봅니다.') + expect(wrapper.get('[data-events-empty]').text()).toContain('[전체]를 누르면 성공한 로그인도 볼 수 있습니다.') expect(wrapper.get('[data-events-hint]').text()).toBe('최근 90일 · 실패 0번') }) @@ -398,7 +398,7 @@ describe('AccountLoginHistoryPage', () => { }, }) expect(wrapper.get('[data-events-more]').text()).toBe('더 보기') - expect(wrapper.text()).toContain('최근 순서 · 20개씩. 90일이 지난 기록은 지웁니다.') + expect(wrapper.text()).toContain('최근 순서로 20개씩 보입니다. 90일이 지난 기록은 지워집니다.') await wrapper.get('[data-events-filter="failures"]').trigger('click') await flushPromises() diff --git a/frontend/src/pages/settings/AccountMailClientsPage.spec.ts b/frontend/src/pages/settings/AccountMailClientsPage.spec.ts index 75985a6..0f42313 100644 --- a/frontend/src/pages/settings/AccountMailClientsPage.spec.ts +++ b/frontend/src/pages/settings/AccountMailClientsPage.spec.ts @@ -53,12 +53,12 @@ describe('AccountMailClientsPage', () => { const wrapper = await mountPage() expect(wrapper.get('h1').text()).toBe('메일 프로그램 연결') - expect(wrapper.text()).toContain('아웃룩 · 썬더버드 · 휴대폰 메일 앱에서 회사 메일을 보는 방법입니다.') + expect(wrapper.text()).toContain('아웃룩, 썬더버드, 휴대폰 메일 앱에서 회사 메일을 보는 방법입니다.') expect(settingsRows(wrapper)).toEqual([ 'mx1.arasystem.it.kr · 포트 993 · SSL/TLS', 'mx1.arasystem.it.kr · 포트 465 · SSL/TLS', - `${USER.email} — 메일 주소 전체`, - '앱 비밀번호 — 웹메일 비밀번호가 아닙니다', + `${USER.email} · 메일 주소 전체`, + '웹메일 비밀번호가 아닌 앱 비밀번호', ]) expect(requests.map((request) => request.url)).toEqual(['/two-factor']) }) @@ -72,10 +72,10 @@ describe('AccountMailClientsPage', () => { expect(wrapper.get('[data-mail-clients-create]').attributes('href')).toBe('/settings/account/app-passwords') }) - it('켜지 않은 직원 — 비밀번호 줄은 「앱 비밀번호(권장) …」, 청록 상자 「앱 비밀번호로 연결하기를 권합니다」', async () => { + it('켜지 않은 직원 — 비밀번호 줄은 「앱 비밀번호를 권합니다. …」, 청록 상자 「앱 비밀번호로 연결하기를 권합니다」', async () => { const wrapper = await mountPage(() => ok(OFF)) - expect(settingsRows(wrapper).at(-1)).toBe('앱 비밀번호(권장) — 2단계 인증을 켜지 않았으면 웹메일 비밀번호도 됩니다') + expect(settingsRows(wrapper).at(-1)).toBe('앱 비밀번호를 권합니다. 2단계 인증을 켜지 않았다면 웹메일 비밀번호로도 연결됩니다.') const notice = wrapper.get('[data-mail-clients-notice]') expect(notice.attributes('data-mail-clients-notice')).toBe('recommend') expect(notice.text()).toContain('앱 비밀번호로 연결하기를 권합니다') @@ -92,7 +92,7 @@ describe('AccountMailClientsPage', () => { const wrapper = await mountPage() expect(wrapper.get('[data-mail-clients-cloud-warning]').text()).toBe( - '새 Outlook · 휴대폰 Outlook 앱은 메일을 Microsoft 클라우드를 거쳐 받습니다 — 회사 정책에 따라 쓰지 않는 것이 좋습니다.', + '새 Outlook과 휴대폰 Outlook 앱은 메일을 Microsoft 클라우드를 거쳐 받습니다. 회사 정책에 따라 쓰지 않는 것이 좋습니다.', ) const buttons = wrapper.findAll('[data-mail-client] h3 button') expect(buttons.map((button) => button.text())).toEqual([ @@ -118,14 +118,14 @@ describe('AccountMailClientsPage', () => { expect(panel.attributes('role')).toBe('region') expect(panel.attributes('aria-labelledby')).toBe('mail-client-outlook-button') expect(panel.findAll('li')).toHaveLength(5) - expect(panel.text()).toContain('Outlook 에서 [파일] → [계정 추가]를 누릅니다.') + expect(panel.text()).toContain('Outlook에서 [파일] → [계정 추가]를 누릅니다.') // 호스트 이름은 굵게 expect(panel.get('strong').text()).toBe('mx1.arasystem.it.kr') await wrapper.get('#mail-client-android-button').trigger('click') expect(outlook.attributes('aria-expanded')).toBe('false') expect(wrapper.find('#mail-client-outlook-panel').exists()).toBe(false) - expect(wrapper.get('#mail-client-android-panel').text()).toContain('보안 유형은 「SSL/TLS」를 넣고 [다음]을 누릅니다.') + expect(wrapper.get('#mail-client-android-panel').text()).toContain('보안 유형은 [SSL/TLS]를 고른 뒤 [다음]을 누릅니다.') await wrapper.get('#mail-client-android-button').trigger('click') expect(wrapper.findAll('[role="region"]')).toHaveLength(0) @@ -136,6 +136,6 @@ describe('AccountMailClientsPage', () => { const notes = wrapper.get('[data-mail-clients-notes]').findAll('p').map((note) => note.text()) expect(notes).toHaveLength(4) - expect(notes[1]).toContain('POP3 로는 연결할 수 없습니다 — 계정 유형은 IMAP 을 골라 주세요.') + expect(notes[1]).toContain('POP3로는 연결할 수 없으니 계정 유형은 IMAP을 골라 주세요.') }) }) diff --git a/frontend/src/pages/settings/AccountPasswordPage.spec.ts b/frontend/src/pages/settings/AccountPasswordPage.spec.ts index 0646d50..ed14167 100644 --- a/frontend/src/pages/settings/AccountPasswordPage.spec.ts +++ b/frontend/src/pages/settings/AccountPasswordPage.spec.ts @@ -79,18 +79,18 @@ describe('AccountPasswordPage', () => { expect(wrapper.get('#password-current').attributes('autocomplete')).toBe('current-password') expect(wrapper.get('#password-next').attributes('autocomplete')).toBe('new-password') expect(wrapper.get('#password-next-hint').text()).toBe( - '8자 이상 · 지금 비밀번호와 달라야 합니다. 영문 · 숫자 · 기호를 섞으면 더 안전합니다.', + '8자 이상이고 지금 비밀번호와 달라야 합니다. 영문, 숫자, 기호를 섞으면 더 안전합니다.', ) // 모든 직원 — 다른 곳 로그인이 끊기고 앱 비밀번호도 모두 지워진다 (12단계) expect(wrapper.get('[data-password-note]').text()).toContain( - '바꾸면 이 브라우저를 뺀 다른 기기 · 브라우저의 웹메일 로그인이 끊깁니다.', + '바꾸면 이 브라우저를 빼고 다른 기기와 브라우저의 웹메일에서 로그아웃됩니다.', ) expect(wrapper.get('[data-password-note]').text()).toContain( - '앱 비밀번호도 모두 지워집니다 — 메일 프로그램은 새 앱 비밀번호로 다시 연결해 주세요.', + '앱 비밀번호도 모두 지워지니, 메일 프로그램은 새 앱 비밀번호로 다시 연결해 주세요.', ) // 2단계 인증이 꺼진 직원 — 머리 설명은 그대로, 코드 칸이 없다 expect(wrapper.get('.settings-page__description').text()).toBe( - '웹메일과 휴대폰 메일 앱(IMAP)이 함께 쓰는 메일 계정의 비밀번호입니다.', + '웹메일에 로그인할 때와 휴대폰 메일 앱을 연결할 때 함께 쓰는 비밀번호입니다.', ) expect(wrapper.find('#password-code').exists()).toBe(false) @@ -148,7 +148,7 @@ describe('AccountPasswordPage', () => { // 서버가 엔진을 두 번 부르고 다른 세션을 끊는다 — 요청 한도는 보통(API_TIMEOUT_MS)보다 긴 30초 expect(PASSWORD_CHANGE_TIMEOUT_MS).toBe(30_000) expect(passwordRequests()[0]?.config.timeout).toBe(PASSWORD_CHANGE_TIMEOUT_MS) - expect(wrapper.get('#password-status').text()).toBe('비밀번호를 바꿨습니다. 다른 기기 · 브라우저의 로그인은 끊었습니다.') + expect(wrapper.get('#password-status').text()).toBe('비밀번호를 바꿨습니다. 다른 기기와 브라우저에서는 로그아웃했습니다.') for (const id of ['password-current', 'password-next', 'password-confirm']) { expect((wrapper.get(`#${id}`).element as HTMLInputElement).value).toBe('') } @@ -234,7 +234,7 @@ describe('AccountPasswordPage', () => { autocomplete: 'one-time-code', 'aria-describedby': 'password-code-hint', }) - expect(wrapper.get('#password-code-hint').text()).toBe('코드는 30초마다 바뀝니다 — 다른 칸을 다 쓴 뒤 넣어 주세요.') + expect(wrapper.get('#password-code-hint').text()).toBe('코드는 30초마다 바뀌니 다른 칸을 다 채운 뒤에 넣어 주세요.') }) it('코드는 숫자만 받는다 — 붙여 넣은 "123 456" · "123-456" 은 다듬고, 6자리가 아니면 보내지 않는다(사전 문구 AUTH_007)', async () => { diff --git a/frontend/src/pages/settings/AccountTwoFactorPage.spec.ts b/frontend/src/pages/settings/AccountTwoFactorPage.spec.ts index 89faa38..7bce367 100644 --- a/frontend/src/pages/settings/AccountTwoFactorPage.spec.ts +++ b/frontend/src/pages/settings/AccountTwoFactorPage.spec.ts @@ -120,10 +120,10 @@ describe('AccountTwoFactorPage', () => { expect(wrapper.find('[data-two-factor-notes]').exists()).toBe(false) }) - it('켜짐 — 「켜짐 — 2026년 10월 31일부터」 · [2단계 인증 끄기] · 안내 상자 / 켠 때를 모르면 「켜짐」', async () => { + it('켜짐 — 「2026년 10월 31일부터 켜짐」 · [2단계 인증 끄기] · 안내 상자 / 켠 때를 모르면 「켜짐」', async () => { const wrapper = await mountPage({ status: () => ok(ON) }) - expect(wrapper.get('[data-two-factor-label]').text()).toBe('켜짐 — 2026년 10월 31일부터') + expect(wrapper.get('[data-two-factor-label]').text()).toBe('2026년 10월 31일부터 켜짐') expect(wrapper.get('[data-two-factor-label]').classes()).toContain('two-factor__state-label--on') expect(wrapper.get('[data-two-factor-disable]').text()).toBe('2단계 인증 끄기') expect(wrapper.find('[data-two-factor-warning]').exists()).toBe(false) @@ -176,7 +176,7 @@ describe('AccountTwoFactorPage', () => { ['K7QF', '2M4X', 'HB6R', 'TZ3N'], ['PW5C', 'YL7D', 'JQ2V', 'MGAS'], ]) - expect(wrapper.text()).toContain('앱에는 「ARA SYSTEM (ara.kim@arasystem.it.kr)」로 저장됩니다.') + expect(wrapper.text()).toContain('앱에는 ‘ARA SYSTEM (ara.kim@arasystem.it.kr)’로 저장됩니다.') expect(document.activeElement?.id).toBe('two-factor-enable-code') expect(wrapper.get('#two-factor-enable-code').attributes()).toMatchObject({ inputmode: 'numeric', @@ -243,11 +243,11 @@ describe('AccountTwoFactorPage', () => { // 서버가 엔진을 여러 번 부른다 — 한도는 보통(10초)보다 긴 30초 expect(calls('/two-factor/activation')[0]?.config.timeout).toBe(TWO_FACTOR_CHANGE_TIMEOUT_MS) expect(enableDialog()?.hasAttribute('open')).toBe(false) - expect(wrapper.get('[data-two-factor-label]').text()).toBe('켜짐 — 2026년 10월 31일부터') + expect(wrapper.get('[data-two-factor-label]').text()).toBe('2026년 10월 31일부터 켜짐') const done = wrapper.get('[data-two-factor-done]') expect(done.attributes('role')).toBe('status') expect(done.text()).toContain('2단계 인증을 켰습니다') - expect(done.text()).toContain('메일 프로그램(Outlook · 썬더버드 · 휴대폰 메일 앱)은 다시 연결할 때 앱 비밀번호가 필요합니다.') + expect(done.text()).toContain('Outlook, 썬더버드, 휴대폰 메일 앱 같은 메일 프로그램은 다시 연결할 때 앱 비밀번호가 필요합니다.') expect(done.get('a').text()).toBe('앱 비밀번호 만들기') expect(done.get('a').attributes('href')).toBe('/settings/account/app-passwords') expect(document.activeElement).toBe(done.element) @@ -668,7 +668,7 @@ describe('AccountTwoFactorPage — 필수인 직원 (13단계)', () => { expect(wrapper.get('[data-two-factor-locked]').text()).toBe('회사 정책에 따라 끌 수 없습니다') expect(wrapper.find('[data-two-factor-grace]').exists()).toBe(false) const notes = wrapper.get('[data-two-factor-notes]').text() - expect(notes).toContain('휴대폰을 바꿀 때는 관리자에게 2단계 인증 해제를 요청해 주세요 — 다음 로그인 때 새 휴대폰으로 다시 켭니다.') + expect(notes).toContain('휴대폰을 바꿀 때는 관리자에게 2단계 인증을 풀어 달라고 요청해 주세요. 다음에 로그인할 때 새 휴대폰으로 다시 켜면 됩니다.') expect(notes).toContain('휴대폰을 잃어버렸거나 인증 앱을 지웠으면 관리자에게 문의해 주세요.') }) }) diff --git a/frontend/src/pages/settings/MailBlocksPage.spec.ts b/frontend/src/pages/settings/MailBlocksPage.spec.ts index 99a51e1..e2f729f 100644 --- a/frontend/src/pages/settings/MailBlocksPage.spec.ts +++ b/frontend/src/pages/settings/MailBlocksPage.spec.ts @@ -119,11 +119,11 @@ describe('MailBlocksPage', () => { expect(empty?.[1]).not.toMatch(/display:\s*none/) }) - it('비었으면 "차단한 주소가 없습니다. 메일의 ⋯ › 보낸 사람 차단으로도 막을 수 있습니다."', async () => { + it('비었으면 "차단한 주소가 없습니다. 메일을 읽다가 [⋯] → [보낸 사람 차단]으로도 막을 수 있습니다."', async () => { const wrapper = await mountPage({ blocks: [] }) expect(wrapper.get('[data-block-empty]').text()).toBe( - '차단한 주소가 없습니다. 메일의 ⋯ › 보낸 사람 차단으로도 막을 수 있습니다.', + '차단한 주소가 없습니다. 메일을 읽다가 [⋯] → [보낸 사람 차단]으로도 막을 수 있습니다.', ) expect(wrapper.get('[data-block-count]').text()).toBe('0 / 500') }) @@ -150,12 +150,12 @@ describe('MailBlocksPage', () => { it.each([ ['형식', 'kim@', '메일 주소나 @도메인 형식으로 써 주세요.'], ['이미 차단함', 'NEWS@cloudbrief.example', '이미 차단한 주소입니다.'], - ['내 주소', 'ara.kim@arasystem.it.kr', '내 주소 · 사내 도메인은 차단할 수 없습니다.'], - ['사내 도메인', '@arasystem.it.kr', '내 주소 · 사내 도메인은 차단할 수 없습니다.'], + ['내 주소', 'ara.kim@arasystem.it.kr', '내 주소와 사내 도메인은 차단할 수 없습니다.'], + ['사내 도메인', '@arasystem.it.kr', '내 주소와 사내 도메인은 차단할 수 없습니다.'], [ '여러 사람이 쓰는 메일 서비스', '@gmail.com', - 'gmail.com 처럼 여러 사람이 쓰는 메일 서비스는 도메인으로 막을 수 없습니다 — 주소로 막아 주세요.', + 'gmail.com처럼 여러 사람이 쓰는 메일 서비스는 도메인 전체를 막을 수 없습니다. 주소를 하나씩 넣어 막아 주세요.', ], ])('보내기 전 검사 — %s: 칸 아래 오류(빨강) · 잘못된 입력, 보내지 않는다', async (_label, value, text) => { const wrapper = await mountPage() @@ -247,7 +247,7 @@ describe('MailBlocksPage', () => { }) describe('엔진에서 우리 규칙이 꺼져 있으면(isActive: false) 머리 아래 노란 줄 — 단추 없음', () => { - const INACTIVE = '지금은 차단이 동작하지 않습니다. 메일 서버의 규칙이 다른 곳에서 바뀌었습니다 — 주소를 차단하거나 풀면 다시 켜집니다.' + const INACTIVE = '다른 곳에서 메일 설정이 바뀌어 지금은 차단이 되지 않습니다. 주소를 차단하거나 풀면 다시 켜집니다.' it('차단한 주소가 있을 때만 보인다(role=status) — 켜져 있거나 비었으면 없다', async () => { const off = await mountPage({ isActive: false }) diff --git a/frontend/src/pages/settings/MailFilterPage.spec.ts b/frontend/src/pages/settings/MailFilterPage.spec.ts index 5a4b971..bc6b779 100644 --- a/frontend/src/pages/settings/MailFilterPage.spec.ts +++ b/frontend/src/pages/settings/MailFilterPage.spec.ts @@ -237,24 +237,24 @@ describe('MailFilterPage', () => { expect(wrapper.get('ol').attributes('aria-busy')).toBeUndefined() expect(rows(wrapper).map((item) => item.classes('is-off'))).toEqual([true, false, false, true, false]) expect(wrapper.findAll('[data-filter-missing]').map((item) => item.text())).toEqual([ - '옮길 메일함이 지워져 받은편지함으로 들어옵니다 — 눌러서 고치세요.', + '옮길 메일함이 지워져 메일이 받은편지함으로 들어옵니다. 눌러서 고쳐 주세요.', ]) expect(row(wrapper, 'r5').get('[data-filter-edit]').attributes('aria-label')).toBe('‘알파 프로젝트’ 필터 고치기') // 맨 위의 ▲ · 맨 아래의 ▼ 는 누를 수 없다 (초점은 그대로 받는다) expect(row(wrapper, 'r1').get('[data-filter-up]').attributes('aria-disabled')).toBe('true') expect(row(wrapper, 'r1').get('[data-filter-down]').attributes('aria-disabled')).toBeUndefined() expect(row(wrapper, 'r5').get('[data-filter-down]').attributes('aria-disabled')).toBe('true') - expect(wrapper.get('[data-filter-count]').text()).toBe('필터 5개 · 50개까지 만들 수 있습니다. 행을 누르면 고칩니다.') + expect(wrapper.get('[data-filter-count]').text()).toBe('필터 5개를 만들었고, 50개까지 만들 수 있습니다. 줄을 누르면 고칠 수 있습니다.') expect(wrapper.get('[data-filter-notes]').findAll('p').map((note) => note.text())).toEqual([ - '·위에서부터 차례로 적용합니다. 메일함으로 옮기는 필터에 맞으면 그 아래 필터는 적용하지 않습니다 — 순서는 ▲ ▼ 로 바꿉니다.', - '·이미 받은 메일에도 적용하려면 필터를 저장할 때 「받은편지함에 이미 있는 메일에도 적용」을 고릅니다. 받은편지함의 메일만 보고, 그 필터 하나만 적용합니다.', - '·스팸으로 판정되어 들어오는 메일에는 필터를 적용하지 않습니다 — 그대로 스팸메일함으로 갑니다.', - '·전달은 사내 주소로만, 새로 오는 메일만 합니다. 메일 한 통은 한 곳으로만 전달됩니다(위의 필터가 먼저). 전달해도 내 메일함에 그대로 남습니다.', - '·부재중 자동 응답은 필터와 함께 동작합니다.', + '·위에 있는 필터부터 차례로 메일을 정리합니다. 메일함으로 옮기는 필터에 맞은 메일은 그 아래 필터를 거치지 않습니다. 순서는 ▲ ▼ 로 바꿀 수 있습니다.', + '·이미 받은 메일도 정리하려면 필터를 저장할 때 [받은편지함에 이미 있는 메일에도 적용]을 골라 주세요. 받은편지함에 있는 메일만 그 필터 하나로 정리합니다.', + '·스팸으로 분류된 메일은 필터를 거치지 않고 그대로 스팸메일함으로 갑니다.', + '·전달은 새로 오는 메일만 사내 주소로 합니다. 메일 한 통은 한 곳으로만 전달되고, 전달하는 필터가 여럿이면 위에 있는 필터가 먼저입니다. 전달해도 내 메일함에 그대로 남습니다.', + '·필터가 메일을 옮겨도 부재중 자동 응답은 그대로 보냅니다.', ]) - expect(wrapper.get('[data-filter-note-apply] strong').text()).toBe('「받은편지함에 이미 있는 메일에도 적용」') + expect(wrapper.get('[data-filter-note-apply] strong').text()).toBe('[받은편지함에 이미 있는 메일에도 적용]') expect(wrapper.get('.settings-page__description').text()).toBe( - '받은 메일을 조건에 따라 메일함으로 옮기거나, 읽음 · 중요로 표시하거나, 동료에게 전달합니다. 새로 오는 메일에 적용하고, 고를 때는 받은편지함에 이미 있는 메일에도 적용합니다.', + '받은 메일을 조건에 따라 메일함으로 옮기거나, 읽음이나 중요로 표시하거나, 동료에게 전달합니다. 새로 오는 메일을 정리하고, 저장할 때 고르면 받은편지함에 이미 있는 메일도 함께 정리합니다.', ) expect(wrapper.find('[data-filter-empty]').exists()).toBe(false) expect(wrapper.find('[data-filter-conflict]').exists()).toBe(false) @@ -403,7 +403,7 @@ describe('MailFilterPage', () => { await save(wrapper) expect(wrapper.get('#mail-filter-condition-0-error').text()).toBe( - '‘${’ 는 쓸 수 없습니다 — 메일 서버가 다른 뜻으로 읽습니다.', + '‘${’는 필터에 쓸 수 없습니다. 이 글자를 빼고 넣어 주세요.', ) expect(wrapper.get('#mail-filter-forward-help').text()).toBe('사내 주소(@arasystem.it.kr)로만 전달할 수 있습니다.') expect(document.activeElement).toBe(value.element) @@ -477,7 +477,7 @@ describe('MailFilterPage', () => { const select = wrapper.get('#mail-filter-move-target') const options = select.findAll('option') expect(options.map((option) => option.text())).toEqual([ - '(지워진 메일함) — 다른 메일함을 고르세요', + '지워진 메일함이니 다른 메일함을 골라 주세요', '받은편지함', '거래처', '전체 공지', @@ -721,7 +721,7 @@ describe('MailFilterPage', () => { const { wrapper } = await mountPage({ filters: { ...SET, isActive: false } }) const bar = wrapper.get('[data-filter-inactive]') - expect(bar.text()).toContain('지금은 필터와 부재중 자동 응답이 동작하지 않습니다. 메일 서버의 규칙이 다른 곳에서 바뀌었습니다.') + expect(bar.text()).toContain('다른 곳에서 메일 설정이 바뀌어 지금은 필터와 부재중 자동 응답이 되지 않습니다.') await click(bar.get('button')) expect(puts()).toEqual([{ rules: RULES, version: 'v1' }]) @@ -820,7 +820,7 @@ describe('MailFilterPage', () => { const applyHelp = (wrapper: VueWrapper) => wrapper.get('#mail-filter-apply-help').text() const runs = () => requests.filter((request) => request.url === '/mail-filter-runs').map((request) => request.data as MailFilterRunRequest) - const HELP = '저장한 뒤 받은편지함에서 조건에 맞는 메일을 찾아 바로 적용합니다.' + const HELP = '저장하면 받은편지함에서 조건에 맞는 메일을 찾아 바로 정리합니다.' it('할 일 아래 — 창을 열 때마다 꺼져 있고 설명은 칸의 aria-describedby, 전달도 하면 설명에 더한다 · 상자 아래 스팸 한 줄', async () => { const { wrapper } = await mountPage() @@ -831,7 +831,7 @@ describe('MailFilterPage', () => { expect(applyBox(wrapper).attributes('aria-describedby')).toBe('mail-filter-apply-help') expect(wrapper.get('[data-filter-apply] label').text()).toBe('받은편지함에 이미 있는 메일에도 적용') expect(applyHelp(wrapper)).toBe(HELP) - expect(wrapper.get('[data-filter-apply-spam]').text()).toBe('스팸으로 판정되어 들어오는 메일에는 적용하지 않습니다.') + expect(wrapper.get('[data-filter-apply-spam]').text()).toBe('스팸으로 분류된 메일은 필터를 거치지 않습니다.') // 6단계의 안내 띠는 없다 expect(wrapper.text()).not.toContain('새로 오는 메일에만 적용됩니다') @@ -852,7 +852,7 @@ describe('MailFilterPage', () => { expect(applyBox(wrapper).attributes('disabled')).toBeDefined() expect(wrapper.get('[data-filter-apply] label').classes()).toContain('is-disabled') - expect(applyHelp(wrapper)).toBe('전달만 하는 필터는 이미 받은 메일에 할 일이 없습니다 — 전달은 새로 오는 메일에만 합니다.') + expect(applyHelp(wrapper)).toBe('전달은 새로 오는 메일에만 해서, 전달만 하는 필터는 이미 받은 메일에 할 일이 없습니다.') await wrapper.get('#mail-filter-read').setValue(true) expect(applyBox(wrapper).attributes('disabled')).toBeUndefined() @@ -863,7 +863,7 @@ describe('MailFilterPage', () => { const { wrapper } = await mountPage() await openRule(wrapper, 'r5') expect(applyBox(wrapper).attributes('disabled')).toBeDefined() - expect(applyHelp(wrapper)).toBe('옮길 메일함이 지워져 이미 받은 메일에 할 일이 없습니다 — 다른 메일함을 고르세요.') + expect(applyHelp(wrapper)).toBe('옮길 메일함이 지워져 이미 받은 메일에 할 일이 없습니다. 다른 메일함을 골라 주세요.') await click(wrapper.get('#mail-filter-cancel')) await openRule(wrapper, 'r2') @@ -927,7 +927,7 @@ describe('MailFilterPage', () => { { ...rule, cursor: 'c1' }, ]) expect(useToastStore().toasts.map((toast) => [toast.kind, toast.message])).toEqual([ - ['progress', '‘거래처 메일’ 필터를 받은편지함에 적용하는 중 · 4,500 / 8,412통 확인'], + ['progress', '‘거래처 메일’ 필터로 받은편지함을 정리하는 중 · 4,500 / 8,412통 확인'], ]) second.finish(step(3912, 7, 0, null)) @@ -936,7 +936,7 @@ describe('MailFilterPage', () => { expect(useToastStore().toasts).toHaveLength(1) expect(done).toMatchObject({ kind: 'success', - message: '‘거래처 메일’ 필터를 받은편지함의 메일 37통에 적용했습니다.', + message: '‘거래처 메일’ 필터로 받은편지함의 메일 37통을 정리했습니다.', action: { label: '거래처 보기', to: { name: 'mail-mailbox', params: { mailboxId: 'vendor' } } }, }) expect(toasts()).not.toContain('필터를 저장했습니다.') @@ -952,7 +952,7 @@ describe('MailFilterPage', () => { await openRule(wrapper, 'r2') expect(applyBox(wrapper).attributes('disabled')).toBeDefined() - expect(applyHelp(wrapper)).toBe('다른 필터를 받은편지함에 적용하는 중입니다. 끝난 뒤에 고를 수 있습니다.') + expect(applyHelp(wrapper)).toBe('다른 필터로 받은편지함을 정리하는 중입니다. 끝난 뒤에 고를 수 있습니다.') await click(wrapper.get('#mail-filter-cancel')) // 다른 화면으로 가도 이어 한다 @@ -960,7 +960,7 @@ describe('MailFilterPage', () => { wrapper.unmount() first.finish(step(8412, 12, 0, null)) await flushPromises() - expect(toasts()).toEqual(['‘결재 알림’ 필터를 받은편지함의 메일 12통에 적용했습니다.']) + expect(toasts()).toEqual(['‘결재 알림’ 필터로 받은편지함의 메일 12통을 정리했습니다.']) expect(useToastStore().toasts[0]?.action).toBeNull() const { wrapper: again } = await mountPage() @@ -981,7 +981,7 @@ describe('MailFilterPage', () => { reply.finish(echo(requests.find((request) => request.method === 'PUT') as MockRequest)) await flushPromises() expect(runs().map((request) => request.cursor)).toEqual([null]) - expect(toasts()).toEqual(['‘거래처 메일’ 필터를 받은편지함의 메일 37통에 적용했습니다.']) + expect(toasts()).toEqual(['‘거래처 메일’ 필터로 받은편지함의 메일 37통을 정리했습니다.']) useToastStore().clear() const leaving = deferred() @@ -997,7 +997,7 @@ describe('MailFilterPage', () => { leaving.finish(echo(requests.find((request) => request.method === 'PUT') as MockRequest)) await flushPromises() expect(runs().map((request) => request.cursor)).toEqual([null]) - expect(toasts()).toEqual(['‘거래처 메일’ 필터를 받은편지함의 메일 37통에 적용했습니다.']) + expect(toasts()).toEqual(['‘거래처 메일’ 필터로 받은편지함의 메일 37통을 정리했습니다.']) }) it('저장하는 중에 세션이 바뀌면 적용 요청을 보내지 않는다 — 알림도 없다', async () => { diff --git a/frontend/src/pages/settings/MailLargeFilesPage.spec.ts b/frontend/src/pages/settings/MailLargeFilesPage.spec.ts index a545dd6..5060935 100644 --- a/frontend/src/pages/settings/MailLargeFilesPage.spec.ts +++ b/frontend/src/pages/settings/MailLargeFilesPage.spec.ts @@ -158,7 +158,7 @@ describe('MailLargeFilesPage', () => { expect(view.get('[data-large-usage]').text()).toBe('6.2 / 10 GB') expect(view.get('meter').attributes('aria-valuetext')).toBe('62%') expect(view.get('.large-usage__note').text()).toBe( - '보낸 링크 4.3 GB · 보내지 않은 파일 1.9 GB(임시 저장 · 올리다 멈춤). 기간이 지나거나, 링크를 끊거나, 보내지 않은 파일을 지우면 바로 줄어듭니다.', + '보낸 링크 4.3 GB, 임시 저장했거나 올리다 멈춘 파일 1.9 GB입니다. 기간이 지나거나, 링크를 끊거나, 보내지 않은 파일을 지우면 바로 줄어듭니다.', ) }) @@ -199,7 +199,7 @@ describe('MailLargeFilesPage', () => { await view.get('[data-unsent-delete]').trigger('click') await flushPromises() expect(view.get('#large-delete-message').text()).toBe( - '‘시공도면_v4.dwg’(620 MB) 파일을 지웁니다. 이 파일을 실은 임시 저장본이 있으면 열 때 첨부 칸에 ‘지운 파일’로 보입니다. 지운 파일은 되돌릴 수 없습니다.', + '‘시공도면_v4.dwg’(620 MB) 파일을 지웁니다. 이 파일을 실은 임시 저장본을 열면 첨부 칸에 지운 파일로 보입니다. 지운 파일은 되돌릴 수 없습니다.', ) expect(document.activeElement?.hasAttribute('data-delete-cancel')).toBe(true) diff --git a/frontend/src/pages/settings/MailNotifyPage.spec.ts b/frontend/src/pages/settings/MailNotifyPage.spec.ts index c19981c..d185add 100644 --- a/frontend/src/pages/settings/MailNotifyPage.spec.ts +++ b/frontend/src/pages/settings/MailNotifyPage.spec.ts @@ -56,13 +56,13 @@ describe('MailNotifyPage (설정 › 메일 › 알림)', () => { expect(wrapper.text()).toContain('새 메일은 1분을 기다리지 않고 바로 목록에 나타납니다.') }) - it('묻기 전 — 칸은 비었고 "켜면 브라우저가 … 「허용」을 눌러 주세요.", 보일 것 · 시험 알림은 흐리게', async () => { + it('묻기 전 — 칸은 비었고 "켜면 브라우저가 … [허용]을 눌러 주세요.", 보일 것 · 시험 알림은 흐리게', async () => { const wrapper = await mountPage() expect(enableInput(wrapper).checked).toBe(false) expect(enableInput(wrapper).disabled).toBe(false) expect(wrapper.get('#notify-enabled-note').text()).toBe( - '켜면 브라우저가 알림을 허락할지 한 번 묻습니다 — 「허용」을 눌러 주세요.', + '켜면 브라우저가 알림을 허락할지 한 번 묻습니다. [허용]을 눌러 주세요.', ) expect(wrapper.get('fieldset').attributes('disabled')).toBeDefined() expect(wrapper.get('#notify-test').attributes('disabled')).toBeDefined() @@ -101,7 +101,7 @@ describe('MailNotifyPage (설정 › 메일 › 알림)', () => { expect(enableInput(wrapper).disabled).toBe(true) const note = wrapper.get('#notify-enabled-note') expect(note.text()).toBe( - '브라우저가 이 사이트의 알림을 막고 있습니다. 주소창 왼쪽의 사이트 정보(자물쇠) › 알림을 「허용」으로 바꾼 뒤 이 화면을 새로 고쳐 주세요.', + '브라우저가 이 사이트의 알림을 막고 있습니다. 주소창 왼쪽의 사이트 정보(자물쇠)를 눌러 [알림]을 [허용]으로 바꾼 뒤 이 화면을 새로 고쳐 주세요.', ) expect(note.classes()).toContain('notify-form__note--danger') expect(useMailNotifyStore().settings.enabled).toBe(false) @@ -200,7 +200,7 @@ describe('MailNotifyPage (설정 › 메일 › 알림)', () => { const subject = wrapper.get('#notify-show-subject') expect((subject.element as HTMLInputElement).checked).toBe(true) - expect(wrapper.text()).toContain('끄면 보낸 사람 이름만 보입니다 — 화면을 다른 사람과 함께 보는 자리라면 꺼 두세요.') + expect(wrapper.text()).toContain('끄면 보낸 사람 이름만 보입니다. 다른 사람과 화면을 함께 보는 자리라면 꺼 두는 것이 좋습니다.') await subject.setValue(false) expect(useMailNotifyStore().settings.showSubject).toBe(false) @@ -213,7 +213,7 @@ describe('MailNotifyPage (설정 › 메일 › 알림)', () => { const status = () => wrapper.get('#notify-realtime-status') expect(wrapper.get('h2').text()).toBe('실시간 연결') - expect(status().text()).toBe('연결되어 있습니다 — 새 메일이 오면 바로 목록에 나타납니다.') + expect(status().text()).toBe('연결되어 있어 새 메일이 오면 바로 목록에 나타납니다.') expect(status().find('.realtime-link__dot').classes()).toContain('is-live') expect(wrapper.find('#notify-realtime-notice').exists()).toBe(false) @@ -225,7 +225,7 @@ describe('MailNotifyPage (설정 › 메일 › 알림)', () => { // 끊긴 동안 — 상태 줄 바로 아래, "저절로 다시 잇습니다…" 안내 앞 expect(wrapper.get('#notify-realtime-notice').text()).toBe('연결될 때까지 바탕화면 알림은 오지 않습니다.') expect(wrapper.find('[role="status"] #notify-realtime-notice').exists()).toBe(true) - expect(wrapper.text()).toContain('저절로 다시 잇습니다.') + expect(wrapper.text()).toContain('저절로 다시 연결합니다.') realtime.setStatus('connecting') await flushPromises() @@ -238,9 +238,9 @@ describe('MailNotifyPage (설정 › 메일 › 알림)', () => { const notes = wrapper.get('[data-notify-notes]') expect(notes.findAll('p').map((note) => note.findAll('span').at(-1)?.text())).toEqual([ - '알림 설정은 이 브라우저에만 적용됩니다. 다른 PC · 브라우저에서는 그곳에서 따로 켜 주세요.', + '알림 설정은 이 브라우저에만 저장됩니다. 다른 PC나 브라우저에서는 그곳에서 따로 켜 주세요.', '웹메일 탭이 하나라도 열려 있어야 알림이 옵니다. 탭을 모두 닫으면 오지 않습니다.', - '웹메일 화면을 보고 있을 때는 바탕화면 알림을 보내지 않습니다 — 목록에 바로 보이므로.', + '웹메일 화면을 보고 있을 때는 새 메일이 목록에 바로 보이므로 바탕화면 알림을 보내지 않습니다.', ]) expect(notes.get('strong').text()).toBe('이 브라우저에만') }) diff --git a/frontend/src/pages/settings/MailSendReadPage.spec.ts b/frontend/src/pages/settings/MailSendReadPage.spec.ts index 5293109..69006b8 100644 --- a/frontend/src/pages/settings/MailSendReadPage.spec.ts +++ b/frontend/src/pages/settings/MailSendReadPage.spec.ts @@ -78,15 +78,15 @@ describe('MailSendReadPage', () => { ['30초', 'false'], ]) expect(wrapper.get('#send-read-undo-hint').text()).toBe( - '[보내기]를 누른 뒤 이 시간 동안 [보내기 취소]로 되돌릴 수 있습니다. 그동안 메일은 메일 서버에서 기다리므로 창을 닫아도 보내집니다.', + '[보내기]를 누른 뒤 이 시간 동안 [보내기 취소]로 되돌릴 수 있습니다. 그사이 창을 닫아도 시간이 지나면 메일은 그대로 보내집니다.', ) expect(wrapper.findAll('.send-read__option-label').map((label) => label.text())).toEqual([ '늘 묻기', - '주소록 · 사내 직원의 메일은 늘 보기', + '주소록과 사내 직원의 메일은 늘 보기', '늘 보기', ]) expect(checkedImages(wrapper)).toEqual(['trusted']) - expect(wrapper.get('[data-images-note]').text()).toContain('사내 메일 서버가 대신 받아 보여 줍니다') + expect(wrapper.get('[data-images-note]').text()).toContain('외부 이미지는 회사를 거쳐 불러오므로 보낸 사람은 이 PC의 주소를 알 수 없습니다.') expect(wrapper.get('[data-settings-status]').text()).toBe('') }) diff --git a/frontend/src/pages/settings/MailSignaturePage.spec.ts b/frontend/src/pages/settings/MailSignaturePage.spec.ts index dbbf545..cc5de8e 100644 --- a/frontend/src/pages/settings/MailSignaturePage.spec.ts +++ b/frontend/src/pages/settings/MailSignaturePage.spec.ts @@ -189,7 +189,7 @@ describe('MailSignaturePage', () => { isForNew: true, isForReply: false, }) - expect(wrapper.get('#signature-save-status').text()).toBe('저장했습니다. 새로 여는 쓰기 화면부터 들어갑니다.') + expect(wrapper.get('#signature-save-status').text()).toBe('저장했습니다. 다음에 메일을 쓸 때부터 들어갑니다.') expect(useMailSignatureStore().signature?.isForReply).toBe(false) await wrapper.get('#signature-for-new').setValue(false) @@ -466,10 +466,10 @@ describe('MailSignaturePage', () => { expect(replyMode.attributes('aria-pressed')).toBe('true') expect(wrapper.find('.signature-preview__quote').exists()).toBe(true) - expect(wrapper.get('[data-signature-preview-note]').text()).toBe('답장 · 전달은 인용한 원문 위에 서명을 넣습니다.') + expect(wrapper.get('[data-signature-preview-note]').text()).toBe('답장과 전달에는 인용한 원문 위에 서명을 넣습니다.') await wrapper.get('#signature-for-reply').setValue(false) expect(wrapper.get('[data-signature-preview]').isVisible()).toBe(false) - expect(wrapper.get('[data-signature-preview-note]').text()).toBe('답장 · 전달에는 서명을 넣지 않습니다.') + expect(wrapper.get('[data-signature-preview-note]').text()).toBe('답장과 전달에는 서명을 넣지 않습니다.') }) }) diff --git a/frontend/src/pages/settings/MailVacationPage.spec.ts b/frontend/src/pages/settings/MailVacationPage.spec.ts index b36f711..ae76a7e 100644 --- a/frontend/src/pages/settings/MailVacationPage.spec.ts +++ b/frontend/src/pages/settings/MailVacationPage.spec.ts @@ -102,7 +102,7 @@ describe('MailVacationPage', () => { // 엔진이 바이트로 막는 한도 안 — 한글로 채워도 저장된다 (계약 2절 "부재중 자동 응답"의 "길이 한도의 근거") expect(wrapper.get('#vacation-subject').attributes('maxlength')).toBe('150') expect(wrapper.get('#vacation-body').attributes('maxlength')).toBe('600') - expect(wrapper.get('[data-vacation-preview-note]').text()).toBe('꺼져 있습니다 — 자동으로 답하지 않습니다.') + expect(wrapper.get('[data-vacation-preview-note]').text()).toBe('꺼져 있어 자동으로 답하지 않습니다.') }) it('저장한 값으로 채운다 — 날짜는 브라우저 시간대의 날짜로, 미리 보기는 보낸 사람 · 제목 · 내용', async () => { @@ -112,7 +112,7 @@ describe('MailVacationPage', () => { expect(value(wrapper, '#vacation-from')).toBe('2026-10-30') expect(value(wrapper, '#vacation-to')).toBe('2026-11-03') expect(wrapper.get('#vacation-period-hint').text()).toBe( - '시작일 0시부터 끝나는 날 자정까지 답합니다 (이 브라우저의 시간대 기준).', + '시작일 0시부터 끝나는 날 자정까지 답합니다. 시각은 이 브라우저의 시간대를 따릅니다.', ) expect(value(wrapper, '#vacation-subject')).toBe(SAVED.subject) const preview = wrapper.get('[data-vacation-preview]') @@ -120,7 +120,7 @@ describe('MailVacationPage', () => { expect(preview.get('[data-vacation-preview-subject]').text()).toBe(SAVED.subject) expect(preview.get('[data-vacation-preview-body]').element.textContent).toBe(SAVED.textBody) expect(wrapper.get('[data-vacation-preview-note]').text()).toBe( - '받은 메일의 보낸 사람에게 위 글로 답합니다. 같은 사람에게 매번 보내지는 않습니다(메일 엔진 규칙).', + '받은 메일의 보낸 사람에게 위 글로 답합니다. 같은 사람에게는 매번 보내지 않습니다.', ) }) @@ -215,7 +215,7 @@ describe('MailVacationPage', () => { expect(puts()).toEqual([{ ...SAVED, isEnabled: false, textBody: longBody }]) expect(wrapper.find('#vacation-body-error').exists()).toBe(false) - expect(wrapper.get('#vacation-save-status').text()).toBe('저장했습니다. 자동 응답을 껐습니다 — 써 둔 글은 남겨 둡니다.') + expect(wrapper.get('#vacation-save-status').text()).toBe('저장했습니다. 자동 응답을 껐고, 써 둔 글은 그대로 남아 있습니다.') expect(useMailVacationStore().vacation?.isEnabled).toBe(false) expect(value(wrapper, '#vacation-body')).toBe(longBody) }) @@ -227,7 +227,7 @@ describe('MailVacationPage', () => { await save(wrapper) expect(puts()).toEqual([{ ...SAVED, isEnabled: false }]) - expect(wrapper.get('#vacation-save-status').text()).toBe('저장했습니다. 자동 응답을 껐습니다 — 써 둔 글은 남겨 둡니다.') + expect(wrapper.get('#vacation-save-status').text()).toBe('저장했습니다. 자동 응답을 껐고, 써 둔 글은 그대로 남아 있습니다.') // 설정한 적 없는 계정을 그대로 꺼 둔 채 저장해도 된다 const { wrapper: fresh } = await mountPage() @@ -279,7 +279,7 @@ describe('MailVacationPage', () => { describe('엔진에서 규칙이 꺼졌을 때 (7단계 — 디자인 원본 MailFilterApplyStates)', () => { const INACTIVE_TEXT = - '켜 둔 자동 응답이 지금은 동작하지 않습니다. 메일 서버의 규칙이 다른 곳에서 바뀌었습니다 — 아래 [저장]을 누르면 다시 켜집니다.' + '다른 곳에서 메일 설정이 바뀌어 켜 둔 자동 응답이 지금은 나가지 않습니다. 아래 [저장]을 누르면 다시 켜집니다.' it('켜 두었는데 isActive: false 면 머리 아래 노란 줄(단추 없음) — 저장하면 isActive 없이 보내고 응답의 값으로 내린다', async () => { const { wrapper } = await mountPage({ diff --git a/frontend/src/pages/share/SharePage.spec.ts b/frontend/src/pages/share/SharePage.spec.ts index b3b0180..6a24a10 100644 --- a/frontend/src/pages/share/SharePage.spec.ts +++ b/frontend/src/pages/share/SharePage.spec.ts @@ -108,11 +108,11 @@ describe('SharePage', () => { // 이름과 주소 사이 띄어쓰기는 괄호 칸 안에 둔다 — 좁은 화면은 괄호와 함께 감추고 주소를 다음 줄로 (디자인 원본 SharePage "김아라 ") expect(view.get('.share__intro').text()).toContain('보낸 사람 김아라 · ARA SYSTEM') expect(view.get('.share__intro').text()).toContain( - '2026년 11월 29일(일)까지 내려받을 수 있습니다 (한국 시간) · 30일 남음', + '한국 시간으로 2026년 11월 29일(일)까지 내려받을 수 있습니다. 30일 남았습니다.', ) expect(view.findAll('.share-file__meta').map((meta) => meta.text())).toEqual([ - '1.2 GB · 보안 검사 · 이상 없음', - '612 MB · 보안 검사 · 이상 없음', + '1.2 GB · 보안 검사 결과 이상 없음', + '612 MB · 보안 검사 결과 이상 없음', ]) const download = view.get('[data-share-file="f1"] a.share-file__download') expect(download.attributes('href')).toBe(`/api/public-links/${TOKEN}/files/f1`) @@ -122,11 +122,11 @@ describe('SharePage', () => { ) expect(view.get('.share__report').text()).toBe('받을 이유가 없는 파일이면 security@arasystem.it.kr 로 알려 주세요.') expect(view.get('.share__english').text()).toBe( - 'Files from 김아라 (ara.kim@arasystem.it.kr) — download by Nov 29, 2026 (KST). This page never asks for a login or password.', + 'Files from 김아라 (ara.kim@arasystem.it.kr). Download by Nov 29, 2026 (KST). This page will never ask you to log in or enter a password.', ) // 메일 제목 · 본문 · 받는 사람 · 받은 수는 보이지 않는다 expect(view.text()).not.toContain('회 받음') - expect(document.title).toBe('김아라 님이 보낸 파일 — ARA SYSTEM') + expect(document.title).toBe('김아라 님이 보낸 파일 · ARA SYSTEM') }) it('링크 정보는 쿠키 없이 · 캐시 없이 · 주소를 싣지 않고 묻는다 — 검색 엔진 · referrer 막기 머리도 둔다', async () => { @@ -192,7 +192,7 @@ describe('SharePage', () => { const view = await mountAt(TOKEN, { link: publicLink({ files: [{ ...DESIGN, status: 'scanning' }] }) }, { link: publicLink() }) expect(view.get('[data-share-view="scanning"] h1').text()).toBe('보안 검사 중입니다') - expect(view.get('.share__english').text()).toBe('Security scan in progress — please check again in a few minutes.') + expect(view.get('.share__english').text()).toBe('A security scan is in progress. Please check again in a few minutes.') await view.get('[data-share-recheck]').trigger('click') await flushPromises() @@ -213,7 +213,7 @@ describe('SharePage', () => { const infected = view.get('[data-share-file="f1"]') expect(infected.classes()).toContain('share-file--danger') - expect(infected.get('.share-file__meta').text()).toBe('1.2 GB · 위험 — 막았습니다') + expect(infected.get('.share-file__meta').text()).toBe('1.2 GB · 위험한 내용이 있어 막았습니다') expect(infected.find('.share-file__download').exists()).toBe(false) const unscannable = view.get('[data-share-file="f2"]') expect(unscannable.classes()).toContain('share-file--warning') @@ -287,7 +287,7 @@ describe('SharePage', () => { expect(view.get('h1').text()).toBe('ara.kim@arasystem.it.kr 님이 보낸 파일 1개 (1.2 GB)') expect(view.find('.share__email').exists()).toBe(false) - expect(document.title).toBe('ara.kim@arasystem.it.kr 님이 보낸 파일 — ARA SYSTEM') + expect(document.title).toBe('ara.kim@arasystem.it.kr 님이 보낸 파일 · ARA SYSTEM') }) it('불러오는 중 — 자리 그림은 숨기고 "불러오는 중"을 화면 낭독기에 읽어 준다 (리뷰 L9)', async () => { diff --git a/frontend/src/pages/share/SharePage.vue b/frontend/src/pages/share/SharePage.vue index 038ecaf..692cc60 100644 --- a/frontend/src/pages/share/SharePage.vue +++ b/frontend/src/pages/share/SharePage.vue @@ -267,7 +267,7 @@ watch(