generated from ttipo/project-base
fix(frontend): 화면 문구를 사용자에게 말하는 말투로 고친다
설계 문서처럼 규칙을 적어 둔 문구가 많다는 사용자 지적(2026-11-05) — "메일 서버에 계정이 있는 직원입니다 — 이름과 주소만 보입니다." · "「@도메인」은 그 도메인에서 오는 메일 모두입니다." 같은 글. 화면 상수 · 템플릿 · 문장을 만드는 유틸의 236곳을 무엇이 되는지 · 무엇을 하면 되는지로 다시 썼다: 줄표로 잇지 않고, 「」 대신 [단추 이름], 메일 서버 · 엔진 · 판정 같은 만든 쪽 말을 빼고, 존댓말은 그대로. 한도 · 날짜 · 개수는 빼지 않았다. 그 문구를 그대로 보던 spec 을 함께 맞췄다(lint · type-check · 단위 2,354 · TZ=UTC · build 통과). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -449,7 +449,7 @@ void supportContactStore.ensureLoaded()
|
||||
로그인
|
||||
</h1>
|
||||
<p class="login__subtitle">
|
||||
회사 메일 계정(@{{ COMPANY_MAIL_DOMAIN }})으로 로그인하세요.
|
||||
회사 메일 주소(@{{ COMPANY_MAIL_DOMAIN }})로 로그인해 주세요.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -524,7 +524,7 @@ void supportContactStore.ensureLoaded()
|
||||
:contact="supportContactStore.text"
|
||||
/>
|
||||
<p class="login__note">
|
||||
Outlook이나 휴대폰 기본 메일 앱에서도 같은 계정으로 메일을 쓸 수 있습니다. (IMAP · SMTP 설정)
|
||||
Outlook이나 휴대폰 기본 메일 앱에서도 IMAP, SMTP 설정으로 같은 메일을 쓸 수 있습니다.
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -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],
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
@@ -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([])
|
||||
|
||||
@@ -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<string, unknown> | 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)
|
||||
})
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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: '받는 사람: 이도윤',
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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([])
|
||||
})
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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을 골라 주세요.')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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('휴대폰을 잃어버렸거나 인증 앱을 지웠으면 관리자에게 문의해 주세요.')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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('이 브라우저에만')
|
||||
})
|
||||
|
||||
@@ -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('')
|
||||
})
|
||||
|
||||
|
||||
@@ -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('답장과 전달에는 서명을 넣지 않습니다.')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -108,11 +108,11 @@ describe('SharePage', () => {
|
||||
// 이름과 주소 사이 띄어쓰기는 괄호 칸 안에 둔다 — 좁은 화면은 괄호와 함께 감추고 주소를 다음 줄로 (디자인 원본 SharePage "김아라 <ara.kim@…>")
|
||||
expect(view.get('.share__intro').text()).toContain('보낸 사람 김아라 <ara.kim@arasystem.it.kr> · 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<HTMLAnchorElement>('[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 () => {
|
||||
|
||||
@@ -267,7 +267,7 @@ watch(
|
||||
<span class="share__org"> · {{ APP_NAME }}</span>
|
||||
</p>
|
||||
<p class="share__line">
|
||||
<strong>{{ untilText }}</strong>{{ untilTail }}
|
||||
{{ SHARE_PAGE_TEXT.untilBefore }}<strong>{{ untilText }}</strong>{{ untilTail }}
|
||||
</p>
|
||||
</div>
|
||||
<ul class="share__files">
|
||||
|
||||
Reference in New Issue
Block a user