222 lines
7.7 KiB
TypeScript
222 lines
7.7 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test'
|
|
import { sendShortcut } from './helpers'
|
|
|
|
const PRIMARY_VAULT_PATH = '/Users/mock/Field Notes'
|
|
const RESEARCH_VAULT_PATH = '/Users/mock/Research Lab'
|
|
const GETTING_STARTED_PATH = '/Users/mock/Getting Started'
|
|
const QUICK_OPEN_INPUT = 'input[placeholder="Search notes..."]'
|
|
const GLOBAL_SEARCH_INPUT = 'input[placeholder="Search in all notes..."]'
|
|
const SEARCH_NEEDLE = 'crossgraph-needle'
|
|
|
|
interface MockEntry {
|
|
path: string
|
|
filename: string
|
|
title: string
|
|
isA: string
|
|
aliases: string[]
|
|
belongsTo: string[]
|
|
relatedTo: string[]
|
|
status: string | null
|
|
archived: boolean
|
|
modifiedAt: number | null
|
|
createdAt: number | null
|
|
fileSize: number
|
|
snippet: string
|
|
wordCount: number
|
|
relationships: Record<string, string[]>
|
|
outgoingLinks: string[]
|
|
properties: Record<string, unknown>
|
|
template: null
|
|
sort: null
|
|
}
|
|
|
|
interface SearchResultData {
|
|
title: string
|
|
path: string
|
|
snippet: string
|
|
score: number
|
|
note_type: string | null
|
|
}
|
|
|
|
type MockHandler = (args?: Record<string, unknown>) => unknown
|
|
type MockWindow = Window & {
|
|
__mockHandlers?: Record<string, MockHandler>
|
|
__mountedWorkspaceSearchCalls?: string[]
|
|
}
|
|
interface MountedWorkspaceInitData {
|
|
contentByPathData: Record<string, string>
|
|
entriesByVaultData: Record<string, MockEntry[]>
|
|
gettingStartedPath: string
|
|
primaryVaultPath: string
|
|
researchVaultPath: string
|
|
}
|
|
|
|
function createMockEntry(vaultPath: string, filename: string, title: string, snippet: string): MockEntry {
|
|
return {
|
|
path: `${vaultPath}/${filename}`,
|
|
filename,
|
|
title,
|
|
isA: 'Note',
|
|
aliases: [],
|
|
belongsTo: [],
|
|
relatedTo: [],
|
|
status: null,
|
|
archived: false,
|
|
modifiedAt: 1_700_000_000,
|
|
createdAt: null,
|
|
fileSize: 256,
|
|
snippet,
|
|
wordCount: 12,
|
|
relationships: {},
|
|
outgoingLinks: [],
|
|
properties: {},
|
|
template: null,
|
|
sort: null,
|
|
}
|
|
}
|
|
|
|
async function installMountedWorkspaceMocks(page: Page): Promise<void> {
|
|
await page.route('**/api/vault/**', (route) => route.abort())
|
|
|
|
const primaryEntry = createMockEntry(
|
|
PRIMARY_VAULT_PATH,
|
|
'primary-signal.md',
|
|
'Primary Signal',
|
|
`Primary workspace has ${SEARCH_NEEDLE}`,
|
|
)
|
|
const researchEntry = createMockEntry(
|
|
RESEARCH_VAULT_PATH,
|
|
'research-beacon.md',
|
|
'Research Beacon',
|
|
`Research workspace has ${SEARCH_NEEDLE}`,
|
|
)
|
|
const entriesByVault: Record<string, MockEntry[]> = {
|
|
[PRIMARY_VAULT_PATH]: [primaryEntry],
|
|
[RESEARCH_VAULT_PATH]: [researchEntry],
|
|
}
|
|
const contentByPath = Object.fromEntries(
|
|
[primaryEntry, researchEntry].map((entry) => [entry.path, `# ${entry.title}\n\n${entry.snippet}`]),
|
|
)
|
|
|
|
await page.addInitScript((data: MountedWorkspaceInitData) => {
|
|
localStorage.clear()
|
|
localStorage.setItem('tolaria:claude-code-onboarding-dismissed', '1')
|
|
|
|
const mockWindow = window as MockWindow
|
|
mockWindow.__mountedWorkspaceSearchCalls = []
|
|
let ref: Record<string, MockHandler> | undefined
|
|
const entriesByVaultData = data.entriesByVaultData
|
|
const contentByPathData = data.contentByPathData
|
|
|
|
Object.defineProperty(window, '__mockHandlers', {
|
|
configurable: true,
|
|
set(value: Record<string, MockHandler>) {
|
|
ref = value
|
|
ref.load_vault_list = () => ({
|
|
vaults: [
|
|
{
|
|
label: 'Field Notes',
|
|
path: data.primaryVaultPath,
|
|
alias: 'field',
|
|
color: 'blue',
|
|
icon: null,
|
|
mounted: true,
|
|
},
|
|
{
|
|
label: 'Research Lab',
|
|
path: data.researchVaultPath,
|
|
alias: 'lab',
|
|
color: 'green',
|
|
icon: null,
|
|
mounted: true,
|
|
},
|
|
],
|
|
active_vault: data.primaryVaultPath,
|
|
default_workspace_path: data.researchVaultPath,
|
|
hidden_defaults: [],
|
|
})
|
|
ref.save_vault_list = () => null
|
|
ref.get_settings = () => ({ multi_workspace_enabled: true })
|
|
ref.get_default_vault_path = () => data.gettingStartedPath
|
|
ref.check_vault_exists = (args?: Record<string, unknown>) =>
|
|
args?.path === data.primaryVaultPath || args?.path === data.researchVaultPath
|
|
ref.list_vault = (args?: Record<string, unknown>) => entriesByVaultData[String(args?.path ?? '')] ?? []
|
|
ref.reload_vault = (args?: Record<string, unknown>) => entriesByVaultData[String(args?.path ?? '')] ?? []
|
|
ref.list_vault_folders = () => []
|
|
ref.list_views = () => []
|
|
ref.get_all_content = () => contentByPathData
|
|
ref.get_note_content = (args?: Record<string, unknown>) => contentByPathData[String(args?.path ?? '')] ?? ''
|
|
ref.get_modified_files = () => []
|
|
ref.get_file_history = () => []
|
|
ref.search_vault = (args?: Record<string, unknown>) => {
|
|
const vaultPath = String(args?.vaultPath ?? '')
|
|
mockWindow.__mountedWorkspaceSearchCalls?.push(vaultPath)
|
|
const query = String(args?.query ?? '').toLowerCase()
|
|
const results = (entriesByVaultData[vaultPath] ?? [])
|
|
.filter((entry) => `${entry.title} ${entry.snippet}`.toLowerCase().includes(query))
|
|
.map((entry): SearchResultData => ({
|
|
title: entry.title,
|
|
path: entry.path,
|
|
snippet: entry.snippet,
|
|
score: 1,
|
|
note_type: entry.isA,
|
|
}))
|
|
return { results, elapsed_ms: 1 }
|
|
}
|
|
},
|
|
get() {
|
|
return ref
|
|
},
|
|
})
|
|
}, {
|
|
contentByPathData: contentByPath,
|
|
entriesByVaultData: entriesByVault,
|
|
gettingStartedPath: GETTING_STARTED_PATH,
|
|
primaryVaultPath: PRIMARY_VAULT_PATH,
|
|
researchVaultPath: RESEARCH_VAULT_PATH,
|
|
})
|
|
}
|
|
|
|
async function openQuickOpen(page: Page): Promise<void> {
|
|
await page.locator('body').click()
|
|
await sendShortcut(page, 'p', ['Control'])
|
|
await expect(page.locator(QUICK_OPEN_INPUT)).toBeVisible()
|
|
}
|
|
|
|
async function openGlobalSearch(page: Page): Promise<void> {
|
|
await page.locator('body').click()
|
|
await sendShortcut(page, 'f', ['Control', 'Shift'])
|
|
await expect(page.locator(GLOBAL_SEARCH_INPUT)).toBeVisible()
|
|
}
|
|
|
|
test('mounted workspaces share the graph across note list, quick open, and search @smoke', async ({ page }) => {
|
|
await installMountedWorkspaceMocks(page)
|
|
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
|
|
|
await page.getByText('All Notes', { exact: true }).click()
|
|
|
|
const noteList = page.getByTestId('note-list-container')
|
|
await expect(noteList.getByText('Primary Signal').first()).toBeVisible()
|
|
await expect(noteList.getByText('Research Beacon').first()).toBeVisible()
|
|
await expect(noteList.getByText('FN').first()).toBeVisible()
|
|
await expect(noteList.getByText('RL').first()).toBeVisible()
|
|
|
|
await openQuickOpen(page)
|
|
await page.locator(QUICK_OPEN_INPUT).fill('Beacon')
|
|
const quickOpen = page.getByTestId('quick-open-palette')
|
|
await expect(quickOpen.getByText('Research Beacon')).toBeVisible()
|
|
await expect(quickOpen.getByTestId('note-search-workspace-badge').filter({ hasText: 'RL' })).toBeVisible()
|
|
await page.keyboard.press('Escape')
|
|
|
|
await openGlobalSearch(page)
|
|
await page.locator(GLOBAL_SEARCH_INPUT).fill(SEARCH_NEEDLE)
|
|
await expect(page.getByText('Primary Signal')).toBeVisible({ timeout: 5_000 })
|
|
await expect(page.getByText('Research Beacon')).toBeVisible()
|
|
await expect(page.getByTestId('search-result-workspace-badge').filter({ hasText: 'FN' })).toBeVisible()
|
|
await expect(page.getByTestId('search-result-workspace-badge').filter({ hasText: 'RL' })).toBeVisible()
|
|
|
|
const searchCalls = await page.evaluate(() => (window as MockWindow).__mountedWorkspaceSearchCalls ?? [])
|
|
expect(new Set(searchCalls)).toEqual(new Set([PRIMARY_VAULT_PATH, RESEARCH_VAULT_PATH]))
|
|
})
|