6479adf14e
* 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>
135 lines
4.6 KiB
TypeScript
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;
|
|
}
|