|
| 1 | +import type { Page, Route } from '@playwright/test' |
| 2 | +import { readFileSync } from 'fs' |
| 3 | +import { fileURLToPath } from 'url' |
| 4 | +import { dirname, join } from 'path' |
| 5 | + |
| 6 | +const __dirname = dirname(fileURLToPath(import.meta.url)) |
| 7 | +const FIXTURES = join(__dirname, '../.vitepress/public/data/chart-api') |
| 8 | + |
| 9 | +// Static fixture data loaded once |
| 10 | +const quotesJson = readFileSync(join(FIXTURES, 'quotes.json'), 'utf8') |
| 11 | +const indicatorsJson = readFileSync(join(FIXTURES, 'indicators.json'), 'utf8') |
| 12 | +const smaJson = readFileSync(join(FIXTURES, 'sma.json'), 'utf8') |
| 13 | +const rsiJson = readFileSync(join(FIXTURES, 'rsi.json'), 'utf8') |
| 14 | + |
| 15 | +/** |
| 16 | + * Intercept all stock-charts API requests and respond with static fixture data, |
| 17 | + * so suites are hermetic and never depend on the live API. |
| 18 | + * |
| 19 | + * The API serves indicator data from per-indicator endpoints keyed by UIID |
| 20 | + * (e.g. `/SMA/`, `/RSI/`, `/MACD/`) — NOT `/indicators/<name>`. The listings |
| 21 | + * fixture (`indicators.json`) carries those absolute endpoints, so the client |
| 22 | + * requests `/<UIID>/` and the routes below must match that shape. |
| 23 | + * |
| 24 | + * Every pattern is anchored to the API host. Bare path patterns would also |
| 25 | + * match same-named routes on the docs site itself — `/indicators` is a real |
| 26 | + * page — and would answer that document request with JSON. |
| 27 | + */ |
| 28 | +const API = 'charts-api\\.stockindicators\\.dev' |
| 29 | + |
| 30 | +export async function mockStockChartsApi(page: Page): Promise<void> { |
| 31 | + // Routes are matched LIFO (last-registered = highest priority). This catch-all |
| 32 | + // is registered first, so every specific route below shadows it. Any indicator |
| 33 | + // endpoint we don't explicitly fixture returns an empty array → the chart |
| 34 | + // reaches the (tolerated) empty state instead of touching the network. |
| 35 | + await page.route(new RegExp(`${API}/.+`), (route: Route) => |
| 36 | + route.fulfill({ contentType: 'application/json', body: '[]' }) |
| 37 | + ) |
| 38 | + |
| 39 | + await page.route(new RegExp(`${API}/quotes(?:\\?|$)`), (route: Route) => |
| 40 | + route.fulfill({ contentType: 'application/json', body: quotesJson }) |
| 41 | + ) |
| 42 | + |
| 43 | + await page.route(new RegExp(`${API}/indicators(?:\\?|$)`), (route: Route) => |
| 44 | + route.fulfill({ contentType: 'application/json', body: indicatorsJson }) |
| 45 | + ) |
| 46 | + |
| 47 | + // SMA indicator data — endpoint is `/SMA/?lookbackPeriods=...` |
| 48 | + await page.route(new RegExp(`${API}/SMA/`, 'i'), (route: Route) => |
| 49 | + route.fulfill({ contentType: 'application/json', body: smaJson }) |
| 50 | + ) |
| 51 | + |
| 52 | + // RSI indicator data — endpoint is `/RSI/?lookbackPeriods=...` |
| 53 | + await page.route(new RegExp(`${API}/RSI/`, 'i'), (route: Route) => |
| 54 | + route.fulfill({ contentType: 'application/json', body: rsiJson }) |
| 55 | + ) |
| 56 | +} |
| 57 | + |
| 58 | +/** |
| 59 | + * Selector matching any terminal state of a chart: rendered, empty, or errored. |
| 60 | + * Waiting on this is the web-first way to know a chart has stopped changing. |
| 61 | + */ |
| 62 | +export const CHART_TERMINAL_SELECTOR = |
| 63 | + '[data-testid$="-overlay-canvas"], [data-testid$="-empty"], [data-testid$="-error"]' |
0 commit comments