mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 08:12:17 +01:00
253 lines
9.4 KiB
TypeScript
253 lines
9.4 KiB
TypeScript
import { expect } from '@playwright/test';
|
|
import type { Page } from '@playwright/test';
|
|
import { PLAYWRIGHT_BASE_URL } from '../playwright/config';
|
|
import { test } from '../playwright/fixtures';
|
|
import { formatCentsWithOrganizationDefaults } from './utils/money';
|
|
import { createProjectViaApi, createProjectMemberViaApi, type TestContext } from './utils/api';
|
|
|
|
async function createProjectWithMemberViaApi(ctx: TestContext, page: Page, projectName: string) {
|
|
const project = await createProjectViaApi(ctx, { name: projectName });
|
|
await createProjectMemberViaApi(ctx, project.id, { member_id: ctx.memberId });
|
|
|
|
// Navigate to the project detail page
|
|
await page.goto(PLAYWRIGHT_BASE_URL + '/projects/' + project.id);
|
|
await expect(page.getByTestId('project_member_table').getByRole('row').first()).toBeVisible();
|
|
return project;
|
|
}
|
|
|
|
test('test that updating project member billable rate works for existing time entries', async ({
|
|
page,
|
|
ctx,
|
|
}) => {
|
|
const newProjectName = 'New Project ' + Math.floor(1 + Math.random() * 10000);
|
|
const newBillableRate = Math.round(Math.random() * 10000);
|
|
await createProjectWithMemberViaApi(ctx, page, newProjectName);
|
|
|
|
await page
|
|
.getByTestId('project_member_table')
|
|
.getByRole('row')
|
|
.first()
|
|
.getByRole('button')
|
|
.click();
|
|
await page.getByRole('menuitem', { name: 'Edit Project Member' }).click();
|
|
await page.getByLabel('Billable Rate').fill(newBillableRate.toString());
|
|
await page.getByRole('button', { name: 'Update Project Member' }).click();
|
|
|
|
await Promise.all([
|
|
page.getByRole('button', { name: 'Yes, update existing time' }).click(),
|
|
page.waitForRequest(
|
|
async (request) =>
|
|
request.url().includes('/project-members/') &&
|
|
request.method() === 'PUT' &&
|
|
request.postDataJSON().billable_rate === newBillableRate * 100
|
|
),
|
|
page.waitForResponse(
|
|
async (response) =>
|
|
response.url().includes('/project-members/') &&
|
|
response.request().method() === 'PUT' &&
|
|
response.status() === 200 &&
|
|
(await response.json()).data.billable_rate === newBillableRate * 100
|
|
),
|
|
]);
|
|
await expect(
|
|
page
|
|
.getByRole('row')
|
|
.first()
|
|
.getByText(formatCentsWithOrganizationDefaults(newBillableRate * 100))
|
|
).toBeVisible();
|
|
});
|
|
|
|
test('test that project member edit modal can be cancelled without sending API request', async ({
|
|
page,
|
|
ctx,
|
|
}) => {
|
|
const projectName = 'Cancel Test ' + Math.floor(1 + Math.random() * 10000);
|
|
|
|
await createProjectWithMemberViaApi(ctx, page, projectName);
|
|
|
|
// Open the edit modal
|
|
await page
|
|
.getByTestId('project_member_table')
|
|
.getByRole('row')
|
|
.first()
|
|
.getByRole('button')
|
|
.click();
|
|
await page.getByRole('menuitem', { name: 'Edit Project Member' }).click();
|
|
|
|
// Verify the modal is open and shows the member name
|
|
await expect(page.getByRole('heading', { name: 'Edit Project Member' })).toBeVisible();
|
|
await expect(page.getByRole('dialog').getByText('John Doe')).toBeVisible();
|
|
|
|
// Enter a new billable rate
|
|
await page.getByLabel('Billable Rate').fill('999');
|
|
|
|
// Set up listener to verify no PUT request is sent
|
|
let putRequestSent = false;
|
|
page.on('request', (request) => {
|
|
if (request.url().includes('/project-members/') && request.method() === 'PUT') {
|
|
putRequestSent = true;
|
|
}
|
|
});
|
|
|
|
// Click Cancel
|
|
await page.getByRole('button', { name: 'Cancel' }).click();
|
|
|
|
// Verify the modal is closed
|
|
await expect(page.getByRole('heading', { name: 'Edit Project Member' })).not.toBeVisible();
|
|
|
|
// Verify no PUT request was sent
|
|
expect(putRequestSent).toBe(false);
|
|
});
|
|
|
|
test('test that project member update without billable rate change skips confirmation and completes', async ({
|
|
page,
|
|
ctx,
|
|
}) => {
|
|
const projectName = 'No Change ' + Math.floor(1 + Math.random() * 10000);
|
|
|
|
await createProjectWithMemberViaApi(ctx, page, projectName);
|
|
|
|
// Open the edit modal
|
|
await page
|
|
.getByTestId('project_member_table')
|
|
.getByRole('row')
|
|
.first()
|
|
.getByRole('button')
|
|
.click();
|
|
await page.getByRole('menuitem', { name: 'Edit Project Member' }).click();
|
|
|
|
// Click Update without changing anything - no confirmation modal since rate didn't change
|
|
await Promise.all([
|
|
page.waitForResponse(
|
|
(response) =>
|
|
response.url().includes('/project-members/') &&
|
|
response.request().method() === 'PUT' &&
|
|
response.status() === 200
|
|
),
|
|
page.getByRole('button', { name: 'Update Project Member' }).click(),
|
|
]);
|
|
|
|
// Verify the edit modal is closed (confirmation modal was skipped)
|
|
await expect(page.getByRole('heading', { name: 'Edit Project Member' })).not.toBeVisible();
|
|
});
|
|
|
|
test('test that billable rate confirmation modal can be cancelled without sending API request', async ({
|
|
page,
|
|
ctx,
|
|
}) => {
|
|
const projectName = 'Rate Cancel ' + Math.floor(1 + Math.random() * 10000);
|
|
const newBillableRate = Math.round(Math.random() * 10000);
|
|
|
|
await createProjectWithMemberViaApi(ctx, page, projectName);
|
|
|
|
// Open the edit modal
|
|
await page
|
|
.getByTestId('project_member_table')
|
|
.getByRole('row')
|
|
.first()
|
|
.getByRole('button')
|
|
.click();
|
|
await page.getByRole('menuitem', { name: 'Edit Project Member' }).click();
|
|
|
|
// Change the billable rate
|
|
await page.getByLabel('Billable Rate').fill(newBillableRate.toString());
|
|
|
|
// Set up listener to verify no PUT request is sent
|
|
let putRequestSent = false;
|
|
page.on('request', (request) => {
|
|
if (request.url().includes('/project-members/') && request.method() === 'PUT') {
|
|
putRequestSent = true;
|
|
}
|
|
});
|
|
|
|
// Click Update - this should show the confirmation modal
|
|
await page.getByRole('button', { name: 'Update Project Member' }).click();
|
|
|
|
// Verify the confirmation modal is shown
|
|
await expect(page.getByText('update all existing time entries')).toBeVisible();
|
|
|
|
// Click Cancel to close the confirmation modal without updating
|
|
await page.getByRole('button', { name: 'Cancel' }).click();
|
|
|
|
// Verify the confirmation modal is closed but edit modal is still open
|
|
await expect(page.getByText('update all existing time entries')).not.toBeVisible();
|
|
await expect(page.getByRole('heading', { name: 'Edit Project Member' })).toBeVisible();
|
|
|
|
// Close the edit modal
|
|
await page.getByRole('dialog').getByRole('button', { name: 'Cancel' }).click();
|
|
|
|
// Verify the edit modal is closed
|
|
await expect(page.getByRole('heading', { name: 'Edit Project Member' })).not.toBeVisible();
|
|
|
|
// Verify no PUT request was sent
|
|
expect(putRequestSent).toBe(false);
|
|
});
|
|
|
|
test('test that clearing billable rate reverts to project default', async ({ page, ctx }) => {
|
|
const projectName = 'Revert Default ' + Math.floor(1 + Math.random() * 10000);
|
|
const customRate = Math.round(100 + Math.random() * 10000);
|
|
|
|
await createProjectWithMemberViaApi(ctx, page, projectName);
|
|
|
|
// Verify the billable rate shows "--" (project default) initially
|
|
await expect(
|
|
page.getByTestId('project_member_table').getByRole('row').first().getByText('--')
|
|
).toBeVisible();
|
|
|
|
// Set a custom billable rate
|
|
await page
|
|
.getByTestId('project_member_table')
|
|
.getByRole('row')
|
|
.first()
|
|
.getByRole('button')
|
|
.click();
|
|
await page.getByRole('menuitem', { name: 'Edit Project Member' }).click();
|
|
await page.getByLabel('Billable Rate').fill(customRate.toString());
|
|
await page.getByRole('button', { name: 'Update Project Member' }).click();
|
|
|
|
// Confirm the billable rate update
|
|
await Promise.all([
|
|
page.getByRole('button', { name: 'Yes, update existing time' }).click(),
|
|
page.waitForResponse(
|
|
(response) =>
|
|
response.url().includes('/project-members/') &&
|
|
response.request().method() === 'PUT' &&
|
|
response.status() === 200
|
|
),
|
|
]);
|
|
|
|
// Verify the custom rate is shown in the table (not "--")
|
|
await expect(
|
|
page.getByTestId('project_member_table').getByRole('row').first().getByText('--')
|
|
).not.toBeVisible();
|
|
|
|
// Now clear the billable rate to revert to project default
|
|
await page
|
|
.getByTestId('project_member_table')
|
|
.getByRole('row')
|
|
.first()
|
|
.getByRole('button')
|
|
.click();
|
|
await page.getByRole('menuitem', { name: 'Edit Project Member' }).click();
|
|
|
|
// Set billable rate to 0 to revert to project default
|
|
await page.getByLabel('Billable Rate').fill('0');
|
|
await page.getByRole('button', { name: 'Update Project Member' }).click();
|
|
|
|
// Confirm the billable rate update
|
|
await Promise.all([
|
|
page.getByRole('button', { name: 'Yes, update existing time' }).click(),
|
|
page.waitForResponse(
|
|
(response) =>
|
|
response.url().includes('/project-members/') &&
|
|
response.request().method() === 'PUT' &&
|
|
response.status() === 200
|
|
),
|
|
]);
|
|
|
|
// Verify the billable rate shows "--" again (project default)
|
|
await expect(
|
|
page.getByTestId('project_member_table').getByRole('row').first().getByText('--')
|
|
).toBeVisible();
|
|
});
|