diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 03f3d6d2..96977003 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -1,6 +1,20 @@ import { defineConfig } from 'vitepress' import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' +/** Draft Lab (docs/lab): included in `docs:dev`; omitted from `docs:build` via scripts/docs-build-prod.mjs (sets LAB_PAGES=0). */ +const includeLabPages = process.env.LAB_PAGES !== '0' + +const presetSidebar = [ + { + text: 'Presets (maintainers)', + items: [ + { text: 'Preset development', link: '/PRESET_DEVELOPMENT' }, + { text: 'Preset template', link: '/PRESET_TEMPLATE' }, + { text: '← Advanced overview', link: '/advanced/' } + ] + } +] + export default defineConfig({ markdown: { config(md) { @@ -21,69 +35,187 @@ export default defineConfig({ ['meta', { name: 'twitter:image', content: 'https://rickcedwhat.github.io/playwright-smart-table/og.png' }], ], themeConfig: { - nav: [ - { text: 'Guide', link: '/guide/start' }, - { text: 'Examples', link: '/examples/' }, - { text: 'API', link: '/api/' }, - ], + nav: [ + { text: 'Guide', link: '/guide/getting-started' }, + ...(includeLabPages ? ([{ text: 'Lab', link: '/lab/' }] as const) : []), + { text: 'API', link: '/api/' }, + { text: 'Examples', link: '/examples/' }, + { text: 'Help', link: '/troubleshooting' } + ], - sidebar: { - '/guide/': [ - { - items: [ - { text: 'Quick Start', link: '/guide/start' }, - { - text: 'Describe Your Table', - link: '/guide/describe/', - collapsed: false, - items: [ - { text: 'Where is your table?', link: '/guide/describe/locator' }, - { text: 'Headers, rows & cells', link: '/guide/describe/identify' }, - { text: 'Pagination', link: '/guide/describe/pagination' }, - { text: 'Virtualization', link: '/guide/describe/virtualization' }, - { text: 'Header text', link: '/guide/describe/header-text' }, - { text: 'Editing cells', link: '/guide/describe/editing' }, - { text: 'Column overrides', link: '/guide/describe/column-overrides' }, - ] - }, - { - text: 'Interact with Your Table', - link: '/guide/query/', - collapsed: false, - items: [ - { text: 'Find rows', link: '/guide/query/find-rows' }, - { text: 'Read cells', link: '/guide/query/read-cells' }, - { text: 'Iterate rows', link: '/guide/query/iterate' }, - { text: 'Write to cells', link: '/guide/query/write' }, - ] - }, - ] - } - ], - '/examples/': [ - { - text: 'Examples', - items: [ - { text: 'Overview', link: '/examples/' }, - ] - } - ], - '/api/': [ - { - text: 'API', - items: [ - { text: 'Reference', link: '/api/' }, - ] - } - ], - }, + sidebar: { + '/guide/': [ + { + text: 'Guide', + items: [ + { text: 'Getting Started', link: '/guide/getting-started' }, + { text: 'Why Smart Table?', link: '/guide/why' }, + { text: 'Filtering & Queries', link: '/guide/filtering' }, + { text: 'Configuration', link: '/guide/configuration' }, + { + text: 'How It Works', + collapsed: true, + items: [ + { text: 'Table Anatomy', link: '/guide/table-anatomy' }, + { text: 'Header Mapping', link: '/guide/header-mapping' }, + { text: 'Pagination', link: '/guide/pagination' }, + { text: 'Strategies', link: '/guide/strategies' }, + { text: 'Concurrency Modes', link: '/guide/concurrency' }, + ] + }, + { + text: 'Advanced', + collapsed: true, + items: [ + { text: 'Custom Resolution', link: '/advanced/custom-resolution' }, + { text: 'TypeScript Tips', link: '/advanced/typescript' }, + { text: 'Custom Strategies', link: '/advanced/custom-strategies' }, + { text: 'Performance Tips', link: '/advanced/performance' }, + { text: 'Preset Development', link: '/PRESET_DEVELOPMENT' }, + { text: 'Preset Template', link: '/PRESET_TEMPLATE' } + ] + } + ] + } + ], + ...(includeLabPages + ? { + '/lab/': [ + { + text: 'Lab (draft)', + items: [{ text: 'Rough drafts', link: '/lab/' }] + } + ] + } + : {}), + '/examples/': [ + { + text: 'Examples', + items: [ + { text: 'Overview', link: '/examples/' }, + { text: 'Basic Usage', link: '/examples/basic' }, + { text: 'Pagination', link: '/examples/pagination' }, + { text: 'Infinite Scroll', link: '/examples/infinite-scroll' }, + { text: 'MUI DataGrid', link: '/examples/mui-datagrid' }, + { text: 'AG Grid', link: '/examples/ag-grid' }, + { text: 'Data Scraping', link: '/examples/data-scraping' }, + ] + } + ], + '/api/': [ + { + text: 'API', + items: [ + { text: 'Overview', link: '/api/' }, + { + text: 'TableConfig', + link: '/api/table-config', + collapsed: false, + items: [ + { text: 'columnOverrides', link: '/api/table-config#columnoverrides' }, + { text: 'headerSelector', link: '/api/table-config#headerselector' }, + { text: 'rowSelector', link: '/api/table-config#rowselector' }, + { text: 'cellSelector', link: '/api/table-config#cellselector' }, + { text: 'maxPages', link: '/api/table-config#maxpages' }, + { text: 'concurrency', link: '/api/table-config#concurrency' }, + { text: 'headerTransformer', link: '/api/table-config#headertransformer' }, + { text: 'strategies', link: '/api/table-config#strategies' }, + { text: 'autoScroll', link: '/api/table-config#autoscroll' }, + { text: 'onReset', link: '/api/table-config#onreset' }, + { text: 'debug', link: '/api/table-config#debug' } + ] + }, + { + text: 'Table Methods', + link: '/api/table-methods', + collapsed: false, + items: [ + { text: 'init()', link: '/api/table-methods#init' }, + { text: 'isInitialized()', link: '/api/table-methods#isinitialized' }, + { text: 'getRow()', link: '/api/table-methods#getrow' }, + { text: 'getRowByIndex()', link: '/api/table-methods#getrowbyindex' }, + { text: 'findRow()', link: '/api/table-methods#findrow' }, + { text: 'findRows()', link: '/api/table-methods#findrows' }, + { text: 'forEach()', link: '/api/table-methods#foreach' }, + { text: 'map()', link: '/api/table-methods#map' }, + { text: 'filter()', link: '/api/table-methods#filter' }, + { + text: 'Async iterator', + link: '/api/table-methods#async-iterator-for-await-of' + }, + { text: 'getHeaders()', link: '/api/table-methods#getheaders' }, + { text: 'getHeaderCell()', link: '/api/table-methods#getheadercell' }, + { text: 'scrollToColumn()', link: '/api/table-methods#scrolltocolumn' }, + { text: 'reset()', link: '/api/table-methods#reset' }, + { text: 'revalidate()', link: '/api/table-methods#revalidate' }, + { text: 'sorting', link: '/api/table-methods#sorting' }, + { text: 'generateConfig()', link: '/api/table-methods#generateconfig' }, + { text: 'generateConfigPrompt()', link: '/api/table-methods#generateconfigprompt' } + ] + }, + { + text: 'SmartRow', + link: '/api/smart-row', + collapsed: false, + items: [ + { text: 'getCell()', link: '/api/smart-row#getcell' }, + { text: 'toJSON()', link: '/api/smart-row#tojson' }, + { text: 'bringIntoView()', link: '/api/smart-row#bringintoview' }, + { text: 'smartFill()', link: '/api/smart-row#smartfill' }, + { text: 'wasFound()', link: '/api/smart-row#wasfound' } + ] + }, + { + text: 'SmartRowArray', + link: '/api/smart-row-array' + }, + { + text: 'Strategies', + link: '/api/strategies', + collapsed: false, + items: [ + { text: 'Pagination', link: '/api/strategies#pagination-strategies' }, + { text: 'Sorting', link: '/api/strategies#sorting-strategies' }, + { text: 'Fill', link: '/api/strategies#fill-strategy' }, + { text: 'Header', link: '/api/strategies#header-strategy' }, + { text: 'Cell locator', link: '/api/strategies#cell-locator-strategy' }, + { text: 'Viewport', link: '/api/strategies#viewport-strategy' } + ] + } + ] + } + ], + '/advanced/': [ + { + text: 'Advanced', + items: [ + { text: 'Custom Resolution', link: '/advanced/custom-resolution' }, + { text: 'TypeScript Tips', link: '/advanced/typescript' }, + { text: 'Custom Strategies', link: '/advanced/custom-strategies' }, + { text: 'Performance Tips', link: '/advanced/performance' }, + { text: 'Preset Development', link: '/PRESET_DEVELOPMENT' }, + { text: 'Preset Template', link: '/PRESET_TEMPLATE' } + ] + } + ], + '/PRESET_DEVELOPMENT': presetSidebar, + '/PRESET_TEMPLATE': presetSidebar, + '/troubleshooting': [ + { + text: 'Help', + items: [ + { text: 'Troubleshooting', link: '/troubleshooting' } + ] + } + ] + }, - socialLinks: [ - { icon: 'github', link: 'https://github.com/rickcedwhat/playwright-smart-table' } - ], + socialLinks: [ + { icon: 'github', link: 'https://github.com/rickcedwhat/playwright-smart-table' } + ], - search: { - provider: 'local' + search: { + provider: 'local' + } } - } }) diff --git a/docs/.vitepress/theme/components/ConfigSwatch.vue b/docs/.vitepress/theme/components/ConfigSwatch.vue new file mode 100644 index 00000000..408ca9c0 --- /dev/null +++ b/docs/.vitepress/theme/components/ConfigSwatch.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/docs/.vitepress/theme/components/MethodBadge.vue b/docs/.vitepress/theme/components/MethodBadge.vue new file mode 100644 index 00000000..43fecfd5 --- /dev/null +++ b/docs/.vitepress/theme/components/MethodBadge.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/docs/.vitepress/theme/components/lab/LabConcurrencyAnimator.vue b/docs/.vitepress/theme/components/lab/LabConcurrencyAnimator.vue new file mode 100644 index 00000000..c6c5a907 --- /dev/null +++ b/docs/.vitepress/theme/components/lab/LabConcurrencyAnimator.vue @@ -0,0 +1,919 @@ + + + + + diff --git a/docs/.vitepress/theme/components/lab/LabDebugPlayback.vue b/docs/.vitepress/theme/components/lab/LabDebugPlayback.vue deleted file mode 100644 index 39ecd7db..00000000 --- a/docs/.vitepress/theme/components/lab/LabDebugPlayback.vue +++ /dev/null @@ -1,140 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/components/lab/LabDebuggerWidget.vue b/docs/.vitepress/theme/components/lab/LabDebuggerWidget.vue index 80b9311b..91171995 100644 --- a/docs/.vitepress/theme/components/lab/LabDebuggerWidget.vue +++ b/docs/.vitepress/theme/components/lab/LabDebuggerWidget.vue @@ -157,11 +157,11 @@ onUnmounted(() => { }) // --- -const advanceIcon = computed(() => !hasStarted.value ? '▶' : isDone.value ? '✓' : '⏭') -const advanceLabel = computed(() => !hasStarted.value ? 'Play' : isDone.value ? 'Playback complete' : `Run line ${nextLine.value + 1}`) +const advanceIcon = computed(() => !hasStarted.value ? '▶' : isDone.value ? '↺' : '⏭') +const advanceLabel = computed(() => !hasStarted.value ? 'Play' : isDone.value ? 'Replay' : `Run line ${nextLine.value + 1}`) function stepLine() { - if (isDone.value) return + if (isDone.value) resetDebugger() if (!executedSet.value.has(nextLine.value)) executed.value = [...executed.value, nextLine.value] nextLine.value += 1 } @@ -179,12 +179,9 @@ function cellClass(rowIndex: number, header: string): string {
{{ title ?? 'Debugger widget' }}
- -
diff --git a/docs/.vitepress/theme/components/lab/LabFailureStates.vue b/docs/.vitepress/theme/components/lab/LabFailureStates.vue deleted file mode 100644 index 04fe2fd3..00000000 --- a/docs/.vitepress/theme/components/lab/LabFailureStates.vue +++ /dev/null @@ -1,111 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/components/lab/LabGetRowTrace.vue b/docs/.vitepress/theme/components/lab/LabGetRowTrace.vue index 72b6d65c..75fcd411 100644 --- a/docs/.vitepress/theme/components/lab/LabGetRowTrace.vue +++ b/docs/.vitepress/theme/components/lab/LabGetRowTrace.vue @@ -24,31 +24,38 @@ const TARGET_CELL = 'Office' const TARGET_VALUE = 'Sydney' const targetPage = pages.findIndex(p => p.some(r => r.name === TARGET_NAME)) -// idle → checking → rejected (user clicks Next) | found → asserting → done -type Phase = 'idle' | 'checking' | 'rejected' | 'found' | 'asserting' | 'done' +type Scenario = 'success' | 'exhausted' +const scenario = ref('success') +const started = ref(false) +const MAX_PAGES: Record = { success: 5, exhausted: 2 } + +// idle → checking → rejected (user clicks Next) | found → asserting → done | exhausted +type Phase = 'idle' | 'checking' | 'rejected' | 'found' | 'asserting' | 'done' | 'exhausted' const phase = ref('idle') const pageIdx = ref(0) const currentRows = computed(() => pages[pageIdx.value]) function rowState(name: string): 'idle' | 'checking' | 'rejected' | 'match' { - if (phase.value === 'idle') return 'idle' - if (phase.value === 'checking') return 'checking' - if (phase.value === 'rejected') return 'rejected' + if (phase.value === 'idle') return 'idle' + if (phase.value === 'checking') return 'checking' + if (phase.value === 'rejected' || phase.value === 'exhausted') return 'rejected' if (phase.value === 'found' || phase.value === 'asserting' || phase.value === 'done') return name === TARGET_NAME ? 'match' : 'rejected' return 'idle' } const statusText = computed(() => { - if (phase.value === 'idle') return '' - if (phase.value === 'checking') return `checking page ${pageIdx.value + 1}…` - if (phase.value === 'rejected') return `no match on page ${pageIdx.value + 1}` - if (phase.value === 'found') return `✓ found on page ${pageIdx.value + 1}` + if (phase.value === 'idle') return '' + if (phase.value === 'checking') return `checking page ${pageIdx.value + 1}…` + if (phase.value === 'rejected') return `no match on page ${pageIdx.value + 1}` + if (phase.value === 'exhausted') return `✗ maxPages (${MAX_PAGES[scenario.value]}) exhausted — row not found` + if (phase.value === 'found') return `✓ found on page ${pageIdx.value + 1}` if (phase.value === 'asserting' || phase.value === 'done') return `✓ '${TARGET_VALUE}' matches` return '' }) -const statusOk = computed(() => ['found','asserting','done'].includes(phase.value)) +const statusOk = computed(() => ['found','asserting','done'].includes(phase.value)) +const statusErr = computed(() => phase.value === 'exhausted') const timers: ReturnType[] = [] function after(ms: number, fn: () => void) { timers.push(setTimeout(fn, ms)) } @@ -57,18 +64,28 @@ onUnmounted(() => timers.forEach(clearTimeout)) function landOnPage() { phase.value = 'checking' after(700, () => { - if (pageIdx.value === targetPage) { + if (pageIdx.value === targetPage && scenario.value === 'success') { phase.value = 'found' after(1000, () => { phase.value = 'asserting' after(1800, () => { phase.value = 'done' }) }) } else { - phase.value = 'rejected' + // Check if we've hit maxPages limit + if (pageIdx.value + 1 >= MAX_PAGES[scenario.value]) { + phase.value = 'exhausted' + } else { + phase.value = 'rejected' + } } }) } +function onStart() { + started.value = true + landOnPage() +} + function onNext() { if (phase.value !== 'rejected') return pageIdx.value++ @@ -76,30 +93,57 @@ function onNext() { after(200, () => landOnPage()) } -function onReplay() { +function reset() { timers.forEach(clearTimeout) timers.length = 0 phase.value = 'idle' pageIdx.value = 0 - after(300, () => landOnPage()) + started.value = false +} + +function replay() { + reset() + onStart() } -// auto-start -landOnPage() +function setScenario(s: Scenario) { + if (scenario.value === s) return + scenario.value = s + reset() +}