mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-15 03:32:15 +01:00
Make sure that time entry billable status updates when project changes,
fixes #981
This commit is contained in:
188
e2e/calendar.spec.ts
Normal file
188
e2e/calendar.spec.ts
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import { PLAYWRIGHT_BASE_URL } from '../playwright/config';
|
||||||
|
import { test } from '../playwright/fixtures';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import type { Page } from '@playwright/test';
|
||||||
|
import { createProject, createBillableProject, createBareTimeEntry } from './utils/reporting';
|
||||||
|
|
||||||
|
async function goToCalendar(page: Page) {
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/calendar');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These tests verify that changing the project on a time entry via the calendar
|
||||||
|
* updates the billable status to match the new project's is_billable setting.
|
||||||
|
*
|
||||||
|
* Issue: https://github.com/solidtime-io/solidtime/issues/981
|
||||||
|
*/
|
||||||
|
|
||||||
|
test('test that changing project in calendar edit modal from non-billable to billable updates billable status', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const billableProjectName = 'Billable Cal Project ' + Math.floor(1 + Math.random() * 10000);
|
||||||
|
|
||||||
|
await createBillableProject(page, billableProjectName);
|
||||||
|
await createBareTimeEntry(page, 'Test billable calendar', '1h');
|
||||||
|
|
||||||
|
await goToCalendar(page);
|
||||||
|
|
||||||
|
// Click on the time entry event in the calendar
|
||||||
|
await page.locator('.fc-event').filter({ hasText: 'Test billable calendar' }).first().click();
|
||||||
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
|
||||||
|
// Verify initially non-billable
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Select the billable project
|
||||||
|
await page.getByRole('dialog').getByRole('button', { name: 'No Project' }).click();
|
||||||
|
await page.getByRole('option', { name: billableProjectName }).click();
|
||||||
|
|
||||||
|
// Verify the billable dropdown updated to Billable
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Save and verify
|
||||||
|
const [updateResponse] = await Promise.all([
|
||||||
|
page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/time-entries/') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200
|
||||||
|
),
|
||||||
|
page.getByRole('button', { name: 'Update Time Entry' }).click(),
|
||||||
|
]);
|
||||||
|
const responseBody = await updateResponse.json();
|
||||||
|
expect(responseBody.data.billable).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test that changing project in calendar edit modal from billable to non-billable updates billable status', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const billableProjectName = 'Billable Cal Rev Project ' + Math.floor(1 + Math.random() * 10000);
|
||||||
|
const nonBillableProjectName =
|
||||||
|
'NonBillable Cal Rev Project ' + Math.floor(1 + Math.random() * 10000);
|
||||||
|
|
||||||
|
await createBillableProject(page, billableProjectName);
|
||||||
|
await createProject(page, nonBillableProjectName);
|
||||||
|
await createBareTimeEntry(page, 'Test billable cal reverse', '1h');
|
||||||
|
|
||||||
|
await goToCalendar(page);
|
||||||
|
|
||||||
|
// Click on the time entry event in the calendar
|
||||||
|
await page
|
||||||
|
.locator('.fc-event')
|
||||||
|
.filter({ hasText: 'Test billable cal reverse' })
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
|
||||||
|
// First assign the billable project
|
||||||
|
await page.getByRole('dialog').getByRole('button', { name: 'No Project' }).click();
|
||||||
|
await page.getByRole('option', { name: billableProjectName }).click();
|
||||||
|
|
||||||
|
// Verify billable status flipped to Billable
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Now switch to the non-billable project
|
||||||
|
await page.getByRole('dialog').getByRole('button', { name: billableProjectName }).click();
|
||||||
|
await page.getByRole('option', { name: nonBillableProjectName }).click();
|
||||||
|
|
||||||
|
// Verify billable status reverted to Non-Billable
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Save and verify
|
||||||
|
const [updateResponse] = await Promise.all([
|
||||||
|
page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/time-entries/') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200
|
||||||
|
),
|
||||||
|
page.getByRole('button', { name: 'Update Time Entry' }).click(),
|
||||||
|
]);
|
||||||
|
const responseBody = await updateResponse.json();
|
||||||
|
expect(responseBody.data.billable).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test that opening calendar edit modal for a time entry with manually overridden billable status preserves that status', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const billableProjectName =
|
||||||
|
'Billable Cal Persist Project ' + Math.floor(1 + Math.random() * 10000);
|
||||||
|
|
||||||
|
await createBillableProject(page, billableProjectName);
|
||||||
|
await createBareTimeEntry(page, 'Test cal persist override', '1h');
|
||||||
|
|
||||||
|
await goToCalendar(page);
|
||||||
|
|
||||||
|
// Click on the time entry event in the calendar
|
||||||
|
await page
|
||||||
|
.locator('.fc-event')
|
||||||
|
.filter({ hasText: 'Test cal persist override' })
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
|
||||||
|
// Assign the billable project
|
||||||
|
await page.getByRole('dialog').getByRole('button', { name: 'No Project' }).click();
|
||||||
|
await page.getByRole('option', { name: billableProjectName }).click();
|
||||||
|
|
||||||
|
// Verify it auto-set to Billable
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Now manually override billable to Non-Billable via the dropdown
|
||||||
|
await page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' }).click();
|
||||||
|
await page.getByRole('option', { name: 'Non Billable' }).click();
|
||||||
|
|
||||||
|
// Verify it shows Non-Billable now
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Save
|
||||||
|
const [firstSaveResponse] = await Promise.all([
|
||||||
|
page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/time-entries/') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200
|
||||||
|
),
|
||||||
|
page.getByRole('button', { name: 'Update Time Entry' }).click(),
|
||||||
|
]);
|
||||||
|
const firstBody = await firstSaveResponse.json();
|
||||||
|
expect(firstBody.data.billable).toBe(false);
|
||||||
|
|
||||||
|
// Re-open the edit modal from the calendar — the project_id watcher should NOT override billable
|
||||||
|
await page
|
||||||
|
.locator('.fc-event')
|
||||||
|
.filter({ hasText: 'Test cal persist override' })
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
|
||||||
|
// The billable dropdown should still show Non-Billable
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Save without changes and verify the response still has billable=false
|
||||||
|
const [updateResponse] = await Promise.all([
|
||||||
|
page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/time-entries/') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200
|
||||||
|
),
|
||||||
|
page.getByRole('button', { name: 'Update Time Entry' }).click(),
|
||||||
|
]);
|
||||||
|
const responseBody = await updateResponse.json();
|
||||||
|
expect(responseBody.data.billable).toBe(false);
|
||||||
|
});
|
||||||
252
e2e/time.spec.ts
252
e2e/time.spec.ts
@@ -9,6 +9,7 @@ import {
|
|||||||
startOrStopTimerWithButton,
|
startOrStopTimerWithButton,
|
||||||
stoppedTimeEntryResponse,
|
stoppedTimeEntryResponse,
|
||||||
} from './utils/currentTimeEntry';
|
} from './utils/currentTimeEntry';
|
||||||
|
import { createProject, createBillableProject, createBareTimeEntry } from './utils/reporting';
|
||||||
|
|
||||||
async function goToTimeOverview(page: Page) {
|
async function goToTimeOverview(page: Page) {
|
||||||
await page.goto(PLAYWRIGHT_BASE_URL + '/time');
|
await page.goto(PLAYWRIGHT_BASE_URL + '/time');
|
||||||
@@ -448,3 +449,254 @@ test('test that setting billable status via the create modal works', async ({ pa
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The following tests verify that changing the project on a time entry
|
||||||
|
* updates the billable status to match the new project's is_billable setting.
|
||||||
|
*
|
||||||
|
* Issue: https://github.com/solidtime-io/solidtime/issues/981
|
||||||
|
*/
|
||||||
|
|
||||||
|
test('test that changing project on a time entry row from non-billable to billable updates billable status', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const billableProjectName = 'Billable Row Project ' + Math.floor(1 + Math.random() * 10000);
|
||||||
|
const nonBillableProjectName =
|
||||||
|
'NonBillable Row Project ' + Math.floor(1 + Math.random() * 10000);
|
||||||
|
|
||||||
|
await createProject(page, nonBillableProjectName);
|
||||||
|
await createBillableProject(page, billableProjectName);
|
||||||
|
await createBareTimeEntry(page, 'Test billable row', '1h');
|
||||||
|
|
||||||
|
await goToTimeOverview(page);
|
||||||
|
const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first();
|
||||||
|
|
||||||
|
// Assign the non-billable project first
|
||||||
|
await timeEntryRow.getByRole('button', { name: 'No Project' }).click();
|
||||||
|
await page.getByRole('option', { name: nonBillableProjectName }).click();
|
||||||
|
await page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/time-entries/') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now switch to the billable project
|
||||||
|
await timeEntryRow.getByRole('button', { name: nonBillableProjectName }).click();
|
||||||
|
await page.getByRole('option', { name: billableProjectName }).click();
|
||||||
|
|
||||||
|
const updateResponse = await page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/time-entries/') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200
|
||||||
|
);
|
||||||
|
const responseBody = await updateResponse.json();
|
||||||
|
expect(responseBody.data.billable).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test that changing project on a time entry row from billable to non-billable updates billable status', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const billableProjectName = 'Billable Row Rev Project ' + Math.floor(1 + Math.random() * 10000);
|
||||||
|
const nonBillableProjectName =
|
||||||
|
'NonBillable Row Rev Project ' + Math.floor(1 + Math.random() * 10000);
|
||||||
|
|
||||||
|
await createBillableProject(page, billableProjectName);
|
||||||
|
await createProject(page, nonBillableProjectName);
|
||||||
|
await createBareTimeEntry(page, 'Test billable row reverse', '1h');
|
||||||
|
|
||||||
|
await goToTimeOverview(page);
|
||||||
|
const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first();
|
||||||
|
|
||||||
|
// Assign the billable project first
|
||||||
|
await timeEntryRow.getByRole('button', { name: 'No Project' }).click();
|
||||||
|
await page.getByRole('option', { name: billableProjectName }).click();
|
||||||
|
const firstResponse = await page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/time-entries/') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200
|
||||||
|
);
|
||||||
|
const firstBody = await firstResponse.json();
|
||||||
|
expect(firstBody.data.billable).toBe(true);
|
||||||
|
|
||||||
|
// Now switch to the non-billable project
|
||||||
|
await timeEntryRow.getByRole('button', { name: billableProjectName }).click();
|
||||||
|
await page.getByRole('option', { name: nonBillableProjectName }).click();
|
||||||
|
|
||||||
|
const updateResponse = await page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/time-entries/') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200
|
||||||
|
);
|
||||||
|
const responseBody = await updateResponse.json();
|
||||||
|
expect(responseBody.data.billable).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test that changing project in edit modal from non-billable to billable updates billable status', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const billableProjectName = 'Billable Modal Project ' + Math.floor(1 + Math.random() * 10000);
|
||||||
|
|
||||||
|
await createBillableProject(page, billableProjectName);
|
||||||
|
await createBareTimeEntry(page, 'Test billable modal', '1h');
|
||||||
|
|
||||||
|
await goToTimeOverview(page);
|
||||||
|
const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first();
|
||||||
|
|
||||||
|
// Open edit modal
|
||||||
|
await timeEntryRow.getByRole('button', { name: 'Actions for the time entry' }).first().click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||||
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
|
||||||
|
// Verify initially non-billable
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Select the billable project
|
||||||
|
await page.getByRole('dialog').getByRole('button', { name: 'No Project' }).click();
|
||||||
|
await page.getByRole('option', { name: billableProjectName }).click();
|
||||||
|
|
||||||
|
// Verify the billable dropdown updated to Billable
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Save and verify
|
||||||
|
const [updateResponse] = await Promise.all([
|
||||||
|
page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/time-entries/') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200
|
||||||
|
),
|
||||||
|
page.getByRole('button', { name: 'Update Time Entry' }).click(),
|
||||||
|
]);
|
||||||
|
const responseBody = await updateResponse.json();
|
||||||
|
expect(responseBody.data.billable).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test that opening edit modal for a time entry with manually overridden billable status preserves that status', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const billableProjectName = 'Billable Persist Project ' + Math.floor(1 + Math.random() * 10000);
|
||||||
|
|
||||||
|
await createBillableProject(page, billableProjectName);
|
||||||
|
await createBareTimeEntry(page, 'Test persist billable override', '1h');
|
||||||
|
|
||||||
|
await goToTimeOverview(page);
|
||||||
|
const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first();
|
||||||
|
|
||||||
|
// Open edit modal and assign the billable project
|
||||||
|
await timeEntryRow.getByRole('button', { name: 'Actions for the time entry' }).first().click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||||
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole('dialog').getByRole('button', { name: 'No Project' }).click();
|
||||||
|
await page.getByRole('option', { name: billableProjectName }).click();
|
||||||
|
|
||||||
|
// Verify it auto-set to Billable
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Now manually override billable to Non-Billable via the dropdown
|
||||||
|
await page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' }).click();
|
||||||
|
await page.getByRole('option', { name: 'Non Billable' }).click();
|
||||||
|
|
||||||
|
// Verify it shows Non-Billable now
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Save
|
||||||
|
const [firstSaveResponse] = await Promise.all([
|
||||||
|
page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/time-entries/') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200
|
||||||
|
),
|
||||||
|
page.getByRole('button', { name: 'Update Time Entry' }).click(),
|
||||||
|
]);
|
||||||
|
const firstBody = await firstSaveResponse.json();
|
||||||
|
expect(firstBody.data.billable).toBe(false);
|
||||||
|
|
||||||
|
// Re-open the edit modal — the project_id watcher should NOT override billable back to true
|
||||||
|
await timeEntryRow.getByRole('button', { name: 'Actions for the time entry' }).first().click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||||
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
|
||||||
|
// The billable dropdown should still show Non-Billable
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Save without changes and verify the response still has billable=false
|
||||||
|
const [updateResponse] = await Promise.all([
|
||||||
|
page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/time-entries/') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200
|
||||||
|
),
|
||||||
|
page.getByRole('button', { name: 'Update Time Entry' }).click(),
|
||||||
|
]);
|
||||||
|
const responseBody = await updateResponse.json();
|
||||||
|
expect(responseBody.data.billable).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test that changing project in edit modal from billable to non-billable updates billable status', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const billableProjectName =
|
||||||
|
'Billable Modal Rev Project ' + Math.floor(1 + Math.random() * 10000);
|
||||||
|
const nonBillableProjectName =
|
||||||
|
'NonBillable Modal Rev Project ' + Math.floor(1 + Math.random() * 10000);
|
||||||
|
|
||||||
|
await createBillableProject(page, billableProjectName);
|
||||||
|
await createProject(page, nonBillableProjectName);
|
||||||
|
await createBareTimeEntry(page, 'Test billable modal reverse', '1h');
|
||||||
|
|
||||||
|
await goToTimeOverview(page);
|
||||||
|
const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first();
|
||||||
|
|
||||||
|
// Open edit modal
|
||||||
|
await timeEntryRow.getByRole('button', { name: 'Actions for the time entry' }).first().click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||||
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
|
||||||
|
// First assign the billable project
|
||||||
|
await page.getByRole('dialog').getByRole('button', { name: 'No Project' }).click();
|
||||||
|
await page.getByRole('option', { name: billableProjectName }).click();
|
||||||
|
|
||||||
|
// Verify billable status flipped to Billable
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Now switch to the non-billable project
|
||||||
|
await page.getByRole('dialog').getByRole('button', { name: billableProjectName }).click();
|
||||||
|
await page.getByRole('option', { name: nonBillableProjectName }).click();
|
||||||
|
|
||||||
|
// Verify billable status reverted to Non-Billable
|
||||||
|
await expect(
|
||||||
|
page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' })
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Save and verify
|
||||||
|
const [updateResponse] = await Promise.all([
|
||||||
|
page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/time-entries/') &&
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.status() === 200
|
||||||
|
),
|
||||||
|
page.getByRole('button', { name: 'Update Time Entry' }).click(),
|
||||||
|
]);
|
||||||
|
const responseBody = await updateResponse.json();
|
||||||
|
expect(responseBody.data.billable).toBe(false);
|
||||||
|
});
|
||||||
|
|||||||
@@ -35,6 +35,25 @@ export async function createProject(page: Page, projectName: string) {
|
|||||||
await expect(page.getByText(projectName)).toBeVisible();
|
await expect(page.getByText(projectName)).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createBillableProject(page: Page, projectName: string) {
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/projects');
|
||||||
|
await expect(page.getByRole('button', { name: 'Create Project' })).toBeVisible();
|
||||||
|
await page.getByRole('button', { name: 'Create Project' }).click();
|
||||||
|
await page.getByLabel('Project name').fill(projectName);
|
||||||
|
await page.getByText('Non-Billable').click();
|
||||||
|
await page.getByText('Default Rate').click();
|
||||||
|
await Promise.all([
|
||||||
|
page.getByRole('dialog').getByRole('button', { name: 'Create Project' }).click(),
|
||||||
|
page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('/projects') &&
|
||||||
|
response.request().method() === 'POST' &&
|
||||||
|
response.status() === 201
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
await expect(page.getByText(projectName)).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
export async function createClient(page: Page, clientName: string) {
|
export async function createClient(page: Page, clientName: string) {
|
||||||
await page.goto(PLAYWRIGHT_BASE_URL + '/clients');
|
await page.goto(PLAYWRIGHT_BASE_URL + '/clients');
|
||||||
await expect(page.getByRole('button', { name: 'Create Client' })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Create Client' })).toBeVisible();
|
||||||
|
|||||||
@@ -74,6 +74,18 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => editableTimeEntry.value?.project_id,
|
||||||
|
(value, oldValue) => {
|
||||||
|
if (oldValue !== undefined && value !== oldValue && editableTimeEntry.value) {
|
||||||
|
const project = props.projects.find((p) => p.id === value);
|
||||||
|
if (project) {
|
||||||
|
editableTimeEntry.value.billable = project.is_billable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const localStart = computed({
|
const localStart = computed({
|
||||||
get: () =>
|
get: () =>
|
||||||
editableTimeEntry.value ? getLocalizedDayJs(editableTimeEntry.value.start).format() : '',
|
editableTimeEntry.value ? getLocalizedDayJs(editableTimeEntry.value.start).format() : '',
|
||||||
|
|||||||
@@ -67,10 +67,12 @@ function updateStartEndTime(start: string, end: string | null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateProjectAndTask(projectId: string, taskId: string) {
|
function updateProjectAndTask(projectId: string, taskId: string) {
|
||||||
|
const project = props.projects.find((p) => p.id === projectId);
|
||||||
props.updateTimeEntry({
|
props.updateTimeEntry({
|
||||||
...props.timeEntry,
|
...props.timeEntry,
|
||||||
project_id: projectId,
|
project_id: projectId,
|
||||||
task_id: taskId,
|
task_id: taskId,
|
||||||
|
billable: project ? project.is_billable : props.timeEntry.billable,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user