Skip to content

Commit 5e1b72f

Browse files
committed
Add e2e test for out of office feature
1 parent ad2eed8 commit 5e1b72f

File tree

6 files changed

+238
-5
lines changed

6 files changed

+238
-5
lines changed

frontend/src/e2e-test/pages/citizen/citizen-messages.ts

+26
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
//
33
// SPDX-License-Identifier: LGPL-2.1-or-later
44

5+
import FiniteDateRange from 'lib-common/finite-date-range'
6+
57
import {
68
Element,
79
ElementCollection,
@@ -37,6 +39,7 @@ export default class CitizenMessagesPage {
3739
#threadUrgent: Element
3840
newMessageButton: Element
3941
fileUpload: Element
42+
#threadOutOfOfficeInfo: Element
4043
constructor(private readonly page: Page) {
4144
this.messageReplyContent = new TextInput(
4245
page.findByDataQa('message-reply-content')
@@ -60,6 +63,9 @@ export default class CitizenMessagesPage {
6063
.findByDataQa('urgent')
6164
this.newMessageButton = page.findAllByDataQa('new-message-btn').first()
6265
this.fileUpload = page.findByDataQa('upload-message-attachment')
66+
this.#threadOutOfOfficeInfo = page
67+
.findByDataQa('thread-reader')
68+
.findByDataQa('out-of-office-info')
6369
}
6470

6571
replyButtonTag = 'message-reply-editor-btn'
@@ -201,6 +207,15 @@ export default class CitizenMessagesPage {
201207

202208
await editor.sendMessage()
203209
}
210+
211+
async assertThreadOutOfOffice(ooo: {
212+
name: string
213+
period: FiniteDateRange
214+
}) {
215+
await this.#threadOutOfOfficeInfo.assertText(
216+
(t) => t.includes(ooo.name) && t.includes(ooo.period.format())
217+
)
218+
}
204219
}
205220

206221
export class CitizenMessageEditor extends Element {
@@ -210,6 +225,7 @@ export class CitizenMessageEditor extends Element {
210225
readonly title = new TextInput(this.findByDataQa('input-title'))
211226
readonly content = new TextInput(this.findByDataQa('input-content'))
212227
readonly #sendMessage = this.findByDataQa('send-message-btn')
228+
readonly #outOfOfficeInfo = this.findByDataQa('out-of-office-info')
213229

214230
secondaryRecipient(name: string) {
215231
return this.find(`[data-qa="secondary-recipient"]`, { hasText: name })
@@ -250,4 +266,14 @@ export class CitizenMessageEditor extends Element {
250266
await this.#sendMessage.click()
251267
await this.waitUntilHidden()
252268
}
269+
270+
async assertOutOfOffice(ooo: { name: string; period: FiniteDateRange }) {
271+
await this.#outOfOfficeInfo.assertText(
272+
(t) => t.includes(ooo.name) && t.includes(ooo.period.format())
273+
)
274+
}
275+
276+
async assertNoOutOfOffice() {
277+
await this.#outOfOfficeInfo.waitUntilHidden()
278+
}
253279
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// SPDX-FileCopyrightText: 2017-2025 City of Espoo
2+
//
3+
// SPDX-License-Identifier: LGPL-2.1-or-later
4+
5+
import FiniteDateRange from 'lib-common/finite-date-range'
6+
import LocalDate from 'lib-common/local-date'
7+
8+
import config from '../../../config'
9+
import { Page, Element, DatePicker } from '../../../utils/page'
10+
11+
export class OutOfOfficePage {
12+
#outOfOffice: Element
13+
#outOfOfficeEditor: Element
14+
#addButton: Element
15+
#saveButton: Element
16+
#editButton: Element
17+
#removeButton: Element
18+
19+
constructor(page: Page) {
20+
this.#outOfOffice = page.findByDataQa('out-of-office-page')
21+
this.#outOfOfficeEditor = page.findByDataQa('out-of-office-editor')
22+
this.#addButton = page.findByDataQa('add-out-of-office')
23+
this.#saveButton = page.findByDataQa('save-out-of-office')
24+
this.#editButton = page.findByDataQa('edit-out-of-office')
25+
this.#removeButton = page.findByDataQa('remove-out-of-office')
26+
}
27+
28+
static async open(page: Page) {
29+
await page.goto(config.employeeUrl + '/out-of-office')
30+
return new OutOfOfficePage(page)
31+
}
32+
33+
async addOutOfOfficePeriod(startDate: LocalDate, endDate: LocalDate) {
34+
await this.#addButton.click()
35+
36+
const startInput = new DatePicker(
37+
this.#outOfOfficeEditor.findAll('input').first()
38+
)
39+
const endInput = new DatePicker(
40+
this.#outOfOfficeEditor.findAll('input').last()
41+
)
42+
43+
await startInput.fill(startDate)
44+
await endInput.fill(endDate)
45+
await this.#saveButton.click()
46+
}
47+
48+
async editStartOfPeriod(newStartDate: LocalDate) {
49+
await this.#editButton.click()
50+
const startInput = new DatePicker(
51+
this.#outOfOfficeEditor.findAll('input').first()
52+
)
53+
await startInput.fill(newStartDate)
54+
await this.#saveButton.click()
55+
}
56+
57+
async removeOutOfOfficePeriod() {
58+
await this.#removeButton.click()
59+
}
60+
61+
async assertNoPeriods() {
62+
await this.#outOfOffice.assertText((t) =>
63+
t.includes('Ei tulevia poissaoloja')
64+
)
65+
}
66+
67+
async assertPeriodExists(startDate: LocalDate, endDate: LocalDate) {
68+
const periodText =
69+
FiniteDateRange.tryCreate(startDate, endDate)?.format() ?? 'error'
70+
await this.#outOfOffice.assertText(
71+
(t) => t.includes(periodText) && !t.includes('Ei tulevia poissaoloja')
72+
)
73+
}
74+
75+
async assertPeriodDoesNotExist(startDate: LocalDate, endDate: LocalDate) {
76+
const periodText =
77+
FiniteDateRange.tryCreate(startDate, endDate)?.format() ?? 'error'
78+
await this.#outOfOffice.assertText((t) => !t.includes(periodText))
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// SPDX-FileCopyrightText: 2017-2025 City of Espoo
2+
//
3+
// SPDX-License-Identifier: LGPL-2.1-or-later
4+
5+
import { DevEmployee } from 'e2e-test/generated/api-types'
6+
import FiniteDateRange from 'lib-common/finite-date-range'
7+
import LocalDate from 'lib-common/local-date'
8+
import LocalTime from 'lib-common/local-time'
9+
10+
import config from '../../config'
11+
import {
12+
Fixture,
13+
testAdult,
14+
testCareArea,
15+
testChild,
16+
testDaycare
17+
} from '../../dev-api/fixtures'
18+
import {
19+
createMessageAccounts,
20+
resetServiceState
21+
} from '../../generated/api-clients'
22+
import CitizenMessagesPage from '../../pages/citizen/citizen-messages'
23+
import { OutOfOfficePage } from '../../pages/employee/messages/out-of-office-page'
24+
import { Page } from '../../utils/page'
25+
import { employeeLogin, enduserLogin } from '../../utils/user'
26+
27+
let supervisor: DevEmployee
28+
29+
beforeEach(async () => {
30+
await resetServiceState()
31+
await Fixture.careArea(testCareArea).save()
32+
const unit = await Fixture.daycare(testDaycare).save()
33+
supervisor = await Fixture.employee().unitSupervisor(unit.id).save()
34+
35+
await Fixture.family({
36+
guardian: testAdult,
37+
children: [testChild]
38+
}).save()
39+
40+
await Fixture.placement({
41+
childId: testChild.id,
42+
unitId: testDaycare.id,
43+
startDate: LocalDate.of(2022, 1, 1),
44+
endDate: LocalDate.of(2022, 12, 31)
45+
}).save()
46+
47+
await createMessageAccounts()
48+
})
49+
50+
describe('Out of Office', () => {
51+
test('Out of Office flow', async () => {
52+
// Employee sets an out of office period and edits it
53+
const employeePage = await Page.open({
54+
mockedTime: LocalDate.of(2022, 12, 1).toHelsinkiDateTime(
55+
LocalTime.of(12, 0)
56+
)
57+
})
58+
await employeeLogin(employeePage, supervisor)
59+
const outOfOfficePage = await OutOfOfficePage.open(employeePage)
60+
await outOfOfficePage.assertNoPeriods()
61+
62+
const startDate = LocalDate.of(2022, 12, 1)
63+
const endDate = LocalDate.of(2022, 12, 7)
64+
await outOfOfficePage.addOutOfOfficePeriod(startDate, endDate)
65+
await outOfOfficePage.assertPeriodExists(startDate, endDate)
66+
67+
const newStartDate = LocalDate.of(2022, 12, 2)
68+
await outOfOfficePage.editStartOfPeriod(newStartDate)
69+
await outOfOfficePage.assertPeriodExists(newStartDate, endDate)
70+
await outOfOfficePage.assertPeriodDoesNotExist(startDate, endDate)
71+
72+
// Citizen doesn't see the out of office period because it starts the next day
73+
const citizenPage1 = await Page.open({
74+
mockedTime: LocalDate.of(2022, 12, 1).toHelsinkiDateTime(
75+
LocalTime.of(13, 0)
76+
)
77+
})
78+
const { editor: editor1 } = await getCitizenMessageEditor(citizenPage1)
79+
await editor1.assertNoOutOfOffice()
80+
81+
// Citizen sees the out of office period the next day
82+
const citizenPage2 = await Page.open({
83+
mockedTime: LocalDate.of(2022, 12, 2).toHelsinkiDateTime(
84+
LocalTime.of(13, 0)
85+
)
86+
})
87+
const { editor: editor2, messagesPage: messagesPage2 } =
88+
await getCitizenMessageEditor(citizenPage2)
89+
await editor2.assertOutOfOffice({
90+
name: getSupervisorName(),
91+
period: FiniteDateRange.tryCreate(newStartDate, endDate)!
92+
})
93+
94+
// Citizen sends the message (so that a message that can be replied to is available)
95+
await editor2.fillMessage('Test title', 'Test content')
96+
await editor2.sendMessage()
97+
98+
// Reply editor shows the out of office period
99+
await messagesPage2.openFirstThreadReplyEditor()
100+
await messagesPage2.assertThreadOutOfOffice({
101+
name: getSupervisorName(),
102+
period: FiniteDateRange.tryCreate(newStartDate, endDate)!
103+
})
104+
105+
// Employee removes the out of office period
106+
await outOfOfficePage.removeOutOfOfficePeriod()
107+
await outOfOfficePage.assertNoPeriods()
108+
})
109+
})
110+
111+
function getSupervisorName() {
112+
return `${supervisor.lastName} ${supervisor.firstName}`
113+
}
114+
115+
async function getCitizenMessageEditor(citizenPage: Page) {
116+
await enduserLogin(citizenPage, testAdult)
117+
await citizenPage.goto(config.enduserMessagesUrl)
118+
const messagesPage = new CitizenMessagesPage(citizenPage)
119+
const editor = await messagesPage.createNewMessage()
120+
const supervisorName = getSupervisorName()
121+
await editor.selectRecipients([supervisorName])
122+
return { editor, messagesPage }
123+
}

frontend/src/employee-frontend/components/out-of-office/OutOfOfficeEditor.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// SPDX-License-Identifier: LGPL-2.1-or-later
44

5-
import React, { Fragment } from 'react'
5+
import React from 'react'
66

77
import { useTranslation } from 'employee-frontend/state/i18n'
88
import { localDateRange } from 'lib-common/form/fields'
@@ -63,7 +63,7 @@ export default React.memo(function OutOfOfficeEditor({
6363
})
6464

6565
return (
66-
<Fragment>
66+
<div data-qa="out-of-office-editor">
6767
<DateRangePickerF
6868
bind={period}
6969
locale={lang}
@@ -78,6 +78,7 @@ export default React.memo(function OutOfOfficeEditor({
7878
disabled={!form.isValid()}
7979
onClick={onSubmit}
8080
onSuccess={onClose}
81+
data-qa="save-out-of-office"
8182
/>
8283
<Gap size="xs" horizontal />
8384
<Button
@@ -86,6 +87,6 @@ export default React.memo(function OutOfOfficeEditor({
8687
onClick={onClose}
8788
/>
8889
</ButtonContainer>
89-
</Fragment>
90+
</div>
9091
)
9192
})

frontend/src/employee-frontend/components/out-of-office/OutOfOfficePage.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export default React.memo(function OutOfOfficePage() {
4646

4747
return (
4848
<Container>
49-
<ContentArea opaque>
49+
<ContentArea opaque data-qa="out-of-office-page">
5050
<H1>{i18n.outOfOffice.title}</H1>
5151
<P>{i18n.outOfOffice.description}</P>
5252
<Gap size="m" />
@@ -63,13 +63,15 @@ export default React.memo(function OutOfOfficePage() {
6363
appearance="inline"
6464
icon={faPen}
6565
onClick={() => startEdit(period)}
66+
data-qa="edit-out-of-office"
6667
/>
6768
<AsyncButton
6869
text={i18n.common.remove}
6970
onClick={() => deletePeriod({ id: period.id })}
7071
onSuccess={() => void {}}
7172
appearance="inline"
7273
icon={faTrash}
74+
data-qa="remove-out-of-office"
7375
/>
7476
</PeriodItemContainer>
7577
</li>
@@ -90,6 +92,7 @@ export default React.memo(function OutOfOfficePage() {
9092
text={i18n.outOfOffice.addOutOfOffice}
9193
primary
9294
onClick={() => setIsEditing(true)}
95+
data-qa="add-out-of-office"
9396
/>
9497
</Fragment>
9598
) : null}

frontend/src/lib-components/messages/OutOfOfficeInfo.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default React.memo(function OutOfOfficeInfo({
4040
)
4141

4242
return outOfOfficeAccounts.length > 0 ? (
43-
<OutOfOfficeInfoArea>
43+
<OutOfOfficeInfoArea data-qa="out-of-office-info">
4444
<RoundIcon content={faExclamation} color={colors.main.m2} size="s" />
4545
<div>
4646
{outOfOfficeAccounts.length === 1 ? (

0 commit comments

Comments
 (0)