Skip to content

Commit

Permalink
test: improve test coverage (#4260)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S committed Mar 1, 2023
1 parent fc31f09 commit 7a47e72
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 113 deletions.
149 changes: 74 additions & 75 deletions packages/cspell-lib/api/api.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/// <reference types="node" />
import { Glob, CSpellSettingsWithSourceTrace, TextOffset, TextDocumentOffset, AdvancedCSpellSettingsWithSourceTrace, Parser, DictionaryDefinitionInline, DictionaryDefinitionPreferred, DictionaryDefinitionAugmented, DictionaryDefinitionCustom, PnPSettings, ImportFileRef, CSpellUserSettings, Issue, MappedText, ParsedText, LocaleId, CSpellSettings } from '@cspell/cspell-types';
import { Glob, CSpellSettingsWithSourceTrace, TextOffset, TextDocumentOffset, AdvancedCSpellSettingsWithSourceTrace, Parser, DictionaryDefinitionInline, DictionaryDefinitionPreferred, DictionaryDefinitionAugmented, DictionaryDefinitionCustom, PnPSettings, ImportFileRef, CSpellUserSettings, Issue, LocaleId, CSpellSettings, MappedText, ParsedText } from '@cspell/cspell-types';
export * from '@cspell/cspell-types';
import { WeightMap } from 'cspell-trie-lib';
export { CompoundWordsMethod } from 'cspell-trie-lib';
import { CachingDictionary, SpellingDictionaryCollection, SuggestOptions, SuggestionResult } from 'cspell-dictionary';
import { SuggestOptions, SuggestionResult, CachingDictionary, SpellingDictionaryCollection } from 'cspell-dictionary';
export { SpellingDictionary, SpellingDictionaryCollection, SuggestOptions, SuggestionCollector, SuggestionResult, createSpellingDictionary, createCollection as createSpellingDictionaryCollection } from 'cspell-dictionary';
export { asyncIterableToArray, readFile, readFileSync, writeToFile, writeToFileIterable, writeToFileIterableP } from 'cspell-io';

Expand Down Expand Up @@ -554,6 +554,75 @@ declare class SpellingDictionaryLoadError extends Error {
}
declare function isSpellingDictionaryLoadError(e: Error): e is SpellingDictionaryLoadError;

interface WordSuggestion extends SuggestionResult {
/**
* The suggested word adjusted to match the original case.
*/
wordAdjustedToMatchCase?: string;
}
interface SuggestedWordBase extends WordSuggestion {
/**
* dictionary names
*/
dictionaries: string[];
}
interface SuggestedWord extends SuggestedWordBase {
noSuggest: boolean;
forbidden: boolean;
}
interface SuggestionsForWordResult {
word: string;
suggestions: SuggestedWord[];
}
type FromSuggestOptions = Pick<SuggestOptions, 'numChanges' | 'numSuggestions' | 'includeTies'>;
interface SuggestionOptions extends FromSuggestOptions {
/**
* languageId to use when determining file type.
*/
languageId?: LanguageId | LanguageId[];
/**
* Locale to use.
*/
locale?: LocaleId;
/**
* Strict case and accent checking
* @default true
*/
strict?: boolean;
/**
* List of dictionaries to use. If specified, only that list of dictionaries will be used.
*/
dictionaries?: string[];
/**
* The number of suggestions to make.
* @default 8
*/
numSuggestions?: number | undefined;
/**
* Max number of changes / edits to the word to get to a suggestion matching suggestion.
* @default 4
*/
numChanges?: number | undefined;
/**
* If multiple suggestions have the same edit / change "cost", then included them even if
* it causes more than `numSuggestions` to be returned.
* @default true
*/
includeTies?: boolean | undefined;
/**
* By default we want to use the default configuration, but there are cases
* where someone might not want that.
* @default true
*/
includeDefaultConfig?: boolean;
}
declare function suggestionsForWords(words: Iterable<string> | AsyncIterable<string>, options?: SuggestionOptions, settings?: CSpellSettings): AsyncIterable<SuggestionsForWordResult>;
declare function suggestionsForWord(word: string, options?: SuggestionOptions, settings?: CSpellSettings): Promise<SuggestionsForWordResult>;
declare class SuggestionError extends Error {
readonly code: string;
constructor(message: string, code: string);
}

interface MatchRange {
startPos: number;
endPos: number;
Expand Down Expand Up @@ -681,8 +750,6 @@ declare class DocumentValidator {
private defaultParser;
private _checkParsedText;
private addPossibleError;
private catchError;
private errorCatcherWrapper;
private _parse;
private getSuggestions;
private genSuggestions;
Expand Down Expand Up @@ -715,10 +782,11 @@ interface Preparations {
localConfig: CSpellUserSettings | undefined;
localConfigFilepath: string | undefined;
}
declare function shouldCheckDocument(doc: TextDocumentRef, options: DocumentValidatorOptions, settings: CSpellUserSettings): Promise<{
interface ShouldCheckDocumentResult {
errors: Error[];
shouldCheck: boolean;
}>;
}
declare function shouldCheckDocument(doc: TextDocumentRef, options: DocumentValidatorOptions, settings: CSpellUserSettings): Promise<ShouldCheckDocumentResult>;

/**
* Annotate text with issues and include / exclude zones.
Expand Down Expand Up @@ -822,75 +890,6 @@ interface DetermineFinalDocumentSettingsResult {
*/
declare function determineFinalDocumentSettings(document: DocumentWithText, settings: CSpellUserSettings): DetermineFinalDocumentSettingsResult;

interface WordSuggestion extends SuggestionResult {
/**
* The suggested word adjusted to match the original case.
*/
wordAdjustedToMatchCase?: string;
}
interface SuggestedWordBase extends WordSuggestion {
/**
* dictionary names
*/
dictionaries: string[];
}
interface SuggestedWord extends SuggestedWordBase {
noSuggest: boolean;
forbidden: boolean;
}
interface SuggestionsForWordResult {
word: string;
suggestions: SuggestedWord[];
}
type FromSuggestOptions = Pick<SuggestOptions, 'numChanges' | 'numSuggestions' | 'includeTies'>;
interface SuggestionOptions extends FromSuggestOptions {
/**
* languageId to use when determining file type.
*/
languageId?: LanguageId | LanguageId[];
/**
* Locale to use.
*/
locale?: LocaleId;
/**
* Strict case and accent checking
* @default true
*/
strict?: boolean;
/**
* List of dictionaries to use. If specified, only that list of dictionaries will be used.
*/
dictionaries?: string[];
/**
* The number of suggestions to make.
* @default 8
*/
numSuggestions?: number | undefined;
/**
* Max number of changes / edits to the word to get to a suggestion matching suggestion.
* @default 4
*/
numChanges?: number | undefined;
/**
* If multiple suggestions have the same edit / change "cost", then included them even if
* it causes more than `numSuggestions` to be returned.
* @default true
*/
includeTies?: boolean | undefined;
/**
* By default we want to use the default configuration, but there are cases
* where someone might not want that.
* @default true
*/
includeDefaultConfig?: boolean;
}
declare function suggestionsForWords(words: Iterable<string> | AsyncIterable<string>, options?: SuggestionOptions, settings?: CSpellSettings): AsyncIterable<SuggestionsForWordResult>;
declare function suggestionsForWord(word: string, options?: SuggestionOptions, settings?: CSpellSettings): Promise<SuggestionsForWordResult>;
declare class SuggestionError extends Error {
readonly code: string;
constructor(message: string, code: string);
}

interface TraceResult {
word: string;
found: boolean;
Expand Down
88 changes: 87 additions & 1 deletion packages/cspell-lib/src/textValidation/docValidator.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import type { CSpellUserSettings } from '@cspell/cspell-types';
import assert from 'assert';
import { promises as fs } from 'fs';
import * as path from 'path';
import { pathToFileURL } from 'url';

import type { TextDocument } from '../Models/TextDocument';
import { createTextDocument } from '../Models/TextDocument';
import type { ValidationIssue } from '../Models/ValidationIssue';
import type { WordSuggestion } from '../suggestions';
import { AutoCache } from '../util/simpleCache';
import { DocumentValidator } from './docValidator';
import { toUri } from '../util/Uri';
import type { DocumentValidatorOptions } from './docValidator';
import { __testing__, DocumentValidator, shouldCheckDocument } from './docValidator';

const docCache = new AutoCache(_loadDoc, 100);
const fixturesDir = path.join(__dirname, '../../fixtures');

const oc = expect.objectContaining;
const ac = expect.arrayContaining;
const sc = expect.stringContaining;

const { sanitizeSuggestion } = __testing__;

describe('docValidator', () => {
test('DocumentValidator', () => {
Expand Down Expand Up @@ -117,6 +125,25 @@ describe('docValidator', () => {
}
);

test.each`
filename | maxDuplicateProblems | expectedIssues | expectedRawIssues
${fix('sample-with-errors.ts')} | ${undefined} | ${['dockblock', 'Helllo']} | ${undefined}
${fix('sample-with-many-errors.ts')} | ${undefined} | ${['reciever', 'naame', 'naame', 'naame', 'reciever', 'Reciever', 'naame', 'Reciever', 'naame', 'kount', 'Reciever', 'kount', 'colector', 'recievers', 'Reciever', 'recievers', 'recievers']} | ${undefined}
${fix('sample-with-many-errors.ts')} | ${1} | ${['reciever', 'naame', 'Reciever', 'kount', 'colector', 'recievers']} | ${undefined}
${fix('parser/sample.ts')} | ${1} | ${['serrors']} | ${['\\x73errors']}
${fix('sample-with-directives-errors.ts')} | ${1} | ${['disable-prev', 'ignored', 'world', 'enable-line']} | ${undefined}
`(
'checkDocumentAsync $filename $maxDuplicateProblems',
async ({ filename, maxDuplicateProblems, expectedIssues, expectedRawIssues }) => {
const doc = await loadDoc(filename);
const dVal = new DocumentValidator(doc, { generateSuggestions: false }, { maxDuplicateProblems });
const r = await dVal.checkDocumentAsync();

expect(r.map((issue) => issue.text)).toEqual(expectedIssues);
expect(extractRawText(doc.text, r)).toEqual(expectedRawIssues ?? expectedIssues);
}
);

test('updateDocumentText', () => {
// cspell:ignore foor
const expectedIssues = [
Expand All @@ -135,6 +162,52 @@ describe('docValidator', () => {
dVal.updateDocumentText(doc.text + '# cspell:ignore foor\n');
expect(dVal.checkDocument()).toEqual([]);
});

function ws(ex: Partial<WordSuggestion>): Partial<WordSuggestion> {
return ex;
}

test.each`
sug | expected
${ws({ word: 'a' })} | ${{ word: 'a' }}
${ws({ word: 'a', wordAdjustedToMatchCase: 'A' })} | ${{ word: 'a', wordAdjustedToMatchCase: 'A' }}
${ws({ word: 'a', isPreferred: undefined })} | ${{ word: 'a' }}
${ws({ word: 'a', isPreferred: false })} | ${{ word: 'a' }}
${ws({ word: 'a', isPreferred: true })} | ${{ word: 'a', isPreferred: true }}
${ws({ word: 'a', wordAdjustedToMatchCase: '', isPreferred: false })} | ${{ word: 'a' }}
${ws({ word: 'a', wordAdjustedToMatchCase: 'A', isPreferred: true })} | ${{ word: 'a', wordAdjustedToMatchCase: 'A', isPreferred: true }}
`('sanitizeSuggestion $sug', ({ sug, expected }) => {
expect(sanitizeSuggestion(sug)).toEqual(expected);
});
});

describe('shouldCheckDocument', () => {
test.each`
file | options | settings | expected
${'src/code.ts'} | ${opts()} | ${s()} | ${true}
${'src/code.ts'} | ${opts({ noConfigSearch: true })} | ${s()} | ${true}
${'src/code.ts'} | ${opts()} | ${s({ noConfigSearch: true })} | ${true}
${'src/code.ts'} | ${opts()} | ${s({ loadDefaultConfiguration: false })} | ${true}
${'src/code.ts'} | ${opts({ noConfigSearch: true })} | ${s({ loadDefaultConfiguration: false })} | ${true}
${'node_modules/mod/index.js'} | ${opts()} | ${s()} | ${false}
${'node_modules/mod/index.js'} | ${opts({ noConfigSearch: true })} | ${s()} | ${true}
${'node_modules/mod/index.js'} | ${opts()} | ${s({ noConfigSearch: true })} | ${true}
${'node_modules/mod/index.js'} | ${opts()} | ${s({ loadDefaultConfiguration: false })} | ${false}
${'node_modules/mod/index.js'} | ${opts({ noConfigSearch: true })} | ${s({ loadDefaultConfiguration: false })} | ${true}
${'node_modules/mod/index.jpg'} | ${opts()} | ${s({ loadDefaultConfiguration: false })} | ${false}
${'node_modules/mod/index.jpg'} | ${opts()} | ${s({ loadDefaultConfiguration: true })} | ${false}
${'src/code.ts'} | ${opts({ configFile: '_nf_' })} | ${s()} | ${{ errors: [oc({ message: sc('Failed to read') })], shouldCheck: true }}
`(
'shouldCheckDocument file: $file options: $options settings: $settings',
async ({ file, options, settings, expected }) => {
const uri = toUri(pathToFileURL(file));
if (typeof expected === 'boolean') {
expected = { errors: [], shouldCheck: expected };
}
console.log(uri);
expect(await shouldCheckDocument({ uri }, options, settings)).toEqual(expected);
}
);
});

function extractRawText(text: string, issues: ValidationIssue[]): string[] {
Expand Down Expand Up @@ -166,3 +239,16 @@ function fix(...fixtureFile: string[]): string {
function fixDict(...fixtureFile: string[]): string {
return fix('../dictionaries', ...fixtureFile);
}

function opts(...options: DocumentValidatorOptions[]): DocumentValidatorOptions {
return merge({}, ...options);
}

function s(...settings: CSpellUserSettings[]): CSpellUserSettings {
return merge({}, ...settings);
}

function merge<T extends object>(first: T, ...rest: T[]): T {
if (!rest.length) return first;
return { ...first, ...merge(rest[0], ...rest.slice(1)) };
}
Loading

0 comments on commit 7a47e72

Please sign in to comment.