33// TODO: Remove Invitation
44import { expect , test } from '../playwright/fixtures' ;
55import { PLAYWRIGHT_BASE_URL } from '../playwright/config' ;
6+ import type { Page } from '@playwright/test' ;
7+ import path from 'path' ;
8+ import fs from 'fs' ;
9+ import os from 'os' ;
610
7- async function goToMembersPage ( page ) {
11+ async function goToMembersPage ( page : Page ) {
812 await page . goto ( PLAYWRIGHT_BASE_URL + '/members' ) ;
913}
1014
11- async function openInviteMemberModal ( page ) {
15+ async function openInviteMemberModal ( page : Page ) {
1216 await Promise . all ( [
1317 page . getByRole ( 'button' , { name : 'Invite Member' } ) . click ( ) ,
1418 expect ( page . getByPlaceholder ( 'Member Email' ) ) . toBeVisible ( ) ,
@@ -35,7 +39,7 @@ test('test that new employee can be invited', async ({ page }) => {
3539 await page . getByRole ( 'button' , { name : 'Employee' } ) . click ( ) ;
3640 await Promise . all ( [
3741 page . getByRole ( 'button' , { name : 'Invite Member' , exact : true } ) . click ( ) ,
38- await expect ( page . getByRole ( 'main' ) ) . toContainText ( `new+${ editorId } @editor.test` ) ,
42+ expect ( page . getByRole ( 'main' ) ) . toContainText ( `new+${ editorId } @editor.test` ) ,
3943 ] ) ;
4044} ) ;
4145
@@ -91,3 +95,123 @@ test('test that organization billable rate can be updated with all existing time
9195 ) ,
9296 ] ) ;
9397} ) ;
98+
99+ async function createPlaceholderMemberViaImport ( page : Page , placeholderName : string ) {
100+ const placeholderEmail = `placeholder+${ Math . floor ( Math . random ( ) * 100000 ) } @solidtime-import.test` ;
101+ const csvContent = [
102+ 'User,Email,Client,Project,Task,Description,Billable,Start date,Start time,End date,End time,Tags' ,
103+ `${ placeholderName } ,${ placeholderEmail } ,,,,Imported entry,No,2024-01-01,09:00:00,2024-01-01,10:00:00,` ,
104+ ] . join ( '\n' ) ;
105+
106+ // Write CSV to a temp file for upload
107+ const tmpDir = os . tmpdir ( ) ;
108+ const tmpFile = path . join ( tmpDir , `import-${ Date . now ( ) } .csv` ) ;
109+ fs . writeFileSync ( tmpFile , csvContent ) ;
110+
111+ await page . goto ( PLAYWRIGHT_BASE_URL + '/import' ) ;
112+
113+ // Select "Toggl Time Entries" import type
114+ await page . locator ( 'select#importType' ) . selectOption ( { label : 'Toggl Time Entries' } ) ;
115+
116+ // Upload the CSV file
117+ await page . locator ( 'input[type="file"]' ) . setInputFiles ( tmpFile ) ;
118+
119+ // Click Import and wait for success
120+ await Promise . all ( [
121+ page . getByRole ( 'button' , { name : 'Import Data' } ) . click ( ) ,
122+ page . waitForResponse (
123+ ( response ) => response . url ( ) . includes ( '/import' ) && response . status ( ) === 200
124+ ) ,
125+ ] ) ;
126+
127+ // Close the result modal
128+ await page . getByRole ( 'button' , { name : 'Close' } ) . click ( ) ;
129+
130+ // Clean up temp file
131+ fs . unlinkSync ( tmpFile ) ;
132+ }
133+
134+ test ( 'test that changing member role updates the role in the member table' , async ( { page } ) => {
135+ const placeholderName = 'RoleChange ' + Math . floor ( Math . random ( ) * 10000 ) ;
136+
137+ // Create a placeholder member via import
138+ await createPlaceholderMemberViaImport ( page , placeholderName ) ;
139+
140+ // Go to members page and verify placeholder exists with role "Placeholder"
141+ await goToMembersPage ( page ) ;
142+ const memberRow = page . getByRole ( 'row' ) . filter ( { hasText : placeholderName } ) ;
143+ await expect ( memberRow ) . toBeVisible ( ) ;
144+ await expect ( memberRow . getByText ( 'Placeholder' ) ) . toBeVisible ( ) ;
145+
146+ // Open the edit modal for the placeholder member
147+ await memberRow . getByRole ( 'button' ) . click ( ) ;
148+ await page . getByRole ( 'menuitem' ) . getByText ( 'Edit' ) . click ( ) ;
149+ await expect ( page . getByRole ( 'dialog' ) ) . toBeVisible ( ) ;
150+ await expect ( page . getByRole ( 'heading' , { name : 'Update Member' } ) ) . toBeVisible ( ) ;
151+
152+ // Change role to Employee
153+ const roleSelect = page . getByRole ( 'dialog' ) . getByRole ( 'combobox' ) . first ( ) ;
154+ await roleSelect . click ( ) ;
155+ await page . getByRole ( 'option' , { name : 'Employee' } ) . click ( ) ;
156+
157+ // Submit the change and verify the API call succeeds
158+ await Promise . all ( [
159+ page . getByRole ( 'button' , { name : 'Update Member' } ) . click ( ) ,
160+ page . waitForResponse (
161+ ( response ) =>
162+ response . url ( ) . includes ( '/members/' ) &&
163+ response . request ( ) . method ( ) === 'PUT' &&
164+ response . status ( ) === 200
165+ ) ,
166+ ] ) ;
167+
168+ // Verify dialog closed
169+ await expect ( page . getByRole ( 'dialog' ) ) . not . toBeVisible ( ) ;
170+
171+ // Verify the role updated in the table
172+ await expect ( memberRow . getByText ( 'Employee' ) ) . toBeVisible ( ) ;
173+ } ) ;
174+
175+ test ( 'test that merging a placeholder member works' , async ( { page } ) => {
176+ const placeholderName = 'Merge Target ' + Math . floor ( Math . random ( ) * 10000 ) ;
177+
178+ // Create a placeholder member via import
179+ await createPlaceholderMemberViaImport ( page , placeholderName ) ;
180+
181+ // Go to members page
182+ await goToMembersPage ( page ) ;
183+ await expect ( page . getByText ( placeholderName ) ) . toBeVisible ( ) ;
184+
185+ // Find the placeholder member row and open actions menu
186+ const placeholderRow = page . getByRole ( 'row' ) . filter ( { hasText : placeholderName } ) ;
187+ await placeholderRow . getByRole ( 'button' ) . click ( ) ;
188+
189+ // Click Merge
190+ await page . getByTestId ( 'member_merge' ) . click ( ) ;
191+ await expect ( page . getByRole ( 'dialog' ) ) . toBeVisible ( ) ;
192+ await expect ( page . getByRole ( 'heading' , { name : 'Merge Member' } ) ) . toBeVisible ( ) ;
193+
194+ // Select the current user (the owner) as merge target via MemberCombobox
195+ const combobox = page . getByRole ( 'dialog' ) . getByRole ( 'combobox' ) ;
196+ await combobox . click ( ) ;
197+
198+ // Wait for dropdown options to load
199+ const firstOption = page . getByRole ( 'option' ) . first ( ) ;
200+ await expect ( firstOption ) . toBeVisible ( { timeout : 10000 } ) ;
201+ await firstOption . click ( ) ;
202+
203+ // Submit merge
204+ await Promise . all ( [
205+ page . getByRole ( 'button' , { name : 'Merge Member' } ) . click ( ) ,
206+ page . waitForResponse (
207+ ( response ) =>
208+ response . url ( ) . includes ( '/member/' ) && response . url ( ) . includes ( '/merge-into' ) && response . ok ( )
209+ ) ,
210+ ] ) ;
211+
212+ // Wait for dialog to close after successful merge
213+ await expect ( page . getByRole ( 'dialog' ) ) . not . toBeVisible ( ) ;
214+
215+ // Verify placeholder member is no longer in the members table
216+ await expect ( page . getByRole ( 'main' ) . getByText ( placeholderName ) ) . not . toBeVisible ( ) ;
217+ } ) ;
0 commit comments