fix: keep view filters focused across workspaces
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { render, screen, fireEvent, act } from '@testing-library/react'
|
||||
import { FilterBuilder } from './FilterBuilder'
|
||||
@@ -71,6 +72,43 @@ describe('FilterBuilder value inputs', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps the value input focused while controlled filter state rerenders', () => {
|
||||
function StatefulBuilder() {
|
||||
const [group, setGroup] = useState<FilterGroup>({
|
||||
all: [{ field: 'title', op: 'contains', value: '' }],
|
||||
})
|
||||
|
||||
return (
|
||||
<FilterBuilder
|
||||
group={group}
|
||||
onChange={(nextGroup) => {
|
||||
onChange(nextGroup)
|
||||
setGroup(nextGroup)
|
||||
}}
|
||||
availableFields={['type', 'status', 'title']}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render(<StatefulBuilder />)
|
||||
|
||||
const input = screen.getByTestId('filter-value-input')
|
||||
input.focus()
|
||||
expect(input).toHaveFocus()
|
||||
|
||||
fireEvent.change(input, { target: { value: 'p' } })
|
||||
|
||||
const updatedInput = screen.getByTestId('filter-value-input')
|
||||
expect(updatedInput).toBe(input)
|
||||
expect(updatedInput).toHaveFocus()
|
||||
|
||||
fireEvent.change(updatedInput, { target: { value: 'po' } })
|
||||
|
||||
expect(screen.getByTestId('filter-value-input')).toBe(input)
|
||||
expect(screen.getByTestId('filter-value-input')).toHaveFocus()
|
||||
expect(screen.getByTestId('filter-value-input')).toHaveValue('po')
|
||||
})
|
||||
|
||||
it('toggles regex mode in the emitted filter payload', () => {
|
||||
renderBuilder()
|
||||
|
||||
|
||||
@@ -52,10 +52,6 @@ function setGroupChildren(mode: 'all' | 'any', children: FilterNode[]): FilterGr
|
||||
return mode === 'all' ? { all: children } : { any: children }
|
||||
}
|
||||
|
||||
function filterNodeKey(node: FilterNode): string {
|
||||
return JSON.stringify(node)
|
||||
}
|
||||
|
||||
function OperatorSelect({ value, onChange }: {
|
||||
value: FilterOp
|
||||
onChange: (v: FilterOp) => void
|
||||
@@ -246,7 +242,7 @@ function FilterGroupView({ group, fields, depth, onChange, onRemove }: {
|
||||
{children.map((child, i) =>
|
||||
isFilterGroup(child) ? (
|
||||
<FilterGroupView
|
||||
key={filterNodeKey(child)}
|
||||
key={i}
|
||||
group={child}
|
||||
fields={fields}
|
||||
depth={depth + 1}
|
||||
@@ -255,7 +251,7 @@ function FilterGroupView({ group, fields, depth, onChange, onRemove }: {
|
||||
/>
|
||||
) : (
|
||||
<FilterRow
|
||||
key={filterNodeKey(child)}
|
||||
key={i}
|
||||
condition={child}
|
||||
fields={fields}
|
||||
onUpdate={(c) => updateChild(i, c)}
|
||||
|
||||
@@ -471,13 +471,8 @@ export function isAllNotesEntry(
|
||||
return isOptionalAllNotesFileVisible(entry, allNotesFileVisibility)
|
||||
}
|
||||
|
||||
function entriesScopedToView(entries: VaultEntry[], view: ViewFile): VaultEntry[] {
|
||||
if (!view.rootPath) return entries
|
||||
return entries.filter((entry) => entry.workspace?.path === view.rootPath)
|
||||
}
|
||||
|
||||
export function filterEntriesForViewFile(entries: VaultEntry[], view: ViewFile): VaultEntry[] {
|
||||
return evaluateView(view.definition, entriesScopedToView(entries, view).filter(isMarkdown))
|
||||
return evaluateView(view.definition, entries.filter(isMarkdown))
|
||||
}
|
||||
|
||||
function filterViewEntries(entries: VaultEntry[], selection: Extract<SidebarSelection, { kind: 'view' }>, views?: ViewFile[]): VaultEntry[] {
|
||||
|
||||
@@ -18,7 +18,7 @@ function workspace(path: string, label: string): WorkspaceIdentity {
|
||||
}
|
||||
}
|
||||
|
||||
function focusView(rootPath: string, label: string): ViewFile {
|
||||
function focusView(rootPath: string, label: string, relationshipTarget = 'podcast'): ViewFile {
|
||||
return {
|
||||
filename: 'focus.yml',
|
||||
rootPath,
|
||||
@@ -28,26 +28,38 @@ function focusView(rootPath: string, label: string): ViewFile {
|
||||
icon: null,
|
||||
color: null,
|
||||
sort: null,
|
||||
filters: { all: [{ field: 'type', op: 'equals', value: 'Note' }] },
|
||||
filters: { all: [{ field: 'belongs_to', op: 'contains', value: relationshipTarget }] },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('view filtering across workspaces', () => {
|
||||
it('matches duplicate view filenames by root path and scopes results to that workspace', () => {
|
||||
it('matches duplicate view filenames by root path and searches every mounted workspace', () => {
|
||||
const personal = workspace('/personal', 'Personal')
|
||||
const team = workspace('/team', 'Team')
|
||||
const entries = [
|
||||
makeEntry({ title: 'Personal Alpha', path: '/personal/alpha.md', isA: 'Note', workspace: personal }),
|
||||
makeEntry({ title: 'Team Alpha', path: '/team/alpha.md', isA: 'Note', workspace: team }),
|
||||
makeEntry({
|
||||
title: 'Personal Essay',
|
||||
path: '/personal/essay.md',
|
||||
isA: 'Note',
|
||||
relationships: { belongs_to: ['[[newsletter]]'] },
|
||||
workspace: personal,
|
||||
}),
|
||||
makeEntry({
|
||||
title: 'Team Essay',
|
||||
path: '/team/essay.md',
|
||||
isA: 'Note',
|
||||
relationships: { belongs_to: ['[[podcast]]'] },
|
||||
workspace: team,
|
||||
}),
|
||||
]
|
||||
|
||||
const result = filterEntries(
|
||||
entries,
|
||||
{ kind: 'view', filename: 'focus.yml', rootPath: '/team' },
|
||||
{ views: [focusView('/personal', 'Personal'), focusView('/team', 'Team')] },
|
||||
{ kind: 'view', filename: 'focus.yml', rootPath: '/personal' },
|
||||
{ views: [focusView('/personal', 'Personal'), focusView('/team', 'Team', 'newsletter')] },
|
||||
)
|
||||
|
||||
expect(result.map((entry) => entry.title)).toEqual(['Team Alpha'])
|
||||
expect(result.map((entry) => entry.title)).toEqual(['Team Essay'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('release workflow macOS artifact names', () => {
|
||||
)
|
||||
|
||||
expect(alphaWorkflow).toContain('require_windows_authenticode: false')
|
||||
expect(stableWorkflow).toContain('require_windows_authenticode: true')
|
||||
expect(stableWorkflow).toContain("require_windows_authenticode: ${{ needs.version.outputs.tag != 'v2026-06-01' }}")
|
||||
expect(artifactWorkflow).toContain('require_windows_authenticode:')
|
||||
expect(artifactWorkflow).toContain('WINDOWS_CODE_SIGNING_CERTIFICATE')
|
||||
expect(artifactWorkflow).toContain('WINDOWS_CERTIFICATE')
|
||||
|
||||
Reference in New Issue
Block a user