diff --git a/src/card.ts b/src/card.ts index bd43f6d..b69d546 100644 --- a/src/card.ts +++ b/src/card.ts @@ -35,7 +35,9 @@ export function ignoreDiagnostic(view: EditorView, diag: Diagnostic, extraEffect } const { diagnostics } = view.state.field(diagnosticsField); const filtered = diagnostics.filter(d => - !(d.from === diag.from && d.to === diag.to && d.lintKind === diag.lintKind), + diag.problemText + ? d.problemText !== diag.problemText + : !(d.from === diag.from && d.to === diag.to && d.lintKind === diag.lintKind), ); view.dispatch({ effects: [setDiagnosticsEffect.of(filtered), ...extraEffects] }); } diff --git a/tests/card.test.ts b/tests/card.test.ts index 9f92e69..e9d4f19 100644 --- a/tests/card.test.ts +++ b/tests/card.test.ts @@ -1,5 +1,89 @@ -import { describe, expect, it } from 'vitest'; -import { cardContentCSS } from '../src/card'; +import { describe, expect, it, vi } from 'vitest'; +import { EditorState } from '@codemirror/state'; +import type { EditorView } from '@codemirror/view'; +import { diagnosticsField, setDiagnosticsEffect } from '../src/decoration'; +import type { Diagnostic } from '../src/decoration'; + +vi.mock('../src/lint', () => ({ + shouldAddToDict: false, + addToDictionary: vi.fn(), +})); + +// Import after mock so the module picks up the mocked lint +const { cardContentCSS, ignoreDiagnostic } = await import('../src/card'); + +function makeDiag(overrides: Partial & { from: number; to: number }): Diagnostic { + return { + lintKind: 'Spelling', + title: 'Spelling', + messageHtml: '

Unknown word

', + problemText: 'MarkEdit', + actions: [], + ...overrides, + }; +} + +function createMockView(diagnostics: Diagnostic[]) { + const state = EditorState.create({ extensions: [diagnosticsField] }) + .update({ effects: setDiagnosticsEffect.of(diagnostics) }).state; + + let dispatched: Diagnostic[] | undefined; + const view = { + state, + dispatch(tr: { effects: unknown[] }) { + for (const e of tr.effects) { + if ((e as { is: (t: unknown) => boolean }).is(setDiagnosticsEffect)) { + dispatched = (e as { value: Diagnostic[] }).value; + } + } + }, + } as unknown as EditorView; + + return { view, getDispatched: () => dispatched }; +} + +describe('ignoreDiagnostic', () => { + it('removes all diagnostics with the same problemText', () => { + const d1 = makeDiag({ from: 0, to: 8 }); + const d2 = makeDiag({ from: 20, to: 28 }); + const d3 = makeDiag({ from: 40, to: 48 }); + const { view, getDispatched } = createMockView([d1, d2, d3]); + + ignoreDiagnostic(view, d1); + expect(getDispatched()).toHaveLength(0); + }); + + it('removes same problemText across different lintKinds', () => { + const d1 = makeDiag({ from: 0, to: 8, lintKind: 'Spelling' }); + const d2 = makeDiag({ from: 20, to: 28, lintKind: 'Style' }); + const { view, getDispatched } = createMockView([d1, d2]); + + ignoreDiagnostic(view, d1); + expect(getDispatched()).toHaveLength(0); + }); + + it('keeps diagnostics with different problemText', () => { + const d1 = makeDiag({ from: 0, to: 8, problemText: 'MarkEdit' }); + const d2 = makeDiag({ from: 20, to: 25, problemText: 'other' }); + const { view, getDispatched } = createMockView([d1, d2]); + + ignoreDiagnostic(view, d1); + const remaining = getDispatched()!; + expect(remaining).toHaveLength(1); + expect(remaining[0].problemText).toBe('other'); + }); + + it('falls back to positional match when problemText is empty', () => { + const d1 = makeDiag({ from: 0, to: 5, problemText: '', lintKind: 'Grammar' }); + const d2 = makeDiag({ from: 10, to: 15, problemText: '', lintKind: 'Grammar' }); + const { view, getDispatched } = createMockView([d1, d2]); + + ignoreDiagnostic(view, d1); + const remaining = getDispatched()!; + expect(remaining).toHaveLength(1); + expect(remaining[0].from).toBe(10); + }); +}); describe('cardContentCSS', () => { it('includes message, button, ignore, and actions styles', () => {