Files
calendar/apps/web/playwright/lib/chart-helpers.ts
T
Eunjae LeeGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
6479adf14e fix: integer to text comparison in routing insights query (#25019)
* fix: integer to text comparison in routing insights query

Add explicit integer array cast to prevent PostgreSQL type mismatch error when comparing bookingUserId (integer) with user_id array values in getRoutedToPerPeriodData query

* add e2e tests

* refactor: remove LoadingInsight component and handle loading in ChartCard

- Enhanced ChartCard to render default loading UI when isPending is true
- Replaced all LoadingInsight usages with ChartCard that accepts isPending/isError props
- Removed LoadingInsight component and updated exports
- Updated 16 chart components to use the new pattern
- ChartCard now shows spinner and skeleton title during loading state

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* fix: make children prop optional in ChartCard when isPending is true

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* refactor: remove unused loadingState prop from ChartCard

- Remove loadingState prop and ChartLoadingState type export
- Simplify computedLoadingState to derive state only from isPending/isError
- No functional changes - loadingState was not being used by any components
- data-loading-state attribute behavior remains unchanged for E2E tests

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* refactor: remove duplicate isPending early returns from chart components

- Update all 16 chart components to use single ChartCard return pattern
- Gate children rendering with !isPending && isSuccess && data checks
- Prevents data processing code from executing during loading state
- Improves code consistency and maintainability across all charts

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* refactor: remove redundant !isPending check from chart conditionals

- Simplify conditional rendering to use just 'isSuccess && data' or 'isSuccess'
- In TanStack Query, isSuccess and isPending are mutually exclusive
- The !isPending check was redundant since isSuccess already implies !isPending
- Applied to all 16 chart components for consistency
- Components with safe defaults (data ?? []) use just 'isSuccess'
- Components requiring data check use 'isSuccess && data'

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* revert the mistake

* apply feedback

* apply feedback

* fix e2e

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-12-01 15:07:57 +01:00

135 lines
4.6 KiB
TypeScript

import { expect, type Page } from "@playwright/test";
export type ChartLoadingState = "initial" | "loading" | "loaded" | "error";
export interface ChartLoadingResult {
chartId: string | null;
loadingState: ChartLoadingState;
success: boolean;
error?: string;
}
/**
* Wait for all chart cards on the page to reach "loaded" state
* @param page Playwright page object
* @param timeout Maximum time to wait for all charts (default: 15000ms)
* @returns Array of chart loading results
*/
export async function waitForAllChartsToLoad(page: Page, timeout = 15000): Promise<ChartLoadingResult[]> {
const startTime = Date.now();
const results: ChartLoadingResult[] = [];
// Wait for at least one chart card to be visible
await page.locator('[data-testid="chart-card"]').first().waitFor({ state: "visible", timeout });
// Get all chart cards
const chartCards = await page.locator('[data-testid="chart-card"]').all();
if (chartCards.length === 0) {
throw new Error("No chart cards found on the page");
}
// Wait for each chart to reach loaded state
for (let i = 0; i < chartCards.length; i++) {
const chart = chartCards[i];
const remainingTime = timeout - (Date.now() - startTime);
if (remainingTime <= 0) {
throw new Error(`Timeout waiting for charts to load after ${timeout}ms`);
}
const chartId = await chart.getAttribute("data-chart-id");
const result: ChartLoadingResult = {
chartId,
loadingState: "initial",
success: false,
};
try {
// Wait for this specific chart to reach loaded state
await expect(chart).toHaveAttribute("data-loading-state", "loaded", {
timeout: remainingTime,
});
result.loadingState = "loaded";
result.success = true;
} catch {
// Get the actual state if it failed
const actualState = await chart.getAttribute("data-loading-state");
result.loadingState = (actualState as ChartLoadingState) || "initial";
result.error = `Chart "${chartId}" failed to load. Current state: ${actualState}`;
result.success = false;
}
results.push(result);
}
return results;
}
/**
* Assert that all charts on the page have loaded successfully
* @param page Playwright page object
* @param timeout Maximum time to wait for all charts (default: 15000ms)
*/
export async function expectAllChartsToLoad(page: Page, timeout = 15000): Promise<void> {
const results = await waitForAllChartsToLoad(page, timeout);
const failedCharts = results.filter((r) => !r.success);
if (failedCharts.length > 0) {
const errorMessages = failedCharts.map((r) => r.error).join("\n");
throw new Error(`${failedCharts.length} chart(s) failed to load:\n${errorMessages}`);
}
// Additional check: ensure no charts are in error state
const errorCharts = page.locator('[data-testid="chart-card"][data-loading-state="error"]');
const errorCount = await errorCharts.count();
if (errorCount > 0) {
const errorChartIds = await Promise.all(
(await errorCharts.all()).map((chart) => chart.getAttribute("data-chart-id"))
);
throw new Error(`${errorCount} chart(s) are in error state: ${errorChartIds.join(", ")}`);
}
}
/**
* Get the loading state of a specific chart by its ID
* @param page Playwright page object
* @param chartId The chart ID (derived from title)
* @returns The loading state of the chart
*/
export async function getChartLoadingState(page: Page, chartId: string): Promise<ChartLoadingState | null> {
const chart = page.locator(`[data-testid="chart-card"][data-chart-id="${chartId}"]`);
const state = await chart.getAttribute("data-loading-state");
return state as ChartLoadingState | null;
}
/**
* Wait for a specific chart to reach loaded state
* @param page Playwright page object
* @param chartId The chart ID (derived from title)
* @param timeout Maximum time to wait (default: 10000ms)
*/
export async function waitForChartToLoad(page: Page, chartId: string, timeout = 10000): Promise<void> {
const chart = page.locator(`[data-testid="chart-card"][data-chart-id="${chartId}"]`);
await expect(chart).toHaveAttribute("data-loading-state", "loaded", { timeout });
}
/**
* Get all chart IDs on the page
* @param page Playwright page object
* @returns Array of chart IDs
*/
export async function getAllChartIds(page: Page): Promise<string[]> {
const chartCards = await page.locator('[data-testid="chart-card"]').all();
const chartIds = await Promise.all(
chartCards.map(async (chart) => {
const id = await chart.getAttribute("data-chart-id");
return id || "unknown";
})
);
return chartIds;
}