@@ -7,16 +7,19 @@ import { SalaryRangeFormPage } from '../../page-objects/salary-range-form.page';
77/**
88 * Salary Range CRUD Tests
99 *
10- * Tests for salary range management operations:
11- * - List salary ranges
12- * - Create salary range (HRAdmin)
13- * - Edit salary range
14- * - Delete salary range
10+ * Angular source facts:
11+ * - Create/Edit buttons: *appHasRole="['HRAdmin', 'Manager']" — both roles can create/edit
12+ * - Delete button: *appHasRole="['HRAdmin']" — only HRAdmin can delete
13+ * - Form fields: name (required), minSalary (required, min(0)), maxSalary (required, min(0))
14+ * - Delete uses ConfirmDialogComponent (Material dialog) — NOT window.confirm()
15+ * - Delete API endpoint: /SalaryRanges/{id} — use case-insensitive URL match
16+ * - Salary values displayed as currency: $50,000 (not raw number)
17+ * - No route guard on /salary-ranges list (both Manager and Employee can view)
18+ * - Route guard (hrAdminGuard or managerGuard) on create/edit — need to confirm
1519 */
1620
1721test . describe ( 'Salary Range CRUD' , ( ) => {
1822 test . beforeEach ( async ( { page } ) => {
19- // Login as HRAdmin (likely required for salary ranges)
2023 await loginAsRole ( page , 'hradmin' ) ;
2124 const list = new SalaryRangeListPage ( page ) ;
2225 await list . goto ( ) ;
@@ -40,12 +43,16 @@ test.describe('Salary Range CRUD', () => {
4043 await list . clickCreate ( ) ;
4144
4245 const salaryData = createSalaryRangeData ( {
46+ name : `TestRange_${ Date . now ( ) } ` ,
4347 minSalary : 50000 ,
4448 maxSalary : 80000 ,
45- currency : 'USD' ,
4649 } ) ;
4750
48- await form . fillForm ( { minSalary : salaryData . minSalary , maxSalary : salaryData . maxSalary } ) ;
51+ await form . fillForm ( {
52+ name : salaryData . name ,
53+ minSalary : salaryData . minSalary ,
54+ maxSalary : salaryData . maxSalary ,
55+ } ) ;
4956 await form . submit ( ) ;
5057
5158 const result = await form . verifySubmissionSuccess ( ) ;
@@ -62,17 +69,16 @@ test.describe('Salary Range CRUD', () => {
6269 const firstRange = list . getRow ( 0 ) ;
6370
6471 if ( await firstRange . isVisible ( { timeout : 3000 } ) ) {
65- // Click edit button (or fall back to row click)
66- const editButton = firstRange . locator ( 'button, a' ) . filter ( { hasText : / e d i t | u p d a t e / i } ) . first ( ) ;
67- if ( await editButton . isVisible ( { timeout : 2000 } ) ) {
68- await editButton . click ( ) ;
69- await page . waitForTimeout ( 1000 ) ;
70- } else {
71- await firstRange . click ( ) ;
72- await page . waitForTimeout ( 1000 ) ;
72+ const editButton = firstRange . locator ( 'button' ) . filter ( { hasText : / e d i t / i } ) . first ( ) ;
73+ if ( ! ( await editButton . isVisible ( { timeout : 2000 } ) ) ) {
74+ test . skip ( ) ;
75+ return ;
7376 }
7477
75- if ( await form . maxSalaryInput . isVisible ( { timeout : 2000 } ) . catch ( ( ) => false ) ) {
78+ await editButton . click ( ) ;
79+ await page . waitForTimeout ( 1000 ) ;
80+
81+ if ( await form . maxSalaryInput . isVisible ( { timeout : 3000 } ) . catch ( ( ) => false ) ) {
7682 await form . fillMaxSalary ( 100000 ) ;
7783 }
7884
@@ -89,63 +95,70 @@ test.describe('Salary Range CRUD', () => {
8995 const list = new SalaryRangeListPage ( page ) ;
9096 const form = new SalaryRangeFormPage ( page ) ;
9197
92- // First create a test salary range to delete
93- if ( await list . hasCreatePermission ( ) ) {
94- await list . clickCreate ( ) ;
95-
96- const salaryData = createSalaryRangeData ( {
97- minSalary : 30000 ,
98- maxSalary : 45000 ,
99- } ) ;
100-
101- await form . fillForm ( { minSalary : salaryData . minSalary , maxSalary : salaryData . maxSalary } ) ;
102- await form . submit ( ) ;
103-
104- await page . waitForTimeout ( 2000 ) ;
98+ if ( ! ( await list . hasCreatePermission ( ) ) ) {
99+ test . skip ( ) ;
100+ return ;
101+ }
105102
106- // Navigate back to list
107- await list . goto ( ) ;
103+ // Create a salary range to delete
104+ const uniqueName = `ToDelete_${ Date . now ( ) } ` ;
105+ await list . clickCreate ( ) ;
106+ await form . fillForm ( { name : uniqueName , minSalary : 30000 , maxSalary : 45000 } ) ;
107+ await form . submit ( ) ;
108+ await page . waitForTimeout ( 2000 ) ;
108109
109- // Find the salary range row (look for the max value)
110- const rangeRow = list . getRowByText ( '45000' ) ;
110+ // Navigate back to list
111+ await list . goto ( ) ;
112+ await page . waitForLoadState ( 'networkidle' ) ;
111113
112- if ( await rangeRow . isVisible ( { timeout : 3000 } ) . catch ( ( ) => false ) ) {
113- const deleteButton = rangeRow . locator ( 'button' ) . filter ( { hasText : / d e l e t e | r e m o v e / i } ) . first ( ) ;
114+ // Find the row by unique name
115+ const rangeRow = list . getRowByText ( 'ToDelete' ) ;
116+ if ( ! ( await rangeRow . isVisible ( { timeout : 3000 } ) . catch ( ( ) => false ) ) ) {
117+ test . skip ( ) ;
118+ return ;
119+ }
114120
115- if ( await deleteButton . isVisible ( { timeout : 2000 } ) ) {
116- await deleteButton . click ( ) ;
117- await page . waitForTimeout ( 1000 ) ;
121+ const deleteButton = rangeRow . locator ( 'button' ) . filter ( { hasText : / d e l e t e / i } ) . first ( ) ;
122+ if ( ! ( await deleteButton . isVisible ( { timeout : 2000 } ) ) ) {
123+ test . skip ( ) ;
124+ return ;
125+ }
118126
119- // Confirm deletion
120- const confirmButton = page . locator ( 'button' ) . filter ( { hasText : / y e s | c o n f i r m | d e l e t e / i } ) ;
121- await confirmButton . last ( ) . click ( ) ;
127+ await deleteButton . click ( ) ;
122128
123- await page . waitForTimeout ( 2000 ) ;
129+ // Salary range delete uses ConfirmDialogComponent (Angular Material dialog)
130+ const dialogConfirm = page . locator ( 'mat-dialog-actions button' ) . filter ( { hasText : / D e l e t e / i } ) ;
131+ await dialogConfirm . waitFor ( { state : 'visible' , timeout : 5000 } ) ;
124132
125- const successIndicator = page . locator ( 'mat-snack-bar, .toast, .notification' ) . filter ( { hasText : / s u c c e s s | d e l e t e d | r e m o v e d / i } ) ;
126- const hasSuccess = await successIndicator . isVisible ( { timeout : 3000 } ) . catch ( ( ) => false ) ;
133+ const [ deleteResponse ] = await Promise . all ( [
134+ page . waitForResponse (
135+ resp => resp . url ( ) . toLowerCase ( ) . includes ( '/salaryranges/' ) && resp . request ( ) . method ( ) === 'DELETE' ,
136+ { timeout : 10000 }
137+ ) ,
138+ dialogConfirm . click ( ) ,
139+ ] ) ;
127140
128- expect ( hasSuccess ) . toBe ( true ) ;
129- } else {
130- test . skip ( ) ;
131- }
132- } else {
133- test . skip ( ) ;
134- }
135- } else {
136- test . skip ( ) ;
137- }
141+ expect ( deleteResponse . status ( ) ) . toBe ( 200 ) ;
138142 } ) ;
139143
140144 test ( 'should search salary ranges' , async ( { page } ) => {
141145 const list = new SalaryRangeListPage ( page ) ;
142146
143147 if ( await list . searchInput . isVisible ( { timeout : 2000 } ) ) {
144- await list . search ( '50000' ) ;
148+ // Get the name from the first row (1st column is Range Name)
149+ const firstRow = list . getRow ( 0 ) ;
150+ const firstCellText = ( await firstRow . locator ( 'td, mat-cell' ) . first ( ) . textContent ( ) || '' ) . trim ( ) ;
151+
152+ if ( ! firstCellText ) {
153+ test . skip ( ) ;
154+ return ;
155+ }
156+
157+ await list . search ( firstCellText ) ;
158+ await page . waitForTimeout ( 1000 ) ;
145159
146160 const rowCount = await list . getRowCount ( ) ;
147- // Either has results or shows empty state
148- expect ( rowCount ) . toBeGreaterThanOrEqual ( 0 ) ;
161+ expect ( rowCount ) . toBeGreaterThanOrEqual ( 1 ) ;
149162 } else {
150163 test . skip ( ) ;
151164 }
@@ -159,10 +172,7 @@ test.describe('Salary Range CRUD', () => {
159172 if ( await firstRow . isVisible ( { timeout : 3000 } ) ) {
160173 const rowText = await firstRow . textContent ( ) ;
161174
162- // Should contain salary values (with or without currency symbol)
163- expect ( rowText ) . toMatch ( / \d { 2 , } / ) ; // At least 2 digits for salary
164-
165- // May contain currency symbols or separators
175+ // Salary values are displayed as $50,000 currency format
166176 const hasCurrencyFormat = / [ $ € £ ¥ ] | \d { 1 , 3 } ( , \d { 3 } ) * / . test ( rowText || '' ) ;
167177 expect ( hasCurrencyFormat ) . toBe ( true ) ;
168178 } else {
@@ -177,36 +187,33 @@ test.describe('Salary Range CRUD', () => {
177187 const columnHeaders = page . locator ( 'th, mat-header-cell' ) . filter ( { hasText : / m i n | m a x | s a l a r y / i } ) ;
178188
179189 if ( await columnHeaders . first ( ) . isVisible ( { timeout : 2000 } ) ) {
180- // Get initial order
181190 const firstRow = list . getRow ( 0 ) ;
182191 const initialFirstValue = await firstRow . textContent ( ) ;
183192
184- // Click to sort
185193 await columnHeaders . first ( ) . click ( ) ;
186194 await page . waitForTimeout ( 1000 ) ;
187195
188- // Get new order
189196 const newFirstValue = await list . getRow ( 0 ) . textContent ( ) ;
190197
191- // Values might have changed (sorted) — basic check that values exist
192198 expect ( initialFirstValue ) . toBeTruthy ( ) ;
193199 expect ( newFirstValue ) . toBeTruthy ( ) ;
194200 } else {
195201 test . skip ( ) ;
196202 }
197203 } ) ;
198204
199- test ( 'should not allow non-HRAdmin to create salary range' , async ( { page } ) => {
205+ test ( 'should show Create and Edit buttons for Manager (not Delete)' , async ( { page } ) => {
206+ // Manager can create and edit salary ranges but NOT delete
200207 await logout ( page ) ;
201208 await loginAsRole ( page , 'manager' ) ;
202209
203210 const list = new SalaryRangeListPage ( page ) ;
204211 await list . goto ( ) ;
205212
206213 const canCreate = await list . hasCreatePermission ( ) ;
207- const accessDenied = await page . locator ( 'text=/access.*denied|forbidden|unauthorized/i' ) . isVisible ( { timeout : 2000 } ) . catch ( ( ) => false ) ;
208- const noTable = ! ( await list . table . isVisible ( { timeout : 2000 } ) . catch ( ( ) => true ) ) ;
214+ const canDelete = await list . hasDeletePermission ( ) ;
209215
210- expect ( ! canCreate || accessDenied || noTable ) . toBe ( true ) ;
216+ expect ( canCreate ) . toBe ( true ) ; // Manager CAN create (appHasRole=['HRAdmin','Manager'])
217+ expect ( canDelete ) . toBe ( false ) ; // Manager CANNOT delete (appHasRole=['HRAdmin'])
211218 } ) ;
212219} ) ;
0 commit comments