diff --git a/src/components/FilterBuilder.test.tsx b/src/components/FilterBuilder.test.tsx index 6594a8d2..f727a2f6 100644 --- a/src/components/FilterBuilder.test.tsx +++ b/src/components/FilterBuilder.test.tsx @@ -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({ + all: [{ field: 'title', op: 'contains', value: '' }], + }) + + return ( + { + onChange(nextGroup) + setGroup(nextGroup) + }} + availableFields={['type', 'status', 'title']} + /> + ) + } + + render() + + 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() diff --git a/src/components/FilterBuilder.tsx b/src/components/FilterBuilder.tsx index 419bc3a8..da5cd607 100644 --- a/src/components/FilterBuilder.tsx +++ b/src/components/FilterBuilder.tsx @@ -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) ? ( ) : ( updateChild(i, c)} diff --git a/src/utils/noteListHelpers.ts b/src/utils/noteListHelpers.ts index 72597df0..a025f20c 100644 --- a/src/utils/noteListHelpers.ts +++ b/src/utils/noteListHelpers.ts @@ -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, views?: ViewFile[]): VaultEntry[] { diff --git a/src/utils/noteListHelpers.view-workspaces.test.ts b/src/utils/noteListHelpers.view-workspaces.test.ts index 45d39e58..ea792e69 100644 --- a/src/utils/noteListHelpers.view-workspaces.test.ts +++ b/src/utils/noteListHelpers.view-workspaces.test.ts @@ -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']) }) }) diff --git a/src/utils/releaseDownloadPage.test.ts b/src/utils/releaseDownloadPage.test.ts index 4e1f342f..474b6a86 100644 --- a/src/utils/releaseDownloadPage.test.ts +++ b/src/utils/releaseDownloadPage.test.ts @@ -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')