-
Notifications
You must be signed in to change notification settings - Fork 77
Expand file tree
/
Copy pathpayment-cycle.cy.js
More file actions
217 lines (167 loc) · 7.48 KB
/
Copy pathpayment-cycle.cy.js
File metadata and controls
217 lines (167 loc) · 7.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
import { TIMEOUTS } from '../support/constants';
describe('Payment cycle workflows', () => {
const getDateOffset = (days) => {
const date = new Date();
date.setDate(date.getDate() + days);
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
return `${day}-${month}-${year}`;
};
const cycleData = () => {
const uniquePart = `${Date.now().toString().slice(-5)}${Math.random().toString(36).slice(2, 4).toUpperCase()}`;
return {
// Code field max length is not as restrictive as program codes; use a short prefix
// to keep within any backend limits while still being unique per test run.
code: `PC${uniquePart}`,
startDate: getDateOffset(0),
endDate: getDateOffset(30),
status: 'PENDING',
};
};
// Payment cycles have no delete action in the UI, so created test records
// accumulate in the database. Use unique codes per run to avoid conflicts.
before(() => {
// Ensure a task group exists for PaymentCycleService tasks so ACTIVE
// cycle creation tasks are auto-assigned (ACCEPTED) and can be approved.
cy.login();
cy.ensurePaymentCycleTaskGroup();
cy.logout();
});
beforeEach(() => {
cy.login();
});
it('validates required fields before allowing payment cycle creation', () => {
cy.openCreatePaymentCycle();
cy.assertSaveDisabled();
});
it('creates a PENDING payment cycle successfully', () => {
const cycle = cycleData();
cy.createPaymentCycle(cycle);
cy.filterPaymentCycles({ code: cycle.code });
cy.assertPaymentCycleRowVisible({ code: cycle.code });
});
it('searches payment cycles by code', () => {
const targetCycle = cycleData();
const otherCycle = cycleData();
cy.createPaymentCycle(targetCycle);
cy.createPaymentCycle(otherCycle);
cy.filterPaymentCycles({ code: targetCycle.code });
cy.assertPaymentCycleRowVisible({ code: targetCycle.code });
cy.assertPaymentCycleRowNotVisible({ code: otherCycle.code });
cy.filterPaymentCycles({ code: otherCycle.code });
cy.assertPaymentCycleRowVisible({ code: otherCycle.code });
cy.assertPaymentCycleRowNotVisible({ code: targetCycle.code });
});
it('views payment cycle details from the list', () => {
const cycle = cycleData();
cy.createPaymentCycle(cycle);
cy.openPaymentCycleForViewFromList(cycle.code);
cy.assertPaymentCycleDetailFields(cycle);
});
it('creates an ACTIVE payment cycle via task approval and verifies all fields', () => {
const cycle = cycleData();
// ACTIVE cycles route through the maker-checker task workflow:
// 1. Save creates a task and shows a notification dialog.
// 2. Approve the task via All Tasks.
// 3. After approval the cycle is created as ACTIVE.
cy.createPaymentCycle({ ...cycle, status: 'ACTIVE' });
cy.approveLatestPaymentCycleTask();
cy.filterPaymentCycles({ code: cycle.code });
cy.assertPaymentCycleRowVisible({ code: cycle.code });
cy.openPaymentCycleForViewFromList(cycle.code);
cy.assertPaymentCycleDetailFields({ ...cycle, status: 'ACTIVE' });
});
it('creates a SUSPENDED payment cycle', () => {
const cycle = cycleData();
cy.createPaymentCycle({ ...cycle, status: 'SUSPENDED' });
cy.filterPaymentCycles({ code: cycle.code });
cy.assertPaymentCycleRowVisible({ code: cycle.code });
});
it('filters payment cycles by status', () => {
const pendingCycle = cycleData();
const suspendedCycle = cycleData();
cy.createPaymentCycle({ ...pendingCycle, status: 'PENDING' });
cy.createPaymentCycle({ ...suspendedCycle, status: 'SUSPENDED' });
// Verify the status filter EXCLUDES cycles with a different status.
cy.filterPaymentCycles({ status: 'PENDING' });
cy.assertPaymentCycleRowNotVisible({ code: suspendedCycle.code });
cy.filterPaymentCycles({ status: 'SUSPENDED' });
cy.assertPaymentCycleRowNotVisible({ code: pendingCycle.code });
});
it('shows read-only fields on the detail page', () => {
const cycle = cycleData();
cy.createPaymentCycle(cycle);
cy.openPaymentCycleForViewFromList(cycle.code);
cy.assertMuiInputDisabled('Code', cycle.code);
cy.assertMuiInputDisabled('Start Date', cycle.startDate);
cy.assertMuiInputDisabled('End Date');
cy.assertMuiSelectValue('Status', 'PENDING');
});
it('blocks save when the code is changed to a duplicate value', () => {
const existingCycle = cycleData();
const newCycle = cycleData();
cy.createPaymentCycle(existingCycle);
cy.openCreatePaymentCycle();
cy.fillPaymentCycleForm(newCycle);
cy.assertSaveEnabled();
cy.enterMuiInput('Code', existingCycle.code);
cy.assertSaveDisabled();
});
it('creates a payment cycle and immediately searches for it', () => {
const cycle = cycleData();
cy.createPaymentCycle(cycle);
cy.filterPaymentCycles({ code: cycle.code });
cy.assertPaymentCycleRowVisible({ code: cycle.code });
});
it('resets payment cycle filters and restores full list', () => {
cy.visit('/front/paymentCycles');
cy.contains(/\d+ Payment Cycle/, { timeout: TIMEOUTS.BACKEND_VALIDATION });
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
cy.enterMuiInput('Code', 'FAKE_CODE');
cy.resetPaymentCycleFilters();
cy.assertMuiInput('Code', '');
});
it('filters payment cycles by date range (Date From / Date To)', () => {
const cycle = cycleData();
cy.createPaymentCycle(cycle);
// A "Date From" filter in the far future must exclude this cycle (its
// startDate is today).
cy.filterPaymentCycles({ code: cycle.code, dateFrom: getDateOffset(365) });
cy.assertPaymentCycleRowNotVisible({ code: cycle.code });
// A "Date From" filter in the past includes it.
cy.filterPaymentCycles({ code: cycle.code, dateFrom: getDateOffset(-365) });
cy.assertPaymentCycleRowVisible({ code: cycle.code });
});
it('payroll form Payment Cycle picker excludes non-ACTIVE cycles', () => {
// The PaymentCyclePicker used on the payroll create form queries
// paymentCycle with `status: ACTIVE`, so a PENDING cycle must never
// appear in its autocomplete options.
const pendingCycle = cycleData();
cy.createPaymentCycle(pendingCycle);
cy.visit('/front/payrolls');
cy.contains('Payrolls Found', { timeout: TIMEOUTS.BACKEND_VALIDATION });
cy.createClick();
cy.url({ timeout: TIMEOUTS.BACKEND_VALIDATION }).should('include', '/payrolls/payroll');
// The cycle picker fires a GraphQL query with `status: ACTIVE` on input
// change — alias it so we can await the response before asserting.
cy.aliasGraphqlQuery('paymentCycle(', 'cyclePickerFetch');
cy.contains('label', 'Payment Cycle', { timeout: TIMEOUTS.BACKEND_VALIDATION })
.siblings('.MuiInputBase-root')
.find('input')
.click({ force: true })
.clear({ force: true })
.type(pendingCycle.code, { force: true });
cy.wait('@cyclePickerFetch', { timeout: TIMEOUTS.BACKEND_VALIDATION });
// The PENDING cycle code must not appear in any autocomplete listbox option.
cy.get('body').then(($body) => {
const options = $body.find('[role="listbox"] li, [role="presentation"] li').toArray();
const pendingVisible = options.some((li) => li.innerText.includes(pendingCycle.code));
expect(
pendingVisible,
`PENDING cycle ${pendingCycle.code} must not appear in the ACTIVE-filtered picker`,
).to.equal(false);
});
});
});