From 5ec1ff92290c16423a4288b48791cd352f449fe0 Mon Sep 17 00:00:00 2001 From: Ben King Date: Fri, 13 Dec 2024 16:00:14 -0500 Subject: [PATCH] Refactoring the punctuation checker test --- .../allowed-character-checker.test.ts | 664 ++++++------ .../paired-punctuation-checker.test.ts | 668 ++++++------ .../test/quotation/quotation-analyzer.test.ts | 515 +++++----- .../test/quotation/quotation-checker.test.ts | 961 +++++++++--------- .../quotation/quotation-corrector.test.ts | 338 +++--- .../test/quotation/quotation-utils.test.ts | 418 ++++---- 6 files changed, 1873 insertions(+), 1691 deletions(-) diff --git a/packages/punctuation-checker/test/allowed-character/allowed-character-checker.test.ts b/packages/punctuation-checker/test/allowed-character/allowed-character-checker.test.ts index 324fb40..73899c2 100644 --- a/packages/punctuation-checker/test/allowed-character/allowed-character-checker.test.ts +++ b/packages/punctuation-checker/test/allowed-character/allowed-character-checker.test.ts @@ -18,290 +18,241 @@ import { StandardRuleSets } from '../../src/rule-set/standard-rule-sets'; import { CharacterClassRegexBuilder } from '../../src/utils'; import { StubDocumentManager, StubSingleLineTextDocument } from '../test-utils'; -const defaultLocalizer: Localizer = new Localizer(); -defaultLocalizer.addNamespace('allowedCharacters', (_language: string) => { - return { - diagnosticMessagesByCode: { - 'disallowed-character': "The character '{{character}}' is not typically used in this language.", - }, - }; -}); -await defaultLocalizer.init(); - -// passing an empty document is fine here since we don't use getText() -const stubDiagnosticFactory: DiagnosticFactory = new DiagnosticFactory( - 'allowed-character-set-checker', - new StubSingleLineTextDocument(''), -); - -function createExpectedDiagnostic(character: string, startOffset: number, endOffset: number): Diagnostic { - return { - code: 'disallowed-character', - severity: DiagnosticSeverity.Warning, - range: { - start: { - line: 0, - character: startOffset, - }, - end: { - line: 0, - character: endOffset, - }, - }, - source: 'allowed-character-set-checker', - message: `The character '${character}' is not typically used in this language.`, - data: '', - }; -} - describe('AllowedCharacterIssueFinder tests', () => { describe('Diagnostics are created only for characters not on the whitelist', () => { describe('Simple cases', () => { - const vowelCharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist(/[aeiouAEIOU]/); - const vowelChecker = new _privateTestingClasses.AllowedCharacterIssueFinder( - defaultLocalizer, - stubDiagnosticFactory, - vowelCharacterSet, - ); - - it('produces no output for empty strings', () => { - expect(vowelChecker.produceDiagnostics('')).toEqual([]); + it('produces no output for empty strings', async () => { + const testEnv: TestEnvironment = new TestEnvironment(new CharacterRegexWhitelist(/[aeiouAEIOU]/)); + await testEnv.init(); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('')).toEqual([]); }); - it('produces Diagnostics for disallowed ASCII characters', () => { - expect(vowelChecker.produceDiagnostics('g')).toEqual([createExpectedDiagnostic('g', 0, 1)]); - expect(vowelChecker.produceDiagnostics('bV')).toEqual([ - createExpectedDiagnostic('b', 0, 1), - createExpectedDiagnostic('V', 1, 2), + it('produces Diagnostics for disallowed ASCII characters', async () => { + const testEnv: TestEnvironment = new TestEnvironment(new CharacterRegexWhitelist(/[aeiouAEIOU]/)); + await testEnv.init(); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('g')).toEqual([ + testEnv.createExpectedDiagnostic('g', 0, 1), ]); - expect(vowelChecker.produceDiagnostics('aaauoieAEOOOUI')).toEqual([]); - expect(vowelChecker.produceDiagnostics('aaautoieAEOOOMUI')).toEqual([ - createExpectedDiagnostic('t', 4, 5), - createExpectedDiagnostic('M', 13, 14), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('bV')).toEqual([ + testEnv.createExpectedDiagnostic('b', 0, 1), + testEnv.createExpectedDiagnostic('V', 1, 2), + ]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('aaauoieAEOOOUI')).toEqual([]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('aaautoieAEOOOMUI')).toEqual([ + testEnv.createExpectedDiagnostic('t', 4, 5), + testEnv.createExpectedDiagnostic('M', 13, 14), ]); }); }); describe('for basic Unicode', () => { - it('correctly handles strings with Unicode-escaped ASCII characters', () => { - const asciiVowelCharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist(/[aeiouAEIOU]/); - const asciiVowelChecker = new _privateTestingClasses.AllowedCharacterIssueFinder( - defaultLocalizer, - stubDiagnosticFactory, - asciiVowelCharacterSet, - ); + it('correctly handles strings with Unicode-escaped ASCII characters', async () => { + const testEnv: TestEnvironment = new TestEnvironment(new CharacterRegexWhitelist(/[aeiouAEIOU]/)); + await testEnv.init(); expect( - asciiVowelChecker.produceDiagnostics( + testEnv.allowedCharacterIssueFinder.produceDiagnostics( '\u0061\u0061\u0061\u0075\u006F\u0069\u0065\u0041\u0045\u004F\u004F\u004F\u0055\u0049', ), ).toEqual([]); - expect(asciiVowelChecker.produceDiagnostics('\u0062\u0056')).toEqual([ - createExpectedDiagnostic('b', 0, 1), - createExpectedDiagnostic('V', 1, 2), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('\u0062\u0056')).toEqual([ + testEnv.createExpectedDiagnostic('b', 0, 1), + testEnv.createExpectedDiagnostic('V', 1, 2), ]); }); - it('correctly handles Unicode-escaped ASCII characters in the whitelist', () => { - const unicodeVowelCharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist( - /[\u0061\u0065\u0069\u006F\u0075\u0041\u0045\u0049\u004F\u0055]/, - ); - const unicodeVowelIssueFinder = new _privateTestingClasses.AllowedCharacterIssueFinder( - defaultLocalizer, - stubDiagnosticFactory, - unicodeVowelCharacterSet, + it('correctly handles Unicode-escaped ASCII characters in the whitelist', async () => { + const testEnv: TestEnvironment = new TestEnvironment( + new CharacterRegexWhitelist(/[\u0061\u0065\u0069\u006F\u0075\u0041\u0045\u0049\u004F\u0055]/), ); + await testEnv.init(); - expect(unicodeVowelIssueFinder.produceDiagnostics('aaauoieAEOOOUI')).toEqual([]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('aaauoieAEOOOUI')).toEqual([]); expect( - unicodeVowelIssueFinder.produceDiagnostics( + testEnv.allowedCharacterIssueFinder.produceDiagnostics( '\u0061\u0061\u0061\u0075\u006F\u0069\u0065\u0041\u0045\u004F\u004F\u004F\u0055\u0049', ), ).toEqual([]); - expect(unicodeVowelIssueFinder.produceDiagnostics('\u0062\u0056')).toEqual([ - createExpectedDiagnostic('b', 0, 1), - createExpectedDiagnostic('V', 1, 2), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('\u0062\u0056')).toEqual([ + testEnv.createExpectedDiagnostic('b', 0, 1), + testEnv.createExpectedDiagnostic('V', 1, 2), ]); - expect(unicodeVowelIssueFinder.produceDiagnostics('bV')).toEqual([ - createExpectedDiagnostic('b', 0, 1), - createExpectedDiagnostic('V', 1, 2), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('bV')).toEqual([ + testEnv.createExpectedDiagnostic('b', 0, 1), + testEnv.createExpectedDiagnostic('V', 1, 2), ]); }); - it('correctly handles non-ASCII Unicode ranges in the whitelist', () => { - const nonASCIICharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist(/[\u2200-\u22FF]/); - const nonASCIICharacterIssueFinder = new _privateTestingClasses.AllowedCharacterIssueFinder( - defaultLocalizer, - stubDiagnosticFactory, - nonASCIICharacterSet, - ); + it('correctly handles non-ASCII Unicode ranges in the whitelist', async () => { + const testEnv: TestEnvironment = new TestEnvironment(new CharacterRegexWhitelist(/[\u2200-\u22FF]/)); + await testEnv.init(); - expect(nonASCIICharacterIssueFinder.produceDiagnostics('≤⊕∴∀∂∯')).toEqual([]); - expect(nonASCIICharacterIssueFinder.produceDiagnostics('\u2264\u2295\u2234\u2200\u2202\u222F')).toEqual([]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('≤⊕∴∀∂∯')).toEqual([]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('\u2264\u2295\u2234\u2200\u2202\u222F')).toEqual( + [], + ); - expect(nonASCIICharacterIssueFinder.produceDiagnostics('≤⊕∴∀A∯')).toEqual([ - createExpectedDiagnostic('A', 4, 5), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('≤⊕∴∀A∯')).toEqual([ + testEnv.createExpectedDiagnostic('A', 4, 5), ]); - expect(nonASCIICharacterIssueFinder.produceDiagnostics('≤⊕∴∀∯⨕')).toEqual([ - createExpectedDiagnostic('⨕', 5, 6), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('≤⊕∴∀∯⨕')).toEqual([ + testEnv.createExpectedDiagnostic('⨕', 5, 6), ]); }); }); describe('for complex Unicode', () => { - it('correctly handles Unicode categories in the whitelist regex', () => { - const unicodeCategoryCharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist( - /[\p{General_Category=Math_Symbol}]/u, - ); - const unicodeCategoryIssueFinder = new _privateTestingClasses.AllowedCharacterIssueFinder( - defaultLocalizer, - stubDiagnosticFactory, - unicodeCategoryCharacterSet, + it('correctly handles Unicode categories in the whitelist regex', async () => { + const testEnv: TestEnvironment = new TestEnvironment( + new CharacterRegexWhitelist(/[\p{General_Category=Math_Symbol}]/u), ); + await testEnv.init(); - expect(unicodeCategoryIssueFinder.produceDiagnostics('≤⊕∴∀∂∯')).toEqual([]); - expect(unicodeCategoryIssueFinder.produceDiagnostics('\u2264\u2295\u2234\u2200\u2202\u222F')).toEqual([]); - expect(unicodeCategoryIssueFinder.produceDiagnostics('≤⊕∴∀A∯')).toEqual([createExpectedDiagnostic('A', 4, 5)]); - expect(unicodeCategoryIssueFinder.produceDiagnostics('≤⊕∴∀∯ච')).toEqual([createExpectedDiagnostic('ච', 5, 6)]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('≤⊕∴∀∂∯')).toEqual([]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('\u2264\u2295\u2234\u2200\u2202\u222F')).toEqual( + [], + ); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('≤⊕∴∀A∯')).toEqual([ + testEnv.createExpectedDiagnostic('A', 4, 5), + ]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('≤⊕∴∀∯ච')).toEqual([ + testEnv.createExpectedDiagnostic('ච', 5, 6), + ]); }); - it('correctly handles Unicode scripts in the whitelist regex', () => { - const unicodeScriptCharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist(/[\p{Script=Hebrew}]/u); - const unicodeScriptIssueFinder = new _privateTestingClasses.AllowedCharacterIssueFinder( - defaultLocalizer, - stubDiagnosticFactory, - unicodeScriptCharacterSet, - ); + it('correctly handles Unicode scripts in the whitelist regex', async () => { + const testEnv: TestEnvironment = new TestEnvironment(new CharacterRegexWhitelist(/[\p{Script=Hebrew}]/u)); + await testEnv.init(); - expect(unicodeScriptIssueFinder.produceDiagnostics('לִינקס')).toEqual([]); - expect(unicodeScriptIssueFinder.produceDiagnostics('לִינקסקּ')).toEqual([]); - expect(unicodeScriptIssueFinder.produceDiagnostics('\u05DC\u05B4\u05D9\u05E0\u05E7\u05E1')).toEqual([]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('לִינקס')).toEqual([]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('לִינקסקּ')).toEqual([]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('\u05DC\u05B4\u05D9\u05E0\u05E7\u05E1')).toEqual( + [], + ); // Mathematical version of aleph - expect(unicodeScriptIssueFinder.produceDiagnostics('לִינקסℵ')).toEqual([createExpectedDiagnostic('ℵ', 6, 7)]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('לִינקסℵ')).toEqual([ + testEnv.createExpectedDiagnostic('ℵ', 6, 7), + ]); - expect(unicodeScriptIssueFinder.produceDiagnostics('לִxינקס')).toEqual([createExpectedDiagnostic('x', 2, 3)]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('לִxינקס')).toEqual([ + testEnv.createExpectedDiagnostic('x', 2, 3), + ]); }); describe('correctly handles surrogate pairs and characters outside the basic multilingual plane', () => { - it('handles ranges of characters with U+10000 and above', () => { - const extendedUnicodeCharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist( - /[\u{1F600}-\u{1F64F}]/u, - ); - const extendedUnicodeIssueFinder = new _privateTestingClasses.AllowedCharacterIssueFinder( - defaultLocalizer, - stubDiagnosticFactory, - extendedUnicodeCharacterSet, - ); + it('handles ranges of characters with U+10000 and above', async () => { + const testEnv: TestEnvironment = new TestEnvironment(new CharacterRegexWhitelist(/[\u{1F600}-\u{1F64F}]/u)); + await testEnv.init(); // all three of these strings should be equivalent - expect(extendedUnicodeIssueFinder.produceDiagnostics('😀😶🙅🙏')).toEqual([]); - expect(extendedUnicodeIssueFinder.produceDiagnostics('\u{1F600}\u{1F636}\u{1F645}\u{1F64F}')).toEqual([]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('😀😶🙅🙏')).toEqual([]); expect( - extendedUnicodeIssueFinder.produceDiagnostics('\uD83D\uDE00\uD83D\uDE36\uD83D\uDE45\uD83D\uDE4F'), + testEnv.allowedCharacterIssueFinder.produceDiagnostics('\u{1F600}\u{1F636}\u{1F645}\u{1F64F}'), + ).toEqual([]); + expect( + testEnv.allowedCharacterIssueFinder.produceDiagnostics('\uD83D\uDE00\uD83D\uDE36\uD83D\uDE45\uD83D\uDE4F'), ).toEqual([]); - expect(extendedUnicodeIssueFinder.produceDiagnostics('🙓😀m🚤')).toEqual([ - createExpectedDiagnostic('🙓', 0, 2), - createExpectedDiagnostic('m', 4, 5), - createExpectedDiagnostic('🚤', 5, 7), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('🙓😀m🚤')).toEqual([ + testEnv.createExpectedDiagnostic('🙓', 0, 2), + testEnv.createExpectedDiagnostic('m', 4, 5), + testEnv.createExpectedDiagnostic('🚤', 5, 7), ]); - expect(extendedUnicodeIssueFinder.produceDiagnostics('\u{1F653}\u{1F600}\u006D\u{1F6A4}')).toEqual([ - createExpectedDiagnostic('🙓', 0, 2), - createExpectedDiagnostic('m', 4, 5), - createExpectedDiagnostic('🚤', 5, 7), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('\u{1F653}\u{1F600}\u006D\u{1F6A4}')).toEqual([ + testEnv.createExpectedDiagnostic('🙓', 0, 2), + testEnv.createExpectedDiagnostic('m', 4, 5), + testEnv.createExpectedDiagnostic('🚤', 5, 7), ]); - expect(extendedUnicodeIssueFinder.produceDiagnostics('\uD83D\uDE53\uD83D\uDE00m\uD83D\uDEA4')).toEqual([ - createExpectedDiagnostic('🙓', 0, 2), - createExpectedDiagnostic('m', 4, 5), - createExpectedDiagnostic('🚤', 5, 7), + expect( + testEnv.allowedCharacterIssueFinder.produceDiagnostics('\uD83D\uDE53\uD83D\uDE00m\uD83D\uDEA4'), + ).toEqual([ + testEnv.createExpectedDiagnostic('🙓', 0, 2), + testEnv.createExpectedDiagnostic('m', 4, 5), + testEnv.createExpectedDiagnostic('🚤', 5, 7), ]); }); - it('correctly handles explicit non-BMP characters', () => { - const explicitExtendedUnicodeCharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist( - /[😀😁😂😃😴😵😶😷🙄🙅🙆🙇🙌🙍🙎🙏]/u, - ); - const explicitExtendedUnicodeIssueFinder = new _privateTestingClasses.AllowedCharacterIssueFinder( - defaultLocalizer, - stubDiagnosticFactory, - explicitExtendedUnicodeCharacterSet, + it('correctly handles explicit non-BMP characters', async () => { + const testEnv: TestEnvironment = new TestEnvironment( + new CharacterRegexWhitelist(/[😀😁😂😃😴😵😶😷🙄🙅🙆🙇🙌🙍🙎🙏]/u), ); + await testEnv.init(); - expect(explicitExtendedUnicodeIssueFinder.produceDiagnostics('😀😶🙅🙏')).toEqual([]); - expect(explicitExtendedUnicodeIssueFinder.produceDiagnostics('\u{1F600}\u{1F636}\u{1F645}\u{1F64F}')).toEqual( - [], - ); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('😀😶🙅🙏')).toEqual([]); + expect( + testEnv.allowedCharacterIssueFinder.produceDiagnostics('\u{1F600}\u{1F636}\u{1F645}\u{1F64F}'), + ).toEqual([]); expect( - explicitExtendedUnicodeIssueFinder.produceDiagnostics('\uD83D\uDE00\uD83D\uDE36\uD83D\uDE45\uD83D\uDE4F'), + testEnv.allowedCharacterIssueFinder.produceDiagnostics('\uD83D\uDE00\uD83D\uDE36\uD83D\uDE45\uD83D\uDE4F'), ).toEqual([]); - expect(explicitExtendedUnicodeIssueFinder.produceDiagnostics('🙓😀m🚤😎')).toEqual([ - createExpectedDiagnostic('🙓', 0, 2), - createExpectedDiagnostic('m', 4, 5), - createExpectedDiagnostic('🚤', 5, 7), - createExpectedDiagnostic('😎', 7, 9), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('🙓😀m🚤😎')).toEqual([ + testEnv.createExpectedDiagnostic('🙓', 0, 2), + testEnv.createExpectedDiagnostic('m', 4, 5), + testEnv.createExpectedDiagnostic('🚤', 5, 7), + testEnv.createExpectedDiagnostic('😎', 7, 9), ]); expect( - explicitExtendedUnicodeIssueFinder.produceDiagnostics('\u{1F653}\u{1F600}\u006D\u{1F6A4}\u{1F60E}'), + testEnv.allowedCharacterIssueFinder.produceDiagnostics('\u{1F653}\u{1F600}\u006D\u{1F6A4}\u{1F60E}'), ).toEqual([ - createExpectedDiagnostic('🙓', 0, 2), - createExpectedDiagnostic('m', 4, 5), - createExpectedDiagnostic('🚤', 5, 7), - createExpectedDiagnostic('😎', 7, 9), + testEnv.createExpectedDiagnostic('🙓', 0, 2), + testEnv.createExpectedDiagnostic('m', 4, 5), + testEnv.createExpectedDiagnostic('🚤', 5, 7), + testEnv.createExpectedDiagnostic('😎', 7, 9), ]); expect( - explicitExtendedUnicodeIssueFinder.produceDiagnostics('\uD83D\uDE53\uD83D\uDE00m\uD83D\uDEA4\uD83D\uDE0E'), + testEnv.allowedCharacterIssueFinder.produceDiagnostics('\uD83D\uDE53\uD83D\uDE00m\uD83D\uDEA4\uD83D\uDE0E'), ).toEqual([ - createExpectedDiagnostic('🙓', 0, 2), - createExpectedDiagnostic('m', 4, 5), - createExpectedDiagnostic('🚤', 5, 7), - createExpectedDiagnostic('😎', 7, 9), + testEnv.createExpectedDiagnostic('🙓', 0, 2), + testEnv.createExpectedDiagnostic('m', 4, 5), + testEnv.createExpectedDiagnostic('🚤', 5, 7), + testEnv.createExpectedDiagnostic('😎', 7, 9), ]); }); - it('correctly handles ranges expressed with surrogate pairs', () => { - const surrogatePairUnicodeCharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist( - /[\uD83D\uDE00-\uD83D\uDE4F]/u, - ); - const surrogatePairIssueFinder = new _privateTestingClasses.AllowedCharacterIssueFinder( - defaultLocalizer, - stubDiagnosticFactory, - surrogatePairUnicodeCharacterSet, + it('correctly handles ranges expressed with surrogate pairs', async () => { + const testEnv: TestEnvironment = new TestEnvironment( + new CharacterRegexWhitelist(/[\uD83D\uDE00-\uD83D\uDE4F]/u), ); + await testEnv.init(); - expect(surrogatePairIssueFinder.produceDiagnostics('😀😶🙅🙏')).toEqual([]); - expect(surrogatePairIssueFinder.produceDiagnostics('\u{1F600}\u{1F636}\u{1F645}\u{1F64F}')).toEqual([]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('😀😶🙅🙏')).toEqual([]); expect( - surrogatePairIssueFinder.produceDiagnostics('\uD83D\uDE00\uD83D\uDE36\uD83D\uDE45\uD83D\uDE4F'), + testEnv.allowedCharacterIssueFinder.produceDiagnostics('\u{1F600}\u{1F636}\u{1F645}\u{1F64F}'), + ).toEqual([]); + expect( + testEnv.allowedCharacterIssueFinder.produceDiagnostics('\uD83D\uDE00\uD83D\uDE36\uD83D\uDE45\uD83D\uDE4F'), ).toEqual([]); - expect(surrogatePairIssueFinder.produceDiagnostics('🙓😀m🚤')).toEqual([ - createExpectedDiagnostic('🙓', 0, 2), - createExpectedDiagnostic('m', 4, 5), - createExpectedDiagnostic('🚤', 5, 7), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('🙓😀m🚤')).toEqual([ + testEnv.createExpectedDiagnostic('🙓', 0, 2), + testEnv.createExpectedDiagnostic('m', 4, 5), + testEnv.createExpectedDiagnostic('🚤', 5, 7), ]); - expect(surrogatePairIssueFinder.produceDiagnostics('\u{1F653}\u{1F600}\u006D\u{1F6A4}')).toEqual([ - createExpectedDiagnostic('🙓', 0, 2), - createExpectedDiagnostic('m', 4, 5), - createExpectedDiagnostic('🚤', 5, 7), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('\u{1F653}\u{1F600}\u006D\u{1F6A4}')).toEqual([ + testEnv.createExpectedDiagnostic('🙓', 0, 2), + testEnv.createExpectedDiagnostic('m', 4, 5), + testEnv.createExpectedDiagnostic('🚤', 5, 7), ]); - expect(surrogatePairIssueFinder.produceDiagnostics('\uD83D\uDE53\uD83D\uDE00m\uD83D\uDEA4')).toEqual([ - createExpectedDiagnostic('🙓', 0, 2), - createExpectedDiagnostic('m', 4, 5), - createExpectedDiagnostic('🚤', 5, 7), + expect( + testEnv.allowedCharacterIssueFinder.produceDiagnostics('\uD83D\uDE53\uD83D\uDE00m\uD83D\uDEA4'), + ).toEqual([ + testEnv.createExpectedDiagnostic('🙓', 0, 2), + testEnv.createExpectedDiagnostic('m', 4, 5), + testEnv.createExpectedDiagnostic('🚤', 5, 7), ]); // invalid surrogate pairs - expect(surrogatePairIssueFinder.produceDiagnostics('\uD83D')).toEqual([ - createExpectedDiagnostic('\uD83D', 0, 1), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('\uD83D')).toEqual([ + testEnv.createExpectedDiagnostic('\uD83D', 0, 1), ]); - expect(surrogatePairIssueFinder.produceDiagnostics('\uD83D\u0061')).toEqual([ - createExpectedDiagnostic('\uD83D', 0, 1), - createExpectedDiagnostic('a', 1, 2), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('\uD83D\u0061')).toEqual([ + testEnv.createExpectedDiagnostic('\uD83D', 0, 1), + testEnv.createExpectedDiagnostic('a', 1, 2), ]); - expect(surrogatePairIssueFinder.produceDiagnostics('\uD83D\u{1F600}')).toEqual([ - createExpectedDiagnostic('\uD83D', 0, 1), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics('\uD83D\u{1F600}')).toEqual([ + testEnv.createExpectedDiagnostic('\uD83D', 0, 1), ]); }); }); @@ -313,35 +264,29 @@ const stubDocumentManager: StubDocumentManager = new StubDocumentManager(new Tex describe('AllowedCharacterChecker tests', () => { it("doesn't provide any DiagnosticFixes", async () => { - const basicCharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist(/[a-zA-Z ]/); - const basicCharacterChecker: AllowedCharacterChecker = new AllowedCharacterChecker( - defaultLocalizer, - stubDocumentManager, - basicCharacterSet, - ); + const testEnv: TestEnvironment = new TestEnvironment(new CharacterRegexWhitelist(/[a-zA-Z ]/)); + await testEnv.init(); expect( - await basicCharacterChecker.getDiagnosticFixes('Hello, _there!&%', createExpectedDiagnostic(',', 5, 6)), + await testEnv.allowedCharacterChecker.getDiagnosticFixes( + 'Hello, _there!&%', + testEnv.createExpectedDiagnostic(',', 5, 6), + ), ).toEqual([]); }); it('initializes its own namespace in the localizer', async () => { - const localizer: Localizer = new Localizer(); - const basicCharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist(/[a-zA-Z ]/); - const basicCharacterChecker: AllowedCharacterChecker = new AllowedCharacterChecker( - localizer, - stubDocumentManager, - basicCharacterSet, - ); - await basicCharacterChecker.init(); - await localizer.init(); + const testEnv: TestEnvironment = new TestEnvironment(new CharacterRegexWhitelist(/[a-zA-Z ]/)); + await testEnv.init(); - expect(await basicCharacterChecker.getDiagnostics('Hello^there')).toEqual([createExpectedDiagnostic('^', 5, 6)]); + expect(await testEnv.allowedCharacterChecker.getDiagnostics('Hello^there')).toEqual([ + testEnv.createExpectedDiagnostic('^', 5, 6), + ]); }); it('gets its messages from the localizer', async () => { - const localizer: Localizer = new Localizer(); - localizer.addNamespace('allowedCharacters', (language: string) => { + const customLocalizer: Localizer = new Localizer(); + customLocalizer.addNamespace('allowedCharacters', (language: string) => { if (language === 'en') { return { diagnosticMessagesByCode: { @@ -356,16 +301,11 @@ describe('AllowedCharacterChecker tests', () => { }; } }); - const basicCharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist(/[a-zA-Z ]/); - const basicCharacterChecker: AllowedCharacterChecker = new AllowedCharacterChecker( - localizer, - stubDocumentManager, - basicCharacterSet, - ); - await basicCharacterChecker.init(); - await localizer.init(); - expect(await basicCharacterChecker.getDiagnostics('Hello^there')).toEqual([ + const testEnv: TestEnvironment = new TestEnvironment(new CharacterRegexWhitelist(/[a-zA-Z ]/), customLocalizer); + await testEnv.init(); + + expect(await testEnv.allowedCharacterChecker.getDiagnostics('Hello^there')).toEqual([ { code: 'disallowed-character', severity: DiagnosticSeverity.Warning, @@ -385,8 +325,8 @@ describe('AllowedCharacterChecker tests', () => { }, ]); - await localizer.changeLanguage('es'); - expect(await basicCharacterChecker.getDiagnostics('Hello^there')).toEqual([ + await customLocalizer.changeLanguage('es'); + expect(await testEnv.allowedCharacterChecker.getDiagnostics('Hello^there')).toEqual([ { code: 'disallowed-character', severity: DiagnosticSeverity.Warning, @@ -410,39 +350,50 @@ describe('AllowedCharacterChecker tests', () => { describe('integration tests', () => { describe('between AllowedCharacterChecker and AllowedCharacterIssueFinder', () => { - const basicCharacterSet: AllowedCharacterSet = new CharacterRegexWhitelist(/[a-zA-Z ]/); - const basicCharacterChecker: AllowedCharacterChecker = new AllowedCharacterChecker( - defaultLocalizer, - stubDocumentManager, - basicCharacterSet, - ); - it('correctly handles ASCII characters', async () => { - expect(await basicCharacterChecker.getDiagnostics('hello there')).toEqual([]); - expect(await basicCharacterChecker.getDiagnostics('hello there!')).toEqual([ - createExpectedDiagnostic('!', 11, 12), + const testEnv: TestEnvironment = new TestEnvironment(new CharacterRegexWhitelist(/[a-zA-Z ]/)); + await testEnv.init(); + + expect(await testEnv.allowedCharacterChecker.getDiagnostics('hello there')).toEqual([]); + expect(await testEnv.allowedCharacterChecker.getDiagnostics('hello there!')).toEqual([ + testEnv.createExpectedDiagnostic('!', 11, 12), ]); }); it('correctly handles Unicode characters', async () => { - expect(await basicCharacterChecker.getDiagnostics('h𝕖llo ther𝖊')).toEqual([ - createExpectedDiagnostic('𝕖', 1, 3), - createExpectedDiagnostic('𝖊', 11, 13), + const testEnv: TestEnvironment = new TestEnvironment(new CharacterRegexWhitelist(/[a-zA-Z ]/)); + await testEnv.init(); + + expect(await testEnv.allowedCharacterChecker.getDiagnostics('h𝕖llo ther𝖊')).toEqual([ + testEnv.createExpectedDiagnostic('𝕖', 1, 3), + testEnv.createExpectedDiagnostic('𝖊', 11, 13), ]); }); }); describe('with the standard English rule set', () => { - const standardEnglishCharacterSet = StandardRuleSets.English; - const standardEnglishCharacterChecker: DiagnosticProvider = - standardEnglishCharacterSet.createSelectedDiagnosticProviders(defaultLocalizer, stubDocumentManager, [ - RuleType.AllowedCharacters, - ])[0]; - it('produces no output for empty strings', async () => { + const localizer: Localizer = new Localizer(); + const standardEnglishCharacterSet = StandardRuleSets.English; + const standardEnglishCharacterChecker: DiagnosticProvider = + standardEnglishCharacterSet.createSelectedDiagnosticProviders(localizer, stubDocumentManager, [ + RuleType.AllowedCharacters, + ])[0]; + await standardEnglishCharacterChecker.init(); + await localizer.init(); + expect(await standardEnglishCharacterChecker.getDiagnostics('')).toEqual([]); }); it('produces no output for standard English allowed characters', async () => { + const localizer: Localizer = new Localizer(); + const standardEnglishCharacterSet = StandardRuleSets.English; + const standardEnglishCharacterChecker: DiagnosticProvider = + standardEnglishCharacterSet.createSelectedDiagnosticProviders(localizer, stubDocumentManager, [ + RuleType.AllowedCharacters, + ])[0]; + await standardEnglishCharacterChecker.init(); + await localizer.init(); + expect(await standardEnglishCharacterChecker.getDiagnostics('Four score, and seven(!!!) years ago?')).toEqual([]); expect(await standardEnglishCharacterChecker.getDiagnostics('"4 Sc0r3;“ \tand\r’ 7 YRS ago: \n')).toEqual([]); expect(await standardEnglishCharacterChecker.getDiagnostics('ALL UPPERCASE TEXT')).toEqual([]); @@ -451,61 +402,65 @@ describe('integration tests', () => { }); it('produces Diagnostics for disallowed ASCII characters', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardEnglishCharacters(); + + const localizer: Localizer = new Localizer(); + const standardEnglishCharacterSet = StandardRuleSets.English; + const standardEnglishCharacterChecker: DiagnosticProvider = + standardEnglishCharacterSet.createSelectedDiagnosticProviders(localizer, stubDocumentManager, [ + RuleType.AllowedCharacters, + ])[0]; + await standardEnglishCharacterChecker.init(); + await localizer.init(); + expect(await standardEnglishCharacterChecker.getDiagnostics('&{+$')).toEqual([ - createExpectedDiagnostic('&', 0, 1), - createExpectedDiagnostic('{', 1, 2), - createExpectedDiagnostic('+', 2, 3), - createExpectedDiagnostic('$', 3, 4), + testEnv.createExpectedDiagnostic('&', 0, 1), + testEnv.createExpectedDiagnostic('{', 1, 2), + testEnv.createExpectedDiagnostic('+', 2, 3), + testEnv.createExpectedDiagnostic('$', 3, 4), ]); }); it('produces Diagnostics for disallowed Unicode characters', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardEnglishCharacters(); + + const localizer: Localizer = new Localizer(); + const standardEnglishCharacterSet = StandardRuleSets.English; + const standardEnglishCharacterChecker: DiagnosticProvider = + standardEnglishCharacterSet.createSelectedDiagnosticProviders(localizer, stubDocumentManager, [ + RuleType.AllowedCharacters, + ])[0]; + await standardEnglishCharacterChecker.init(); + await localizer.init(); + // confusable characters expect(await standardEnglishCharacterChecker.getDiagnostics('аᖯ𝐜𝖽e')).toEqual([ - createExpectedDiagnostic('а', 0, 1), - createExpectedDiagnostic('ᖯ', 1, 2), - createExpectedDiagnostic('𝐜', 2, 4), - createExpectedDiagnostic('𝖽', 4, 6), - createExpectedDiagnostic('e', 6, 7), + testEnv.createExpectedDiagnostic('а', 0, 1), + testEnv.createExpectedDiagnostic('ᖯ', 1, 2), + testEnv.createExpectedDiagnostic('𝐜', 2, 4), + testEnv.createExpectedDiagnostic('𝖽', 4, 6), + testEnv.createExpectedDiagnostic('e', 6, 7), ]); // other seemingly plausible characters expect(await standardEnglishCharacterChecker.getDiagnostics('‟″á×ꭗfi')).toEqual([ - createExpectedDiagnostic('‟', 0, 1), - createExpectedDiagnostic('″', 1, 2), - createExpectedDiagnostic('á', 2, 3), - createExpectedDiagnostic('×', 3, 4), - createExpectedDiagnostic('ꭗ', 4, 5), - createExpectedDiagnostic('fi', 5, 6), + testEnv.createExpectedDiagnostic('‟', 0, 1), + testEnv.createExpectedDiagnostic('″', 1, 2), + testEnv.createExpectedDiagnostic('á', 2, 3), + testEnv.createExpectedDiagnostic('×', 3, 4), + testEnv.createExpectedDiagnostic('ꭗ', 4, 5), + testEnv.createExpectedDiagnostic('fi', 5, 6), ]); }); }); }); describe('ScriptureDocument tests', () => { - const stylesheet = new UsfmStylesheet('usfm.sty'); - const documentFactory = new UsfmDocumentFactory(stylesheet); - const standardAllowedCharacterSet = new CharacterRegexWhitelist( - new CharacterClassRegexBuilder() - .addRange('A', 'Z') - .addRange('a', 'z') - .addRange('0', '9') - .addCharacters(['.', ',', '?', '/', '\\', ':', ';', '(', ')', '-', '—', '!']) - .addCharacters(['"', "'", '\u2018', '\u2019', '\u201C', '\u201D']) - .addCharacters([' ', '\r', '\n', '\t']) - .build(), - ); - const allowedCharacterIssueFinder = new _privateTestingClasses.AllowedCharacterIssueFinder( - defaultLocalizer, - stubDiagnosticFactory, - standardAllowedCharacterSet, - ); - - it('produces no errors for well-formed text', () => { - const scriptureDocument: ScriptureDocument = documentFactory.create( - 'test-uri', - 'usfm', - 1, + it('produces no errors for well-formed text', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardEnglishCharacters(); + await testEnv.init(); + + const scriptureDocument: ScriptureDocument = testEnv.createScriptureDocument( `\\id GEN \\toc3 Gen \\toc2 Genesis @@ -518,14 +473,14 @@ describe('ScriptureDocument tests', () => { \\v 1 Now Abraham was old, well advanced in years. And the Lord had blessed Abraham in all things.`, ); - expect(allowedCharacterIssueFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([]); + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([]); }); - it('identifies disallowed characters in verse text', () => { - const scriptureDocument: ScriptureDocument = documentFactory.create( - 'test-uri', - 'usfm', - 1, + it('identifies disallowed characters in verse text', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardEnglishCharacters(); + await testEnv.init(); + + const scriptureDocument: ScriptureDocument = testEnv.createScriptureDocument( `\\id GEN \\toc3 Gen \\toc2 Genesis @@ -538,16 +493,16 @@ describe('ScriptureDocument tests', () => { \\v 1 The servant% said to him, “Perhaps the woman may not be ‘willing to follow me to this land. Must I then take your son back to the land from which you came?”`, ); - expect(allowedCharacterIssueFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([ - createExpectedDiagnostic('%', 171, 172), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([ + testEnv.createExpectedDiagnostic('%', 171, 172), ]); }); - it('identifies disallowed characters that occur in non-verse portions', () => { - const scriptureDocument: ScriptureDocument = documentFactory.create( - 'test-uri', - 'usfm', - 1, + it('identifies disallowed characters that occur in non-verse portions', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardEnglishCharacters(); + await testEnv.init(); + + const scriptureDocument: ScriptureDocument = testEnv.createScriptureDocument( `\\id GEN \\toc3 G*n \\toc2 Gene$is @@ -560,12 +515,109 @@ describe('ScriptureDocument tests', () => { \\v 1 The servant said to him, “Perhaps the woman may not be willing to follow me to this land. Must I then take your son back to the land from which you came?”`, ); - expect(allowedCharacterIssueFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([ - createExpectedDiagnostic('*', 21, 22), - createExpectedDiagnostic('$', 40, 41), - createExpectedDiagnostic('@', 80, 81), - createExpectedDiagnostic('|', 122, 123), - createExpectedDiagnostic('&', 128, 129), + expect(testEnv.allowedCharacterIssueFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([ + testEnv.createExpectedDiagnostic('*', 21, 22), + testEnv.createExpectedDiagnostic('$', 40, 41), + testEnv.createExpectedDiagnostic('@', 80, 81), + testEnv.createExpectedDiagnostic('|', 122, 123), + testEnv.createExpectedDiagnostic('&', 128, 129), ]); }); }); + +class TestEnvironment { + private readonly allowedCharacterIssueFinderLocalizer: Localizer; + private readonly allowedCharacterCheckerLocalizer: Localizer; + + readonly allowedCharacterChecker: AllowedCharacterChecker; + readonly allowedCharacterIssueFinder; + + private readonly scriptureDocumentFactory: UsfmDocumentFactory; + + constructor( + private readonly allowedCharacterSet: AllowedCharacterSet, + private readonly customLocalizer?: Localizer, + ) { + this.allowedCharacterIssueFinderLocalizer = this.createDefaultLocalizer(); + + const stubDiagnosticFactory: DiagnosticFactory = new DiagnosticFactory( + 'allowed-character-set-checker', + new StubSingleLineTextDocument(''), + ); + + this.allowedCharacterIssueFinder = new _privateTestingClasses.AllowedCharacterIssueFinder( + this.customLocalizer ?? this.allowedCharacterIssueFinderLocalizer, + stubDiagnosticFactory, + allowedCharacterSet, + ); + + this.allowedCharacterCheckerLocalizer = new Localizer(); + + this.allowedCharacterChecker = new AllowedCharacterChecker( + this.customLocalizer ?? this.allowedCharacterCheckerLocalizer, + stubDocumentManager, + allowedCharacterSet, + ); + + const stylesheet = new UsfmStylesheet('usfm.sty'); + this.scriptureDocumentFactory = new UsfmDocumentFactory(stylesheet); + } + + private createDefaultLocalizer(): Localizer { + const defaultLocalizer: Localizer = new Localizer(); + defaultLocalizer.addNamespace('allowedCharacters', (_language: string) => { + return { + diagnosticMessagesByCode: { + 'disallowed-character': "The character '{{character}}' is not typically used in this language.", + }, + }; + }); + return defaultLocalizer; + } + + public async init(): Promise { + await this.allowedCharacterChecker.init(); + await this.allowedCharacterCheckerLocalizer.init(); + await this.allowedCharacterIssueFinderLocalizer.init(); + await this.customLocalizer?.init(); + } + + static createWithStandardEnglishCharacters(): TestEnvironment { + return new TestEnvironment( + new CharacterRegexWhitelist( + new CharacterClassRegexBuilder() + .addRange('A', 'Z') + .addRange('a', 'z') + .addRange('0', '9') + .addCharacters(['.', ',', '?', '/', '\\', ':', ';', '(', ')', '-', '—', '!']) + .addCharacters(['"', "'", '\u2018', '\u2019', '\u201C', '\u201D']) + .addCharacters([' ', '\r', '\n', '\t']) + .build(), + ), + ); + } + + createExpectedDiagnostic(character: string, startOffset: number, endOffset: number): Diagnostic { + return { + code: 'disallowed-character', + severity: DiagnosticSeverity.Warning, + range: { + start: { + line: 0, + character: startOffset, + }, + end: { + line: 0, + character: endOffset, + }, + }, + source: 'allowed-character-set-checker', + message: `The character '${character}' is not typically used in this language.`, + data: '', + }; + } + + createScriptureDocument(usfm: string): ScriptureDocument { + return this.scriptureDocumentFactory.create('test-uri', 'usfm', 1, usfm); + } +} diff --git a/packages/punctuation-checker/test/paired-punctuation/paired-punctuation-checker.test.ts b/packages/punctuation-checker/test/paired-punctuation/paired-punctuation-checker.test.ts index 8a6fe0d..a0b1cb5 100644 --- a/packages/punctuation-checker/test/paired-punctuation/paired-punctuation-checker.test.ts +++ b/packages/punctuation-checker/test/paired-punctuation/paired-punctuation-checker.test.ts @@ -11,70 +11,22 @@ import { PairedPunctuationConfig } from '../../src/paired-punctuation/paired-pun import { PairedPunctuationDirection } from '../../src/utils'; import { StubDocumentManager, StubSingleLineTextDocument } from '../test-utils'; -const defaultLocalizer: Localizer = new Localizer(); -defaultLocalizer.addNamespace('pairedPunctuation', (_language: string) => { - return { - diagnosticMessagesByCode: { - 'unmatched-opening-parenthesis': 'Opening parenthesis with no closing parenthesis.', - 'unmatched-closing-parenthesis': 'Closing parenthesis with no opening parenthesis.', - 'unmatched-opening-square-bracket': 'Opening square bracket with no closing bracket.', - 'unmatched-closing-square-bracket': 'Closing square bracket with no opening bracket.', - 'unmatched-opening-curly-bracket': 'Opening curly bracket with no closing bracket.', - 'unmatched-closing-curly-bracket': 'Closing curly bracket with no opening bracket.', - 'unmatched-opening-punctuation-mark': 'Opening punctuation mark with no closing mark.', - 'unmatched-closing-punctuation-mark': 'Closing punctuation mark with no opening mark.', - 'overlapping-punctuation-pairs': - 'This pair of punctuation marks {{firstPair}} overlaps with another pair {{secondPair}}.', - }, - }; -}); -await defaultLocalizer.init(); - -const stubDiagnosticFactory: DiagnosticFactory = new DiagnosticFactory( - 'paired-punctuation-checker', - new StubSingleLineTextDocument(''), // passing an empty document is fine here since we don't use getText() -); - describe('PairedPunctuationErrorFinder tests', () => { - const pairedPunctuationConfig: PairedPunctuationConfig = new PairedPunctuationConfig.Builder() - .addQuotationRule({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addQuotationRule({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addRule({ - openingPunctuationMark: '(', - closingPunctuationMark: ')', - }) - .addRule({ - openingPunctuationMark: '[', - closingPunctuationMark: ']', - }) - .addRule({ - openingPunctuationMark: '{', - closingPunctuationMark: '}', - }) - .addRule({ - openingPunctuationMark: '<', - closingPunctuationMark: '>', - }) - .build(); - - const pairedPunctuationErrorFinder = new _privateTestingClasses.PairedPunctuationErrorFinder( - defaultLocalizer, - pairedPunctuationConfig, - stubDiagnosticFactory, - ); - - it('creates no Diagnostics for error-free text', () => { - expect(pairedPunctuationErrorFinder.produceDiagnostics('The rain in Spain falls mainly on the plain.')).toEqual([]); + it('creates no Diagnostics for error-free text', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuationAndAngleBrackets(); + await testEnv.init(); + expect( + testEnv.pairedPunctuationErrorFinder.produceDiagnostics('The rain in Spain falls mainly on the plain.'), + ).toEqual([]); }); - it('creates Diagnostics for unmatched paired punctuation', () => { - expect(pairedPunctuationErrorFinder.produceDiagnostics('The (rain in Spain falls mainly on the plain.')).toEqual([ + it('creates Diagnostics for unmatched paired punctuation', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuationAndAngleBrackets(); + await testEnv.init(); + + expect( + testEnv.pairedPunctuationErrorFinder.produceDiagnostics('The (rain in Spain falls mainly on the plain.'), + ).toEqual([ { code: 'unmatched-opening-parenthesis', source: 'paired-punctuation-checker', @@ -94,7 +46,9 @@ describe('PairedPunctuationErrorFinder tests', () => { }, ]); - expect(pairedPunctuationErrorFinder.produceDiagnostics('The [rain in Spain falls mainly on the plain.')).toEqual([ + expect( + testEnv.pairedPunctuationErrorFinder.produceDiagnostics('The [rain in Spain falls mainly on the plain.'), + ).toEqual([ { code: 'unmatched-opening-square-bracket', source: 'paired-punctuation-checker', @@ -114,7 +68,9 @@ describe('PairedPunctuationErrorFinder tests', () => { }, ]); - expect(pairedPunctuationErrorFinder.produceDiagnostics('The {rain in Spain falls mainly on the plain.')).toEqual([ + expect( + testEnv.pairedPunctuationErrorFinder.produceDiagnostics('The {rain in Spain falls mainly on the plain.'), + ).toEqual([ { code: 'unmatched-opening-curly-bracket', source: 'paired-punctuation-checker', @@ -134,7 +90,9 @@ describe('PairedPunctuationErrorFinder tests', () => { }, ]); - expect(pairedPunctuationErrorFinder.produceDiagnostics('The { }, ]); - expect(pairedPunctuationErrorFinder.produceDiagnostics('The )rain in Spain falls mainly on the plain.')).toEqual([ + expect( + testEnv.pairedPunctuationErrorFinder.produceDiagnostics('The )rain in Spain falls mainly on the plain.'), + ).toEqual([ { code: 'unmatched-closing-parenthesis', source: 'paired-punctuation-checker', @@ -174,7 +134,9 @@ describe('PairedPunctuationErrorFinder tests', () => { }, ]); - expect(pairedPunctuationErrorFinder.produceDiagnostics('The ]rain in Spain falls mainly on the plain.')).toEqual([ + expect( + testEnv.pairedPunctuationErrorFinder.produceDiagnostics('The ]rain in Spain falls mainly on the plain.'), + ).toEqual([ { code: 'unmatched-closing-square-bracket', source: 'paired-punctuation-checker', @@ -194,7 +156,9 @@ describe('PairedPunctuationErrorFinder tests', () => { }, ]); - expect(pairedPunctuationErrorFinder.produceDiagnostics('The }rain in Spain falls mainly on the plain.')).toEqual([ + expect( + testEnv.pairedPunctuationErrorFinder.produceDiagnostics('The }rain in Spain falls mainly on the plain.'), + ).toEqual([ { code: 'unmatched-closing-curly-bracket', source: 'paired-punctuation-checker', @@ -214,7 +178,9 @@ describe('PairedPunctuationErrorFinder tests', () => { }, ]); - expect(pairedPunctuationErrorFinder.produceDiagnostics('The >rain in Spain falls mainly on the plain.')).toEqual([ + expect( + testEnv.pairedPunctuationErrorFinder.produceDiagnostics('The >rain in Spain falls mainly on the plain.'), + ).toEqual([ { code: 'unmatched-closing-punctuation-mark', source: 'paired-punctuation-checker', @@ -234,7 +200,9 @@ describe('PairedPunctuationErrorFinder tests', () => { }, ]); - expect(pairedPunctuationErrorFinder.produceDiagnostics('The (rain in Spain falls] mainly on the plain.')).toEqual([ + expect( + testEnv.pairedPunctuationErrorFinder.produceDiagnostics('The (rain in Spain falls] mainly on the plain.'), + ).toEqual([ { code: 'unmatched-closing-square-bracket', source: 'paired-punctuation-checker', @@ -272,7 +240,9 @@ describe('PairedPunctuationErrorFinder tests', () => { ]); expect( - pairedPunctuationErrorFinder.produceDiagnostics('The \u2018rain in Spain falls mainly\u2019 on the {plain.'), + testEnv.pairedPunctuationErrorFinder.produceDiagnostics( + 'The \u2018rain in Spain falls mainly\u2019 on the {plain.', + ), ).toEqual([ { code: 'unmatched-opening-curly-bracket', @@ -294,57 +264,65 @@ describe('PairedPunctuationErrorFinder tests', () => { ]); }); - it('does not create Diagnostics for unmatched quotes', () => { + it('does not create Diagnostics for unmatched quotes', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuationAndAngleBrackets(); + await testEnv.init(); + expect( - pairedPunctuationErrorFinder.produceDiagnostics('The rain in \u201CSpain falls mainly on the plain.'), + testEnv.pairedPunctuationErrorFinder.produceDiagnostics('The rain in \u201CSpain falls mainly on the plain.'), ).toEqual([]); expect( - pairedPunctuationErrorFinder.produceDiagnostics('The rain in (Spain falls mainly) on the plain.\u2019'), + testEnv.pairedPunctuationErrorFinder.produceDiagnostics('The rain in (Spain falls mainly) on the plain.\u2019'), ).toEqual([]); }); - it('creates Diagnostics for incorrectly overlapping paired punctuation', () => { - expect(pairedPunctuationErrorFinder.produceDiagnostics('The (rain in [Spain) falls mainly] on the plain.')).toEqual( - [ - { - code: 'overlapping-punctuation-pairs', - source: 'paired-punctuation-checker', - severity: DiagnosticSeverity.Warning, - range: { - start: { - line: 0, - character: 19, - }, - end: { - line: 0, - character: 20, - }, + it('creates Diagnostics for incorrectly overlapping paired punctuation', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuationAndAngleBrackets(); + await testEnv.init(); + + expect( + testEnv.pairedPunctuationErrorFinder.produceDiagnostics('The (rain in [Spain) falls mainly] on the plain.'), + ).toEqual([ + { + code: 'overlapping-punctuation-pairs', + source: 'paired-punctuation-checker', + severity: DiagnosticSeverity.Warning, + range: { + start: { + line: 0, + character: 19, + }, + end: { + line: 0, + character: 20, }, - message: 'This pair of punctuation marks (\u2026) overlaps with another pair [\u2026].', - data: '', }, - { - code: 'overlapping-punctuation-pairs', - source: 'paired-punctuation-checker', - severity: DiagnosticSeverity.Warning, - range: { - start: { - line: 0, - character: 13, - }, - end: { - line: 0, - character: 14, - }, + message: 'This pair of punctuation marks (\u2026) overlaps with another pair [\u2026].', + data: '', + }, + { + code: 'overlapping-punctuation-pairs', + source: 'paired-punctuation-checker', + severity: DiagnosticSeverity.Warning, + range: { + start: { + line: 0, + character: 13, + }, + end: { + line: 0, + character: 14, }, - message: 'This pair of punctuation marks [\u2026] overlaps with another pair (\u2026).', - data: '', }, - ], - ); + message: 'This pair of punctuation marks [\u2026] overlaps with another pair (\u2026).', + data: '', + }, + ]); expect( - pairedPunctuationErrorFinder.produceDiagnostics('The \u201Crain in {Spain\u201D falls mainly} on the plain.'), + testEnv.pairedPunctuationErrorFinder.produceDiagnostics( + 'The \u201Crain in {Spain\u201D falls mainly} on the plain.', + ), ).toEqual([ { code: 'overlapping-punctuation-pairs', @@ -385,58 +363,33 @@ describe('PairedPunctuationErrorFinder tests', () => { }); describe('PairedPunctuationIterator tests', () => { - const pairedPunctuationConfig: PairedPunctuationConfig = new PairedPunctuationConfig.Builder() - .addQuotationRule({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addQuotationRule({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addRule({ - openingPunctuationMark: '(', - closingPunctuationMark: ')', - }) - .addRule({ - openingPunctuationMark: '[', - closingPunctuationMark: ']', - }) - .addRule({ - openingPunctuationMark: '{', - closingPunctuationMark: '}', - }) - .build(); - - it('does not identify any punctuation marks not in the PairedPunctuationConfig', () => { - const emptyStringPairedPunctuationIterator: PairedPunctuationIterator = new PairedPunctuationIterator( - pairedPunctuationConfig, - '', - ); + it('does not identify any punctuation marks not in the PairedPunctuationConfig', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuation(); + await testEnv.init(); + + const emptyStringPairedPunctuationIterator: PairedPunctuationIterator = testEnv.newPairedPunctuationIterator(''); expect(emptyStringPairedPunctuationIterator.hasNext()).toBe(false); - const noPunctuationPairedPunctuationIterator: PairedPunctuationIterator = new PairedPunctuationIterator( - pairedPunctuationConfig, + const noPunctuationPairedPunctuationIterator: PairedPunctuationIterator = testEnv.newPairedPunctuationIterator( 'The rain in Spain falls mainly on the plain', ); expect(noPunctuationPairedPunctuationIterator.hasNext()).toBe(false); - const noPairedPunctuationPairedPunctuationIterator: PairedPunctuationIterator = new PairedPunctuationIterator( - pairedPunctuationConfig, - 'The #rain, in @Spain! falls& mainly? on^ the plain*', - ); + const noPairedPunctuationPairedPunctuationIterator: PairedPunctuationIterator = + testEnv.newPairedPunctuationIterator('The #rain, in @Spain! falls& mainly? on^ the plain*'); expect(noPairedPunctuationPairedPunctuationIterator.hasNext()).toBe(false); - const pairedPunctuationNotInTheConfigIterator: PairedPunctuationIterator = new PairedPunctuationIterator( - pairedPunctuationConfig, + const pairedPunctuationNotInTheConfigIterator: PairedPunctuationIterator = testEnv.newPairedPunctuationIterator( '\u201EThe rain in `Spain` falls on the plain\u201F', ); expect(pairedPunctuationNotInTheConfigIterator.hasNext()).toBe(false); }); - it('identifies properly paired punctuation in the context of a sentence', () => { - const pairedPunctuationIterator: PairedPunctuationIterator = new PairedPunctuationIterator( - pairedPunctuationConfig, + it('identifies properly paired punctuation in the context of a sentence', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuation(); + await testEnv.init(); + + const pairedPunctuationIterator: PairedPunctuationIterator = testEnv.newPairedPunctuationIterator( '\u201CThe rain\u201D in [Spain {falls} mainly] (on the plain).', ); expect(pairedPunctuationIterator.hasNext()).toBe(true); @@ -497,9 +450,11 @@ describe('PairedPunctuationIterator tests', () => { }); }); - it('identifies malformed paired punctuation in the context of a sentence', () => { - const pairedPunctuationIterator: PairedPunctuationIterator = new PairedPunctuationIterator( - pairedPunctuationConfig, + it('identifies malformed paired punctuation in the context of a sentence', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuation(); + await testEnv.init(); + + const pairedPunctuationIterator: PairedPunctuationIterator = testEnv.newPairedPunctuationIterator( '\u201DThe rain\u201C in [Spain [falls{ mainly) (on the plain].', ); expect(pairedPunctuationIterator.hasNext()).toBe(true); @@ -560,32 +515,11 @@ describe('PairedPunctuationIterator tests', () => { }); }); - it('adheres to the PairedPunctuationConfig', () => { - const backwardsPairedPunctuationConfig = new PairedPunctuationConfig.Builder() - .addQuotationRule({ - openingPunctuationMark: '\u201D', - closingPunctuationMark: '\u201C', - }) - .addQuotationRule({ - openingPunctuationMark: '\u2019', - closingPunctuationMark: '\u2018', - }) - .addRule({ - openingPunctuationMark: ')', - closingPunctuationMark: '(', - }) - .addRule({ - openingPunctuationMark: ']', - closingPunctuationMark: '[', - }) - .addRule({ - openingPunctuationMark: '}', - closingPunctuationMark: '{', - }) - .build(); - - const pairedPunctuationIterator: PairedPunctuationIterator = new PairedPunctuationIterator( - backwardsPairedPunctuationConfig, + it('adheres to the PairedPunctuationConfig', async () => { + const backwardsTestEnv: TestEnvironment = TestEnvironment.createWithBackwardsPairedPunctuation(); + await backwardsTestEnv.init(); + + const pairedPunctuationIterator: PairedPunctuationIterator = backwardsTestEnv.newPairedPunctuationIterator( '\u201CThe rain\u201D in [Spain {falls} mainly] (on the plain).', ); expect(pairedPunctuationIterator.hasNext()).toBe(true); @@ -645,19 +579,10 @@ describe('PairedPunctuationIterator tests', () => { text: ')', }); - const bizarrePairedPunctuationConfig = new PairedPunctuationConfig.Builder() - .addRule({ - openingPunctuationMark: '+', - closingPunctuationMark: '-', - }) - .addRule({ - openingPunctuationMark: '/', - closingPunctuationMark: '\\', - }) - .build(); - - const bizarrePairedPunctuationIterator: PairedPunctuationIterator = new PairedPunctuationIterator( - bizarrePairedPunctuationConfig, + const bizarreTestEnv: TestEnvironment = TestEnvironment.createWithBizarrePairedPunctuation(); + await bizarreTestEnv.init(); + + const bizarrePairedPunctuationIterator: PairedPunctuationIterator = bizarreTestEnv.newPairedPunctuationIterator( 'The +rain in /Spain +falls- mainly\\ on the- plain.', ); expect(bizarrePairedPunctuationIterator.hasNext()).toBe(true); @@ -706,65 +631,44 @@ describe('PairedPunctuationIterator tests', () => { }); describe('PairedPunctuationAnalyzer tests', () => { - const pairedPunctuationConfig: PairedPunctuationConfig = new PairedPunctuationConfig.Builder() - .addQuotationRule({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addQuotationRule({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addRule({ - openingPunctuationMark: '(', - closingPunctuationMark: ')', - }) - .addRule({ - openingPunctuationMark: '[', - closingPunctuationMark: ']', - }) - .addRule({ - openingPunctuationMark: '{', - closingPunctuationMark: '}', - }) - .build(); - - it('identifies no issues with well-formed text', () => { - const pairedPunctuationAnalyzer = new _privateTestingClasses.PairedPunctuationAnalyzer(pairedPunctuationConfig); - - const emptyAnalysis = pairedPunctuationAnalyzer.analyze(''); + it('identifies no issues with well-formed text', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuation(); + await testEnv.init(); + + const emptyAnalysis = testEnv.pairedPunctuationAnalyzer.analyze(''); expect(emptyAnalysis.getOverlappingPunctuationMarks()).toEqual([]); expect(emptyAnalysis.getUnmatchedPunctuationMarks()).toEqual([]); - const quotesAnalysis = pairedPunctuationAnalyzer.analyze( + const quotesAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( '\u201CThe rain in \u2018Spain \u201Cfalls\u201D mainly\u2019 on the plain.\u201D', ); expect(quotesAnalysis.getOverlappingPunctuationMarks()).toEqual([]); expect(quotesAnalysis.getUnmatchedPunctuationMarks()).toEqual([]); - const multiplePairsAnalysis = pairedPunctuationAnalyzer.analyze( + const multiplePairsAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( '(The rain) in [Spain] falls {mainly} (on the plain).', ); expect(multiplePairsAnalysis.getOverlappingPunctuationMarks()).toEqual([]); expect(multiplePairsAnalysis.getUnmatchedPunctuationMarks()).toEqual([]); - const multipleNestedPairsAnalysis = pairedPunctuationAnalyzer.analyze( + const multipleNestedPairsAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( '(The rain in [Spain {falls} mainly] on the plain).', ); expect(multipleNestedPairsAnalysis.getOverlappingPunctuationMarks()).toEqual([]); expect(multipleNestedPairsAnalysis.getUnmatchedPunctuationMarks()).toEqual([]); - const quotesAndOtherPairsAnalysis = pairedPunctuationAnalyzer.analyze( + const quotesAndOtherPairsAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( '\u201CThe rain in (Spain \u2018falls [mainly]\u2019 on the plain).\u201D', ); expect(quotesAndOtherPairsAnalysis.getOverlappingPunctuationMarks()).toEqual([]); expect(quotesAndOtherPairsAnalysis.getUnmatchedPunctuationMarks()).toEqual([]); }); - it('identifies unmatched punctuation pairs', () => { - const pairedPunctuationAnalyzer = new _privateTestingClasses.PairedPunctuationAnalyzer(pairedPunctuationConfig); + it('identifies unmatched punctuation pairs', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuation(); + await testEnv.init(); - const openingParenAnalysis = pairedPunctuationAnalyzer.analyze('('); + const openingParenAnalysis = testEnv.pairedPunctuationAnalyzer.analyze('('); expect(openingParenAnalysis.getUnmatchedPunctuationMarks()).toEqual([ { direction: PairedPunctuationDirection.Opening, @@ -774,7 +678,7 @@ describe('PairedPunctuationAnalyzer tests', () => { }, ]); - const closingParenAnalysis = pairedPunctuationAnalyzer.analyze(')'); + const closingParenAnalysis = testEnv.pairedPunctuationAnalyzer.analyze(')'); expect(closingParenAnalysis.getUnmatchedPunctuationMarks()).toEqual([ { direction: PairedPunctuationDirection.Closing, @@ -784,7 +688,7 @@ describe('PairedPunctuationAnalyzer tests', () => { }, ]); - const unmatchedOpeningParenAnalysis = pairedPunctuationAnalyzer.analyze( + const unmatchedOpeningParenAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( '(The rain in [Spain falls] mainly on the plain', ); expect(unmatchedOpeningParenAnalysis.getUnmatchedPunctuationMarks()).toEqual([ @@ -796,7 +700,7 @@ describe('PairedPunctuationAnalyzer tests', () => { }, ]); - const unmatchedClosingParenAnalysis = pairedPunctuationAnalyzer.analyze( + const unmatchedClosingParenAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( 'The rain in [Spain falls] mainly on the plain)', ); expect(unmatchedClosingParenAnalysis.getUnmatchedPunctuationMarks()).toEqual([ @@ -808,7 +712,7 @@ describe('PairedPunctuationAnalyzer tests', () => { }, ]); - const nestedUnmatchedOpeningMarkAnalysis = pairedPunctuationAnalyzer.analyze( + const nestedUnmatchedOpeningMarkAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( '(The rain in [Spain falls) mainly on the plain', ); expect(nestedUnmatchedOpeningMarkAnalysis.getUnmatchedPunctuationMarks()).toEqual([ @@ -820,7 +724,7 @@ describe('PairedPunctuationAnalyzer tests', () => { }, ]); - const nestedUnmatchedClosingMarkAnalysis = pairedPunctuationAnalyzer.analyze( + const nestedUnmatchedClosingMarkAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( '(The rain in ]Spain falls) mainly on the plain', ); expect(nestedUnmatchedClosingMarkAnalysis.getUnmatchedPunctuationMarks()).toEqual([ @@ -833,29 +737,31 @@ describe('PairedPunctuationAnalyzer tests', () => { ]); }); - it("doesn't identify unmatched Quotation mark errors", () => { - const pairedPunctuationAnalyzer = new _privateTestingClasses.PairedPunctuationAnalyzer(pairedPunctuationConfig); + it("doesn't identify unmatched Quotation mark errors", async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuation(); + await testEnv.init(); - const unmatchedOpeningQuoteAnalysis = pairedPunctuationAnalyzer.analyze( + const unmatchedOpeningQuoteAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( '\u201CThe (rain in [Spain]) falls mainly on the plain', ); expect(unmatchedOpeningQuoteAnalysis.getUnmatchedPunctuationMarks()).toEqual([]); - const unmatchedClosingQuoteAnalysis = pairedPunctuationAnalyzer.analyze( + const unmatchedClosingQuoteAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( 'The (rain in [Spain\u201D]) falls mainly on the plain', ); expect(unmatchedClosingQuoteAnalysis.getUnmatchedPunctuationMarks()).toEqual([]); - const noOtherMarksAnalysis = pairedPunctuationAnalyzer.analyze( + const noOtherMarksAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( '\u201CThe rain in Spain\u2019 falls mainly on the plain\u2019', ); expect(noOtherMarksAnalysis.getUnmatchedPunctuationMarks()).toEqual([]); }); - it('identifies pairs of punctuation marks that incorrectly overlap', () => { - const pairedPunctuationAnalyzer = new _privateTestingClasses.PairedPunctuationAnalyzer(pairedPunctuationConfig); + it('identifies pairs of punctuation marks that incorrectly overlap', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuation(); + await testEnv.init(); - const overlappingPairsAnalysis = pairedPunctuationAnalyzer.analyze( + const overlappingPairsAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( '(The rain in [Spain) falls] mainly on the plain.', ); expect(overlappingPairsAnalysis.getOverlappingPunctuationMarks()).toEqual([ @@ -875,7 +781,7 @@ describe('PairedPunctuationAnalyzer tests', () => { ), ]); - const overlapsWithQuotesAnalysis = pairedPunctuationAnalyzer.analyze( + const overlapsWithQuotesAnalysis = testEnv.pairedPunctuationAnalyzer.analyze( '(The rain in \u201CSpain) falls\u201D mainly on the plain.', ); expect(overlapsWithQuotesAnalysis.getOverlappingPunctuationMarks()).toEqual([ @@ -898,42 +804,12 @@ describe('PairedPunctuationAnalyzer tests', () => { }); describe('PairedPunctuationChecker tests', () => { - const stubDocumentManager: StubDocumentManager = new StubDocumentManager(new TextDocumentFactory()); - const pairedPunctuationConfig: PairedPunctuationConfig = new PairedPunctuationConfig.Builder() - .addQuotationRule({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addQuotationRule({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addRule({ - openingPunctuationMark: '(', - closingPunctuationMark: ')', - }) - .addRule({ - openingPunctuationMark: '[', - closingPunctuationMark: ']', - }) - .addRule({ - openingPunctuationMark: '{', - closingPunctuationMark: '}', - }) - .build(); - it('produces DiagnosticFixes for unmatched marks', async () => { - const localizer: Localizer = new Localizer(); - const pairedPunctuationChecker: PairedPunctuationChecker = new PairedPunctuationChecker( - localizer, - stubDocumentManager, - pairedPunctuationConfig, - ); - await pairedPunctuationChecker.init(); - await localizer.init(); + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuation(); + await testEnv.init(); expect( - await pairedPunctuationChecker.getDiagnosticFixes('(Hello', { + await testEnv.pairedPunctuationChecker.getDiagnosticFixes('(Hello', { code: 'unmatched-opening-parenthesis', source: 'paired-punctuation-checker', severity: DiagnosticSeverity.Error, @@ -989,17 +865,11 @@ describe('PairedPunctuationChecker tests', () => { }); it('does not produce DiagnosticFixes for overlapping pairs of marks', async () => { - const localizer: Localizer = new Localizer(); - const pairedPunctuationChecker: PairedPunctuationChecker = new PairedPunctuationChecker( - localizer, - stubDocumentManager, - pairedPunctuationConfig, - ); - await pairedPunctuationChecker.init(); - await localizer.init(); + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuation(); + await testEnv.init(); expect( - await pairedPunctuationChecker.getDiagnosticFixes('(Hello\u201C) there\u201D', { + await testEnv.pairedPunctuationChecker.getDiagnosticFixes('(Hello\u201C) there\u201D', { code: 'overlapping-punctuation-pairs', source: 'paired-punctuation-checker', severity: DiagnosticSeverity.Warning, @@ -1019,16 +889,12 @@ describe('PairedPunctuationChecker tests', () => { }); it('initializes its own namespace in the localizer', async () => { - const localizer: Localizer = new Localizer(); - const pairedPunctuationChecker: PairedPunctuationChecker = new PairedPunctuationChecker( - localizer, - stubDocumentManager, - pairedPunctuationConfig, - ); - await pairedPunctuationChecker.init(); - await localizer.init(); + const testEnv: TestEnvironment = TestEnvironment.createWithStandardPairedPunctuation(); + await testEnv.init(); - expect(await pairedPunctuationChecker.getDiagnostics('The [rain in Spain falls mainly on the plain.')).toEqual([ + expect( + await testEnv.pairedPunctuationChecker.getDiagnostics('The [rain in Spain falls mainly on the plain.'), + ).toEqual([ { code: 'unmatched-opening-square-bracket', source: 'paired-punctuation-checker', @@ -1049,7 +915,9 @@ describe('PairedPunctuationChecker tests', () => { ]); expect( - await pairedPunctuationChecker.getDiagnostics('The {rain in \u2018Spain} falls mainly\u2019 on the plain.'), + await testEnv.pairedPunctuationChecker.getDiagnostics( + 'The {rain in \u2018Spain} falls mainly\u2019 on the plain.', + ), ).toEqual([ { code: 'overlapping-punctuation-pairs', @@ -1089,8 +957,8 @@ describe('PairedPunctuationChecker tests', () => { }); it('gets its messages from the localizer', async () => { - const localizer: Localizer = new Localizer(); - localizer.addNamespace('pairedPunctuation', (_language: string) => { + const customLocalizer: Localizer = new Localizer(); + customLocalizer.addNamespace('pairedPunctuation', (_language: string) => { return { diagnosticMessagesByCode: { 'unmatched-opening-square-bracket': "You didn't close your opening square bracket.", @@ -1098,15 +966,14 @@ describe('PairedPunctuationChecker tests', () => { }, }; }); - const pairedPunctuationChecker: PairedPunctuationChecker = new PairedPunctuationChecker( - localizer, - stubDocumentManager, - pairedPunctuationConfig, - ); - await pairedPunctuationChecker.init(); - await localizer.init(); - expect(await pairedPunctuationChecker.getDiagnostics('The [rain in Spain falls mainly on the plain.')).toEqual([ + const testEnv: TestEnvironment = + TestEnvironment.createWithStandardPairedPunctuationAndCustomLocalizer(customLocalizer); + await testEnv.init(); + + expect( + await testEnv.pairedPunctuationChecker.getDiagnostics('The [rain in Spain falls mainly on the plain.'), + ).toEqual([ { code: 'unmatched-opening-square-bracket', source: 'paired-punctuation-checker', @@ -1127,7 +994,9 @@ describe('PairedPunctuationChecker tests', () => { ]); expect( - await pairedPunctuationChecker.getDiagnostics('The {rain in \u2018Spain} falls mainly\u2019 on the plain.'), + await testEnv.pairedPunctuationChecker.getDiagnostics( + 'The {rain in \u2018Spain} falls mainly\u2019 on the plain.', + ), ).toEqual([ { code: 'overlapping-punctuation-pairs', @@ -1166,3 +1035,200 @@ describe('PairedPunctuationChecker tests', () => { ]); }); }); + +class TestEnvironment { + readonly pairedPunctuationErrorFinder; + readonly pairedPunctuationAnalyzer; + readonly pairedPunctuationChecker: PairedPunctuationChecker; + + private readonly pairedPunctuationErrorFinderLocalizer: Localizer; // we have separate localizers for the two classes + private readonly pairedPunctuationCheckerLocalizer: Localizer; // since QuotationChecker populates the localizer on its own + + constructor( + private readonly pairedPunctuationConfig: PairedPunctuationConfig, + private readonly customLocalizer?: Localizer, + ) { + this.pairedPunctuationErrorFinderLocalizer = this.createDefaultLocalizer(); + this.pairedPunctuationCheckerLocalizer = new Localizer(); + + const stubDiagnosticFactory: DiagnosticFactory = new DiagnosticFactory( + 'paired-punctuation-checker', + new StubSingleLineTextDocument(''), // passing an empty document is fine here since we don't use getText() + ); + this.pairedPunctuationErrorFinder = new _privateTestingClasses.PairedPunctuationErrorFinder( + this.customLocalizer ?? this.pairedPunctuationErrorFinderLocalizer, + this.pairedPunctuationConfig, + stubDiagnosticFactory, + ); + + this.pairedPunctuationAnalyzer = new _privateTestingClasses.PairedPunctuationAnalyzer(this.pairedPunctuationConfig); + + const stubDocumentManager: StubDocumentManager = new StubDocumentManager(new TextDocumentFactory()); + this.pairedPunctuationChecker = new PairedPunctuationChecker( + this.customLocalizer ?? this.pairedPunctuationCheckerLocalizer, + stubDocumentManager, + pairedPunctuationConfig, + ); + } + + private createDefaultLocalizer(): Localizer { + const defaultLocalizer: Localizer = new Localizer(); + defaultLocalizer.addNamespace('pairedPunctuation', (_language: string) => { + return { + diagnosticMessagesByCode: { + 'unmatched-opening-parenthesis': 'Opening parenthesis with no closing parenthesis.', + 'unmatched-closing-parenthesis': 'Closing parenthesis with no opening parenthesis.', + 'unmatched-opening-square-bracket': 'Opening square bracket with no closing bracket.', + 'unmatched-closing-square-bracket': 'Closing square bracket with no opening bracket.', + 'unmatched-opening-curly-bracket': 'Opening curly bracket with no closing bracket.', + 'unmatched-closing-curly-bracket': 'Closing curly bracket with no opening bracket.', + 'unmatched-opening-punctuation-mark': 'Opening punctuation mark with no closing mark.', + 'unmatched-closing-punctuation-mark': 'Closing punctuation mark with no opening mark.', + 'overlapping-punctuation-pairs': + 'This pair of punctuation marks {{firstPair}} overlaps with another pair {{secondPair}}.', + }, + }; + }); + return defaultLocalizer; + } + + public async init(): Promise { + await this.pairedPunctuationChecker.init(); + await this.pairedPunctuationErrorFinderLocalizer.init(); + await this.pairedPunctuationCheckerLocalizer.init(); + + await this.customLocalizer?.init(); + } + + static createWithStandardPairedPunctuationAndAngleBrackets(): TestEnvironment { + return new TestEnvironment( + new PairedPunctuationConfig.Builder() + .addQuotationRule({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addQuotationRule({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .addRule({ + openingPunctuationMark: '(', + closingPunctuationMark: ')', + }) + .addRule({ + openingPunctuationMark: '[', + closingPunctuationMark: ']', + }) + .addRule({ + openingPunctuationMark: '{', + closingPunctuationMark: '}', + }) + .addRule({ + openingPunctuationMark: '<', + closingPunctuationMark: '>', + }) + .build(), + ); + } + + static createWithStandardPairedPunctuation(): TestEnvironment { + return new TestEnvironment( + new PairedPunctuationConfig.Builder() + .addQuotationRule({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addQuotationRule({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .addRule({ + openingPunctuationMark: '(', + closingPunctuationMark: ')', + }) + .addRule({ + openingPunctuationMark: '[', + closingPunctuationMark: ']', + }) + .addRule({ + openingPunctuationMark: '{', + closingPunctuationMark: '}', + }) + .build(), + ); + } + + static createWithBackwardsPairedPunctuation(): TestEnvironment { + return new TestEnvironment( + new PairedPunctuationConfig.Builder() + .addQuotationRule({ + openingPunctuationMark: '\u201D', + closingPunctuationMark: '\u201C', + }) + .addQuotationRule({ + openingPunctuationMark: '\u2019', + closingPunctuationMark: '\u2018', + }) + .addRule({ + openingPunctuationMark: ')', + closingPunctuationMark: '(', + }) + .addRule({ + openingPunctuationMark: ']', + closingPunctuationMark: '[', + }) + .addRule({ + openingPunctuationMark: '}', + closingPunctuationMark: '{', + }) + .build(), + ); + } + + static createWithBizarrePairedPunctuation(): TestEnvironment { + return new TestEnvironment( + new PairedPunctuationConfig.Builder() + .addRule({ + openingPunctuationMark: '+', + closingPunctuationMark: '-', + }) + .addRule({ + openingPunctuationMark: '/', + closingPunctuationMark: '\\', + }) + .build(), + ); + } + + static createWithStandardPairedPunctuationAndCustomLocalizer(customLocalizer: Localizer): TestEnvironment { + return new TestEnvironment( + new PairedPunctuationConfig.Builder() + .addQuotationRule({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addQuotationRule({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .addRule({ + openingPunctuationMark: '(', + closingPunctuationMark: ')', + }) + .addRule({ + openingPunctuationMark: '[', + closingPunctuationMark: ']', + }) + .addRule({ + openingPunctuationMark: '{', + closingPunctuationMark: '}', + }) + .build(), + customLocalizer, + ); + } + + newPairedPunctuationIterator(text: string): PairedPunctuationIterator { + return new PairedPunctuationIterator(this.pairedPunctuationConfig, text); + } +} diff --git a/packages/punctuation-checker/test/quotation/quotation-analyzer.test.ts b/packages/punctuation-checker/test/quotation/quotation-analyzer.test.ts index 59ecaaf..75ff75a 100644 --- a/packages/punctuation-checker/test/quotation/quotation-analyzer.test.ts +++ b/packages/punctuation-checker/test/quotation/quotation-analyzer.test.ts @@ -13,15 +13,11 @@ import { PairedPunctuationDirection } from '../../src/utils'; describe('QuotationAnalyzer tests', () => { describe('Miscellaneous tests', () => { it("doesn't store any state between runs", () => { - const quotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .build(); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelEnglishQuotes(); - const quotationAnalyzer: QuotationAnalyzer = new QuotationAnalyzer(quotationConfig); - const danglingQuotationAnalysis: QuotationAnalysis = quotationAnalyzer.analyze('\u201CSample "text'); + const danglingQuotationAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( + '\u201CSample \u2018text\u2019', + ); expect(danglingQuotationAnalysis.getUnmatchedQuotes()).toEqual([ { depth: QuotationDepth.Primary, @@ -33,40 +29,35 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const emptyQuotationAnalysis: QuotationAnalysis = quotationAnalyzer.analyze('Sample "text'); + const emptyQuotationAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze('Sample \u2018text\u2019'); expect(emptyQuotationAnalysis.getUnmatchedQuotes()).toEqual([]); }); }); describe('Issue identification tests', () => { describe('Top-level English quotation marks', () => { - const quotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .build(); - const quotationAnalyzer: QuotationAnalyzer = new QuotationAnalyzer(quotationConfig); - describe('No issues for well-formed text', () => { it('produces no issues for quote-less text', () => { - const emptyStringAnalysis: QuotationAnalysis = quotationAnalyzer.analyze(''); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelEnglishQuotes(); + + const emptyStringAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze(''); expect(emptyStringAnalysis.getUnmatchedQuotes()).toEqual([]); expect(emptyStringAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); - const noQuotationAnalysis: QuotationAnalysis = quotationAnalyzer.analyze('Sample text'); + const noQuotationAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze('Sample text'); expect(noQuotationAnalysis.getUnmatchedQuotes()).toEqual([]); expect(noQuotationAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); }); it('produces no issues for well-formed quote pairs', () => { - const singleQuotationPairAnalysis: QuotationAnalysis = quotationAnalyzer.analyze('\u201CSample text\u201D'); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelEnglishQuotes(); + + const singleQuotationPairAnalysis: QuotationAnalysis = + testEnv.quotationAnalyzer.analyze('\u201CSample text\u201D'); expect(singleQuotationPairAnalysis.getUnmatchedQuotes()).toEqual([]); expect(singleQuotationPairAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); - const multipleQuotationPairAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleQuotationPairAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CSample text\u201D with \u201Cmore sample text\u201D', ); expect(multipleQuotationPairAnalysis.getUnmatchedQuotes()).toEqual([]); @@ -74,19 +65,23 @@ describe('QuotationAnalyzer tests', () => { }); it('produces no issues when ambiguous quotation marks are well-formed', () => { - const pairOfAmbiguousQuotePairAnalysis: QuotationAnalysis = quotationAnalyzer.analyze('""'); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelEnglishQuotes(); + + const pairOfAmbiguousQuotePairAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze('""'); expect(pairOfAmbiguousQuotePairAnalysis.getUnmatchedQuotes()).toEqual([]); expect(pairOfAmbiguousQuotePairAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); - const midStringAmbiguousQuotePairAnalysis: QuotationAnalysis = quotationAnalyzer.analyze('Sample "text"'); + const midStringAmbiguousQuotePairAnalysis: QuotationAnalysis = + testEnv.quotationAnalyzer.analyze('Sample "text"'); expect(midStringAmbiguousQuotePairAnalysis.getUnmatchedQuotes()).toEqual([]); expect(midStringAmbiguousQuotePairAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); - const mismatchedQuotationPairAnalysis: QuotationAnalysis = quotationAnalyzer.analyze('\u201CSample text"'); + const mismatchedQuotationPairAnalysis: QuotationAnalysis = + testEnv.quotationAnalyzer.analyze('\u201CSample text"'); expect(mismatchedQuotationPairAnalysis.getUnmatchedQuotes()).toEqual([]); expect(mismatchedQuotationPairAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); - const multipleQuotationPairAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleQuotationPairAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '"Sample text\u201D using \u201Cmixed "quotation" marks\u201D', ); expect(multipleQuotationPairAnalysis.getUnmatchedQuotes()).toEqual([]); @@ -96,7 +91,9 @@ describe('QuotationAnalyzer tests', () => { describe('Incorrectly paired quotation marks', () => { it('identifies unpaired open quotes', () => { - const isolatedQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze('\u201C'); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelEnglishQuotes(); + + const isolatedQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze('\u201C'); expect(isolatedQuoteAnalysis.getUnmatchedQuotes()).toEqual([ { depth: QuotationDepth.Primary, @@ -108,7 +105,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const singleOpeningQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const singleOpeningQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( 'Sample text with \u201CMore sample text', ); expect(singleOpeningQuoteAnalysis.getUnmatchedQuotes()).toEqual([ @@ -122,7 +119,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const multipleQuotesAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleQuotesAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CSample text\u201D with \u201CMore sample text', ); expect(multipleQuotesAnalysis.getUnmatchedQuotes()).toEqual([ @@ -136,7 +133,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const multipleNestedQuotesAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleNestedQuotesAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CSample text with \u201CMore sample text\u201D', ); expect(multipleNestedQuotesAnalysis.getUnmatchedQuotes()).toEqual([ @@ -152,7 +149,9 @@ describe('QuotationAnalyzer tests', () => { }); it('identifies unpaired closing quotes', () => { - const isolatedQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze('\u201D'); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelEnglishQuotes(); + + const isolatedQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze('\u201D'); expect(isolatedQuoteAnalysis.getUnmatchedQuotes()).toEqual([ { depth: QuotationDepth.Primary, @@ -164,7 +163,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const singleClosingQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const singleClosingQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( 'Sample text with\u201D more sample text', ); expect(singleClosingQuoteAnalysis.getUnmatchedQuotes()).toEqual([ @@ -178,7 +177,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const multipleQuotesAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleQuotesAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( 'Sample text with\u201D \u201Cmore sample text\u201D', ); expect(multipleQuotesAnalysis.getUnmatchedQuotes()).toEqual([ @@ -192,7 +191,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const multipleNestedQuotesAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleNestedQuotesAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CSample text with\u201D more sample text\u201D', ); expect(multipleNestedQuotesAnalysis.getUnmatchedQuotes()).toEqual([ @@ -210,7 +209,9 @@ describe('QuotationAnalyzer tests', () => { describe('Incorectly nested quotation marks', () => { it('identifies an opening primary quote in the middle of a primary quote', () => { - const multipleNestedQuotesAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelEnglishQuotes(); + + const multipleNestedQuotesAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CSample text with\u201C more sample text\u201D', ); expect(multipleNestedQuotesAnalysis.getIncorrectlyNestedQuotes()).toEqual([ @@ -229,47 +230,29 @@ describe('QuotationAnalyzer tests', () => { }); describe('Multi-level English quotation mark tests', () => { - const quotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .mapAmbiguousQuotationMark("'", '\u2018') - .mapAmbiguousQuotationMark("'", '\u2019') - .build(); - const quotationAnalyzer: QuotationAnalyzer = new QuotationAnalyzer(quotationConfig); - describe('No issues for well-formed text', () => { it('produces no issues for well-formed quote pairs', () => { - const twoNestedQuotationPairsAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + + const twoNestedQuotationPairsAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CSample text with \u2018another nested quote.\u2019\u201D', ); expect(twoNestedQuotationPairsAnalysis.getUnmatchedQuotes()).toEqual([]); expect(twoNestedQuotationPairsAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); - const multipleQuotationPairAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleQuotationPairAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CSample \u2018nested quote\u2019\u201D with \u201Canother quote\u201D', ); expect(multipleQuotationPairAnalysis.getUnmatchedQuotes()).toEqual([]); expect(multipleQuotationPairAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); - const multipleNestedQuotationPairAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleNestedQuotationPairAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CSample \u2018nested quote\u2019\u201D with \u201C\u2018another\u2019 nested quote.\u201D', ); expect(multipleNestedQuotationPairAnalysis.getUnmatchedQuotes()).toEqual([]); expect(multipleNestedQuotationPairAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); - const threeLevelNestedQuotationPairAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const threeLevelNestedQuotationPairAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CSample \u2018nested quote with yet another \u201Cnested quote\u201D\u2019\u201D', ); expect(threeLevelNestedQuotationPairAnalysis.getUnmatchedQuotes()).toEqual([]); @@ -277,31 +260,33 @@ describe('QuotationAnalyzer tests', () => { }); it('produces no issues when ambiguous quotation marks are well-formed', () => { - const nestedAmbiguousQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + + const nestedAmbiguousQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '"A quote \'within a quote.\'"', ); expect(nestedAmbiguousQuoteAnalysis.getUnmatchedQuotes()).toEqual([]); expect(nestedAmbiguousQuoteAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); - const threeLevelNestedAmbiguousQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const threeLevelNestedAmbiguousQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '"A quote \'within a quote and "a third."\'"', ); expect(threeLevelNestedAmbiguousQuoteAnalysis.getUnmatchedQuotes()).toEqual([]); expect(threeLevelNestedAmbiguousQuoteAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); - const firstLevelAmbiguousNestedQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const firstLevelAmbiguousNestedQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '"The first level \u2018of quotes\u2019" is ambiguous.', ); expect(firstLevelAmbiguousNestedQuoteAnalysis.getUnmatchedQuotes()).toEqual([]); expect(firstLevelAmbiguousNestedQuoteAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); - const secondLevelAmbiguousNestedQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const secondLevelAmbiguousNestedQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( "\u201CThe second level 'of quotes' is ambiguous.\u201D", ); expect(secondLevelAmbiguousNestedQuoteAnalysis.getUnmatchedQuotes()).toEqual([]); expect(secondLevelAmbiguousNestedQuoteAnalysis.getIncorrectlyNestedQuotes()).toEqual([]); - const thirdLevelAmbiguousNestedQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const thirdLevelAmbiguousNestedQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThe third \u2018level "of quotes" is ambiguous.\u2019\u201D', ); expect(thirdLevelAmbiguousNestedQuoteAnalysis.getUnmatchedQuotes()).toEqual([]); @@ -311,7 +296,9 @@ describe('QuotationAnalyzer tests', () => { describe('Incorrectly paired quotation marks', () => { it('identifies unpaired open quotes', () => { - const unclosedPrimaryQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + + const unclosedPrimaryQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis has a \u2018nested quote\u2019', ); expect(unclosedPrimaryQuoteAnalysis.getUnmatchedQuotes()).toEqual([ @@ -325,7 +312,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const unclosedSecondaryQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const unclosedSecondaryQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis has an \u2018unclosed nested quote\u201D', ); expect(unclosedSecondaryQuoteAnalysis.getUnmatchedQuotes()).toEqual([ @@ -339,7 +326,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const unclosedTertiaryQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const unclosedTertiaryQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis has \u2018an \u201Cunclosed thrid-level\u2019 quote\u201D', ); expect(unclosedTertiaryQuoteAnalysis.getUnmatchedQuotes()).toEqual([ @@ -353,7 +340,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const multipleUnclosedQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleUnclosedQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis has two \u2018unclosed quotes', ); expect(multipleUnclosedQuoteAnalysis.getUnmatchedQuotes()).toEqual([ @@ -375,7 +362,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const multipleUnclosedQuoteAnalysis2: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleUnclosedQuoteAnalysis2: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis has two \u2018unclosed \u201Cquotes\u201D', ); expect(multipleUnclosedQuoteAnalysis2.getUnmatchedQuotes()).toEqual([ @@ -397,7 +384,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const ambiguousUnclosedQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const ambiguousUnclosedQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '"This has an \u2018ambiguous\u2019 unclosed quote', ); expect(ambiguousUnclosedQuoteAnalysis.getUnmatchedQuotes()).toEqual([ @@ -411,7 +398,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const ambiguousUnclosedQuoteAnalysis2: QuotationAnalysis = quotationAnalyzer.analyze( + const ambiguousUnclosedQuoteAnalysis2: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( "\u201CThis has an 'ambiguous unclosed quote\u201D", ); expect(ambiguousUnclosedQuoteAnalysis2.getUnmatchedQuotes()).toEqual([ @@ -424,25 +411,12 @@ describe('QuotationAnalyzer tests', () => { isAutocorrectable: true, }, ]); + }); - // new quotation config with unambiguous 3rd-level quotes - const alternativeQuotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201E', - closingPunctuationMark: '\u201F', - }) - .build(); - const alternativeQuotationAnalyzer: QuotationAnalyzer = new QuotationAnalyzer(alternativeQuotationConfig); - - const unpairedThirdAnalysis: QuotationAnalysis = alternativeQuotationAnalyzer.analyze( + it('identifies unpaired open quotes when the first- and third-level quotes are distinct', () => { + const testEnv: TestEnvironment = TestEnvironment.createWithDifferentFirstAndThirdLevelQuotes(); + + const unpairedThirdAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis text has a \u2018primary \u201Equote\u201D closing inside a tertiary quote', ); expect(unpairedThirdAnalysis.getUnmatchedQuotes()).toEqual([ @@ -464,7 +438,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const unpairedThirdInSecondAnalysis: QuotationAnalysis = alternativeQuotationAnalyzer.analyze( + const unpairedThirdInSecondAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis text has a \u2018primary \u201Equote\u2019 closing inside a tertiary quote\u201D', ); expect(unpairedThirdInSecondAnalysis.getUnmatchedQuotes()).toEqual([ @@ -480,7 +454,9 @@ describe('QuotationAnalyzer tests', () => { }); it('identifies unpaired closing quotes', () => { - const unpairedPrimaryQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + + const unpairedPrimaryQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( 'Text with an \u2018unpaired\u2019 closing quote\u201D', ); expect(unpairedPrimaryQuoteAnalysis.getUnmatchedQuotes()).toEqual([ @@ -494,7 +470,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const unpairedSecondaryQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const unpairedSecondaryQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CText with an unpaired\u2019 closing quote\u201D', ); expect(unpairedSecondaryQuoteAnalysis.getUnmatchedQuotes()).toEqual([ @@ -508,37 +484,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - // new quotation config with unambiguous 3rd-level quotes - const alternativeQuotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201E', - closingPunctuationMark: '\u201F', - }) - .build(); - const alternativeQuotationAnalyzer: QuotationAnalyzer = new QuotationAnalyzer(alternativeQuotationConfig); - const unpairedTertiaryQuoteAnalysis: QuotationAnalysis = alternativeQuotationAnalyzer.analyze( - '\u201CText \u2018with an unpaired\u201F\u2019 closing quote\u201D', - ); - expect(unpairedTertiaryQuoteAnalysis.getUnmatchedQuotes()).toEqual([ - { - depth: QuotationDepth.Tertiary, - direction: PairedPunctuationDirection.Closing, - startIndex: 23, - endIndex: 24, - text: '\u201F', - isAutocorrectable: false, - }, - ]); - - const multipleUnpairedQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleUnpairedQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( 'This text has multiple\u2019 unpaired quotes\u201D', ); expect(multipleUnpairedQuoteAnalysis.getUnmatchedQuotes()).toEqual([ @@ -560,7 +506,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const multipleUnpairedQuoteAnalysis2: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleUnpairedQuoteAnalysis2: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis text has multiple\u201D unpaired\u2019 quotes\u201D', ); expect(multipleUnpairedQuoteAnalysis2.getUnmatchedQuotes()).toEqual([ @@ -582,7 +528,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const ambiguousUnpairedQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const ambiguousUnpairedQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis text has an ambiguous\u201D unpaired" quote', ); expect(ambiguousUnpairedQuoteAnalysis.getUnmatchedQuotes()).toEqual([ @@ -596,7 +542,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const ambiguousUnpairedQuoteAnalysis2: QuotationAnalysis = quotationAnalyzer.analyze( + const ambiguousUnpairedQuoteAnalysis2: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( "\u201CThis text has an ambiguous\u201D unpaired' quote", ); expect(ambiguousUnpairedQuoteAnalysis2.getUnmatchedQuotes()).toEqual([ @@ -610,8 +556,26 @@ describe('QuotationAnalyzer tests', () => { parentDepth: new QuotationRootLevel(), }, ]); + }); + + it('identifies unpaired closing quotes whne the first- and third-level quotes are distinct', () => { + const testEnv: TestEnvironment = TestEnvironment.createWithDifferentFirstAndThirdLevelQuotes(); - const secondInFirstAnalysis: QuotationAnalysis = alternativeQuotationAnalyzer.analyze( + const unpairedTertiaryQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( + '\u201CText \u2018with an unpaired\u201F\u2019 closing quote\u201D', + ); + expect(unpairedTertiaryQuoteAnalysis.getUnmatchedQuotes()).toEqual([ + { + depth: QuotationDepth.Tertiary, + direction: PairedPunctuationDirection.Closing, + startIndex: 23, + endIndex: 24, + text: '\u201F', + isAutocorrectable: false, + }, + ]); + + const secondInFirstAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis text has a\u2019 secondary quote closing inside a primary quote\u201D', ); expect(secondInFirstAnalysis.getUnmatchedQuotes()).toEqual([ @@ -625,7 +589,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const thirdInSecondAnalysis: QuotationAnalysis = alternativeQuotationAnalyzer.analyze( + const thirdInSecondAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis text has a\u2018 tertiary quote\u201F closing inside a secondary quote\u2019\u201D', ); expect(thirdInSecondAnalysis.getUnmatchedQuotes()).toEqual([ @@ -639,7 +603,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const thirdInFirstAnalysis: QuotationAnalysis = alternativeQuotationAnalyzer.analyze( + const thirdInFirstAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis text has a tertiary quote\u201F closing inside a primary quote\u201D', ); expect(thirdInFirstAnalysis.getUnmatchedQuotes()).toEqual([ @@ -657,9 +621,11 @@ describe('QuotationAnalyzer tests', () => { describe('Incorectly nested quotation marks', () => { it("identifies opening quotes that don't match the context", () => { + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + // primary opening quote in the midst of a primary quote was already tested above - const secondInSecondAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const secondInSecondAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis text has a \u2018secondary quote \u2018opening inside a secondary quote', ); expect(secondInSecondAnalysis.getIncorrectlyNestedQuotes()).toEqual([ @@ -674,7 +640,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const secondInSecondAnalysis2: QuotationAnalysis = quotationAnalyzer.analyze( + const secondInSecondAnalysis2: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis text has a \u2018secondary quote \u2018opening inside a secondary quote\u2019\u2019\u201D', ); expect(secondInSecondAnalysis2.getIncorrectlyNestedQuotes()).toEqual([ @@ -689,7 +655,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const thirdInThirdAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const thirdInThirdAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis text has a \u2018tertiary quote \u201Copening \u201Cinside a tertiary quote', ); expect(thirdInThirdAnalysis.getIncorrectlyNestedQuotes()).toEqual([ @@ -704,7 +670,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const secondInSecondAmbiguousAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const secondInSecondAmbiguousAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( "\u201CThis text has a 'secondary quote \u2018opening inside a secondary quote", ); expect(secondInSecondAmbiguousAnalysis.getIncorrectlyNestedQuotes()).toEqual([ @@ -718,25 +684,12 @@ describe('QuotationAnalyzer tests', () => { parentDepth: QuotationDepth.Secondary, }, ]); + }); + + it("identifies opening quotes that don't match the context when the first- and third-level quotes are distinct", () => { + const testEnv: TestEnvironment = TestEnvironment.createWithDifferentFirstAndThirdLevelQuotes(); - // different QuotationConfig to allow us to test tertiary-inside-primary errors - const alternativeQuotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201E', - closingPunctuationMark: '\u201F', - }) - .build(); - const alternativeQuotationAnalyzer: QuotationAnalyzer = new QuotationAnalyzer(alternativeQuotationConfig); - - const tertiaryInPrimaryAnalysis: QuotationAnalysis = alternativeQuotationAnalyzer.analyze( + const tertiaryInPrimaryAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '\u201CThis text has a \u201Etertiary quote opening in a primary context', ); expect(tertiaryInPrimaryAnalysis.getIncorrectlyNestedQuotes()).toEqual([ @@ -754,38 +707,14 @@ describe('QuotationAnalyzer tests', () => { }); describe('Quotes that are nested too deeply', () => { - const quotationConfigBuilder = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .mapAmbiguousQuotationMark("'", '\u2018') - .mapAmbiguousQuotationMark("'", '\u2019'); - it('warns only for quotes that are deeper than the nesting warning depth', () => { - const allQuotesAreTooDeepQuotationConfig: QuotationConfig = quotationConfigBuilder - .setNestingWarningDepth(QuotationDepth.Primary) - .build(); - const allQuotesAreTooDeepAnalyzer: QuotationAnalyzer = new QuotationAnalyzer( - allQuotesAreTooDeepQuotationConfig, - ); + const allQuotesAreTooDeepTestEnv: TestEnvironment = + TestEnvironment.createWithFullEnglishQuotesAndWarningDepth(QuotationDepth.Primary); expect( - allQuotesAreTooDeepAnalyzer.analyze('\u201CFirst level quotes only\u201D').getTooDeeplyNestedQuotes(), + allQuotesAreTooDeepTestEnv.quotationAnalyzer + .analyze('\u201CFirst level quotes only\u201D') + .getTooDeeplyNestedQuotes(), ).toEqual([ { depth: QuotationDepth.Primary, @@ -806,7 +735,7 @@ describe('QuotationAnalyzer tests', () => { ]); expect( - allQuotesAreTooDeepAnalyzer + allQuotesAreTooDeepTestEnv.quotationAnalyzer .analyze('\u201CFirst and \u2018second level\u2019 quotes\u201D') .getTooDeeplyNestedQuotes(), ).toEqual([ @@ -844,19 +773,17 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const secondLevelIsTooDeepQuotationConfig: QuotationConfig = quotationConfigBuilder - .setNestingWarningDepth(QuotationDepth.Secondary) - .build(); - const secondLevelIsTooDeepAnalyzer: QuotationAnalyzer = new QuotationAnalyzer( - secondLevelIsTooDeepQuotationConfig, - ); + const secondLevelIsTooDeepTestEnv: TestEnvironment = + TestEnvironment.createWithFullEnglishQuotesAndWarningDepth(QuotationDepth.Secondary); expect( - secondLevelIsTooDeepAnalyzer.analyze('\u201CFirst level quotes only\u201D').getTooDeeplyNestedQuotes(), + secondLevelIsTooDeepTestEnv.quotationAnalyzer + .analyze('\u201CFirst level quotes only\u201D') + .getTooDeeplyNestedQuotes(), ).toEqual([]); expect( - secondLevelIsTooDeepAnalyzer + secondLevelIsTooDeepTestEnv.quotationAnalyzer .analyze('\u201CFirst and \u2018second level\u2019 quotes\u201D') .getTooDeeplyNestedQuotes(), ).toEqual([ @@ -879,7 +806,7 @@ describe('QuotationAnalyzer tests', () => { ]); expect( - secondLevelIsTooDeepAnalyzer + secondLevelIsTooDeepTestEnv.quotationAnalyzer .analyze('\u201CFirst, \u2018second, and \u201Cthird\u201D level\u2019 quotes\u201D') .getTooDeeplyNestedQuotes(), ).toEqual([ @@ -917,25 +844,23 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const thirdLevelIsTooDeepQuotationConfig: QuotationConfig = quotationConfigBuilder - .setNestingWarningDepth(QuotationDepth.Tertiary) - .build(); - const thirdLevelIsTooDeepAnalyzer: QuotationAnalyzer = new QuotationAnalyzer( - thirdLevelIsTooDeepQuotationConfig, - ); + const thirdLevelIsTooDeepTestEnv: TestEnvironment = + TestEnvironment.createWithFullEnglishQuotesAndWarningDepth(QuotationDepth.Tertiary); expect( - thirdLevelIsTooDeepAnalyzer.analyze('\u201CFirst level quotes only\u201D').getTooDeeplyNestedQuotes(), + thirdLevelIsTooDeepTestEnv.quotationAnalyzer + .analyze('\u201CFirst level quotes only\u201D') + .getTooDeeplyNestedQuotes(), ).toEqual([]); expect( - thirdLevelIsTooDeepAnalyzer + thirdLevelIsTooDeepTestEnv.quotationAnalyzer .analyze('\u201CFirst and \u2018second level\u2019 quotes\u201D') .getTooDeeplyNestedQuotes(), ).toEqual([]); expect( - thirdLevelIsTooDeepAnalyzer + thirdLevelIsTooDeepTestEnv.quotationAnalyzer .analyze('\u201CFirst, \u2018second, and \u201Cthird\u201D level\u2019 quotes\u201D') .getTooDeeplyNestedQuotes(), ).toEqual([ @@ -958,7 +883,7 @@ describe('QuotationAnalyzer tests', () => { ]); expect( - thirdLevelIsTooDeepAnalyzer + thirdLevelIsTooDeepTestEnv.quotationAnalyzer .analyze('\u201CFirst, \u2018second, \u201Cthird, and \u2018fourth\u2019\u201D level\u2019 quotes\u201D') .getTooDeeplyNestedQuotes(), ).toEqual([ @@ -996,31 +921,29 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const fourthLevelIsTooDeepQuotationConfig: QuotationConfig = quotationConfigBuilder - .setNestingWarningDepth(QuotationDepth.fromNumber(4)) - .build(); - const fourthLevelIsTooDeepAnalyzer: QuotationAnalyzer = new QuotationAnalyzer( - fourthLevelIsTooDeepQuotationConfig, - ); + const fourthLevelIsTooDeepTestEnv: TestEnvironment = + TestEnvironment.createWithFullEnglishQuotesAndWarningDepth(QuotationDepth.fromNumber(4)); expect( - fourthLevelIsTooDeepAnalyzer.analyze('\u201CFirst level quotes only\u201D').getTooDeeplyNestedQuotes(), + fourthLevelIsTooDeepTestEnv.quotationAnalyzer + .analyze('\u201CFirst level quotes only\u201D') + .getTooDeeplyNestedQuotes(), ).toEqual([]); expect( - fourthLevelIsTooDeepAnalyzer + fourthLevelIsTooDeepTestEnv.quotationAnalyzer .analyze('\u201CFirst and \u2018second level\u2019 quotes\u201D') .getTooDeeplyNestedQuotes(), ).toEqual([]); expect( - fourthLevelIsTooDeepAnalyzer + fourthLevelIsTooDeepTestEnv.quotationAnalyzer .analyze('\u201CFirst, \u2018second, and \u201Cthird\u201D level\u2019 quotes\u201D') .getTooDeeplyNestedQuotes(), ).toEqual([]); expect( - fourthLevelIsTooDeepAnalyzer + fourthLevelIsTooDeepTestEnv.quotationAnalyzer .analyze('\u201CFirst, \u2018second, \u201Cthird, and \u2018fourth\u2019\u201D level\u2019 quotes\u201D') .getTooDeeplyNestedQuotes(), ).toEqual([ @@ -1044,23 +967,23 @@ describe('QuotationAnalyzer tests', () => { }); it('warns for ambiguous quotes that are deeper than the nesting warning depth', () => { - const thirdLevelIsTooDeepQuotationConfig: QuotationConfig = quotationConfigBuilder - .setNestingWarningDepth(QuotationDepth.Tertiary) - .build(); - const thirdLevelIsTooDeepAnalyzer: QuotationAnalyzer = new QuotationAnalyzer( - thirdLevelIsTooDeepQuotationConfig, - ); + const thirdLevelIsTooDeepTestEnv: TestEnvironment = + TestEnvironment.createWithFullEnglishQuotesAndWarningDepth(QuotationDepth.Tertiary); - expect(thirdLevelIsTooDeepAnalyzer.analyze('"First level quotes only"').getTooDeeplyNestedQuotes()).toEqual( - [], - ); + expect( + thirdLevelIsTooDeepTestEnv.quotationAnalyzer + .analyze('"First level quotes only"') + .getTooDeeplyNestedQuotes(), + ).toEqual([]); expect( - thirdLevelIsTooDeepAnalyzer.analyze('"First and \'second level\' quotes"').getTooDeeplyNestedQuotes(), + thirdLevelIsTooDeepTestEnv.quotationAnalyzer + .analyze('"First and \'second level\' quotes"') + .getTooDeeplyNestedQuotes(), ).toEqual([]); expect( - thirdLevelIsTooDeepAnalyzer + thirdLevelIsTooDeepTestEnv.quotationAnalyzer .analyze('"First, \'second, and "third" level\' quotes"') .getTooDeeplyNestedQuotes(), ).toEqual([ @@ -1083,7 +1006,7 @@ describe('QuotationAnalyzer tests', () => { ]); expect( - thirdLevelIsTooDeepAnalyzer + thirdLevelIsTooDeepTestEnv.quotationAnalyzer .analyze('"First, \'second, "third, and \'fourth\'" level\' quotes"') .getTooDeeplyNestedQuotes(), ).toEqual([ @@ -1121,29 +1044,29 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const fourthLevelIsTooDeepQuotationConfig: QuotationConfig = quotationConfigBuilder - .setNestingWarningDepth(QuotationDepth.fromNumber(4)) - .build(); - const fourthLevelIsTooDeepAnalyzer: QuotationAnalyzer = new QuotationAnalyzer( - fourthLevelIsTooDeepQuotationConfig, - ); + const fourthLevelIsTooDeepTestEnv: TestEnvironment = + TestEnvironment.createWithFullEnglishQuotesAndWarningDepth(QuotationDepth.fromNumber(4)); - expect(fourthLevelIsTooDeepAnalyzer.analyze('"First level quotes only"').getTooDeeplyNestedQuotes()).toEqual( - [], - ); + expect( + fourthLevelIsTooDeepTestEnv.quotationAnalyzer + .analyze('"First level quotes only"') + .getTooDeeplyNestedQuotes(), + ).toEqual([]); expect( - fourthLevelIsTooDeepAnalyzer.analyze('"First and \'second level\' quotes"').getTooDeeplyNestedQuotes(), + fourthLevelIsTooDeepTestEnv.quotationAnalyzer + .analyze('"First and \'second level\' quotes"') + .getTooDeeplyNestedQuotes(), ).toEqual([]); expect( - fourthLevelIsTooDeepAnalyzer + fourthLevelIsTooDeepTestEnv.quotationAnalyzer .analyze('"First, \'second, and "third" level\' quotes"') .getTooDeeplyNestedQuotes(), ).toEqual([]); expect( - fourthLevelIsTooDeepAnalyzer + fourthLevelIsTooDeepTestEnv.quotationAnalyzer .analyze('"First, \'second, "third, and \'fourth\'" level\' quotes"') .getTooDeeplyNestedQuotes(), ).toEqual([ @@ -1170,34 +1093,19 @@ describe('QuotationAnalyzer tests', () => { }); describe('Ambiguous quote correction tests', () => { - const quotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .mapAmbiguousQuotationMark("'", '\u2018') - .mapAmbiguousQuotationMark("'", '\u2019') - .build(); - const quotationAnalyzer: QuotationAnalyzer = new QuotationAnalyzer(quotationConfig); - it('produces no output for unambiguous quotation marks', () => { - const unambiguousQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + + const unambiguousQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( 'Sample \u201Ctext with no \u2018ambiguous\u2019 quotation\u201D marks.', ); expect(unambiguousQuoteAnalysis.getAmbiguousQuoteCorrections()).toEqual([]); }); + it('replaces ambiguous quotation marks with the corresponding unambiguous versions', () => { - const simpleQuoteAnalysis: QuotationAnalysis = quotationAnalyzer.analyze('"'); + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + + const simpleQuoteAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze('"'); expect(simpleQuoteAnalysis.getAmbiguousQuoteCorrections()).toEqual([ { existingQuotationMark: { @@ -1219,7 +1127,7 @@ describe('QuotationAnalyzer tests', () => { }, ]); - const multipleNestedQuotesAnalysis: QuotationAnalysis = quotationAnalyzer.analyze( + const multipleNestedQuotesAnalysis: QuotationAnalysis = testEnv.quotationAnalyzer.analyze( '"Sample text with \u2018ambiguous marks\'', ); @@ -1653,3 +1561,94 @@ describe('QuotationResolver tests', () => { }); }); }); + +class TestEnvironment { + readonly quotationAnalyzer: QuotationAnalyzer; + + private constructor(private readonly quotationConfig: QuotationConfig) { + this.quotationAnalyzer = new QuotationAnalyzer(this.quotationConfig); + } + + static createWithTopLevelEnglishQuotes(): TestEnvironment { + return new TestEnvironment( + new QuotationConfig.Builder() + .setTopLevelQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .mapAmbiguousQuotationMark('"', '\u201C') + .mapAmbiguousQuotationMark('"', '\u201D') + .build(), + ); + } + + static createWithFullEnglishQuotes(): TestEnvironment { + return new TestEnvironment( + new QuotationConfig.Builder() + .setTopLevelQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .mapAmbiguousQuotationMark('"', '\u201C') + .mapAmbiguousQuotationMark('"', '\u201D') + .mapAmbiguousQuotationMark("'", '\u2018') + .mapAmbiguousQuotationMark("'", '\u2019') + .build(), + ); + } + + static createWithDifferentFirstAndThirdLevelQuotes(): TestEnvironment { + return new TestEnvironment( + new QuotationConfig.Builder() + .setTopLevelQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u201E', + closingPunctuationMark: '\u201F', + }) + .build(), + ); + } + + static createWithFullEnglishQuotesAndWarningDepth(warningDepth: QuotationDepth): TestEnvironment { + return new TestEnvironment( + new QuotationConfig.Builder() + .setTopLevelQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .mapAmbiguousQuotationMark('"', '\u201C') + .mapAmbiguousQuotationMark('"', '\u201D') + .mapAmbiguousQuotationMark("'", '\u2018') + .mapAmbiguousQuotationMark("'", '\u2019') + .setNestingWarningDepth(warningDepth) + .build(), + ); + } +} diff --git a/packages/punctuation-checker/test/quotation/quotation-checker.test.ts b/packages/punctuation-checker/test/quotation/quotation-checker.test.ts index 46b670d..921fd20 100644 --- a/packages/punctuation-checker/test/quotation/quotation-checker.test.ts +++ b/packages/punctuation-checker/test/quotation/quotation-checker.test.ts @@ -18,522 +18,355 @@ import { QuotationDepth, QuotationRootLevel } from '../../src/quotation/quotatio import { StringContextMatcher } from '../../src/utils'; import { StubDocumentManager, StubSingleLineTextDocument } from '../test-utils'; -const defaultLocalizer: Localizer = new Localizer(); -defaultLocalizer.addNamespace('quotation', (_language: string) => { - return { - diagnosticMessagesByCode: { - 'unmatched-opening-quotation-mark': 'Opening quotation mark with no closing mark.', - 'unmatched-closing-quotation-mark': 'Closing quotation mark with no opening mark.', - 'incorrectly-nested-quotation-mark': 'Incorrectly nested quotation mark.', - 'ambiguous-quotation-mark': 'This quotation mark is ambiguous.', - 'deeply-nested-quotation-mark': 'Too many levels of quotation marks. Consider rephrasing to avoid this.', - }, - }; -}); -await defaultLocalizer.init(); - -// Functions/objects for creating expected output objects -const stubDiagnosticFactory: DiagnosticFactory = new DiagnosticFactory( - 'quotation-mark-checker', - new StubSingleLineTextDocument(''), // passing an empty document is fine here since we don't use getText() -); - -function createUnmatchedOpeningQuoteDiagnostic(startOffset: number, endOffset: number): Diagnostic { - return { - code: 'unmatched-opening-quotation-mark', - severity: DiagnosticSeverity.Error, - range: { - start: { - line: 0, - character: startOffset, - }, - end: { - line: 0, - character: endOffset, - }, - }, - source: 'quotation-mark-checker', - message: `Opening quotation mark with no closing mark.`, - data: '', - }; -} - -function createUnmatchedClosingQuoteDiagnostic(startOffset: number, endOffset: number): Diagnostic { - return { - code: 'unmatched-closing-quotation-mark', - severity: DiagnosticSeverity.Error, - range: { - start: { - line: 0, - character: startOffset, - }, - end: { - line: 0, - character: endOffset, - }, - }, - source: 'quotation-mark-checker', - message: `Closing quotation mark with no opening mark.`, - data: '', - }; -} - -function createIncorrectlyNestedDiagnostic( - startOffset: number, - endOffset: number, - parentDepth: QuotationDepth, -): Diagnostic { - return { - code: 'incorrectly-nested-quotation-mark', - severity: DiagnosticSeverity.Warning, - range: { - start: { - line: 0, - character: startOffset, - }, - end: { - line: 0, - character: endOffset, - }, - }, - source: 'quotation-mark-checker', - message: `Incorrectly nested quotation mark.`, - data: { - depth: parentDepth.asNumber(), - }, - }; -} - -function createAmbiguousDiagnostic( - startOffset: number, - endOffset: number, - ambiguousMark: string, - unambiguousMark: string, -): Diagnostic { - return { - code: 'ambiguous-quotation-mark', - severity: DiagnosticSeverity.Warning, - range: { - start: { - line: 0, - character: startOffset, - }, - end: { - line: 0, - character: endOffset, - }, - }, - source: 'quotation-mark-checker', - message: `This quotation mark is ambiguous.`, - data: { - existingQuotationMark: ambiguousMark, - correctedQuotationMark: unambiguousMark, - }, - }; -} - -function createTooDeeplyNestedDiagnostic(startOffset: number, endOffset: number): Diagnostic { - return { - code: 'deeply-nested-quotation-mark', - severity: DiagnosticSeverity.Warning, - range: { - start: { - line: 0, - character: startOffset, - }, - end: { - line: 0, - character: endOffset, - }, - }, - source: 'quotation-mark-checker', - message: 'Too many levels of quotation marks. Consider rephrasing to avoid this.', - data: '', - }; -} - describe('QuotationErrorFinder tests', () => { - it('keeps no internal state', () => { - const quotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .build(); - const quotationErrorFinder = new _privateTestingClasses.QuotationErrorFinder( - defaultLocalizer, - quotationConfig, - stubDiagnosticFactory, - ); - expect(quotationErrorFinder.produceDiagnostics('Sample text \u201Dwith more text')).toEqual([ - createUnmatchedClosingQuoteDiagnostic(12, 13), + it('keeps no internal state', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotes(); + await testEnv.init(); + + expect(testEnv.quotationErrorFinder.produceDiagnostics('Sample text \u201Dwith more text')).toEqual([ + testEnv.createUnmatchedClosingQuoteDiagnostic(12, 13), ]); - expect(quotationErrorFinder.produceDiagnostics('Sample text \u201Dwith more text')).toEqual([ - createUnmatchedClosingQuoteDiagnostic(12, 13), + expect(testEnv.quotationErrorFinder.produceDiagnostics('Sample text \u201Dwith more text')).toEqual([ + testEnv.createUnmatchedClosingQuoteDiagnostic(12, 13), ]); - expect(quotationErrorFinder.produceDiagnostics('Sample text with no quote')).toEqual([]); + expect(testEnv.quotationErrorFinder.produceDiagnostics('Sample text with no quote')).toEqual([]); }); describe('For standard English top-level quotes', () => { - const quotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .build(); - - it('creates Diagnostics for unmatched quotation marks', () => { - const quotationErrorFinder = new _privateTestingClasses.QuotationErrorFinder( - defaultLocalizer, - quotationConfig, - stubDiagnosticFactory, - ); - - expect(quotationErrorFinder.produceDiagnostics('Sample text \u201Cwith more text')).toEqual([ - createUnmatchedOpeningQuoteDiagnostic(12, 13), + it('creates Diagnostics for unmatched quotation marks', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotes(); + await testEnv.init(); + + expect(testEnv.quotationErrorFinder.produceDiagnostics('Sample text \u201Cwith more text')).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(12, 13), ]); - expect(quotationErrorFinder.produceDiagnostics('Sample text \u201Dwith more text')).toEqual([ - createUnmatchedClosingQuoteDiagnostic(12, 13), + expect(testEnv.quotationErrorFinder.produceDiagnostics('Sample text \u201Dwith more text')).toEqual([ + testEnv.createUnmatchedClosingQuoteDiagnostic(12, 13), ]); - expect(quotationErrorFinder.produceDiagnostics('\u201CSample text\u201D \u201Cwith more text')).toEqual([ - createUnmatchedOpeningQuoteDiagnostic(14, 15), + expect(testEnv.quotationErrorFinder.produceDiagnostics('\u201CSample text\u201D \u201Cwith more text')).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(14, 15), ]); - expect(quotationErrorFinder.produceDiagnostics('\u201CSample text\u201D \u201Dwith more text')).toEqual([ - createUnmatchedClosingQuoteDiagnostic(14, 15), + expect(testEnv.quotationErrorFinder.produceDiagnostics('\u201CSample text\u201D \u201Dwith more text')).toEqual([ + testEnv.createUnmatchedClosingQuoteDiagnostic(14, 15), ]); }); - it('creates Diagnostics for incorrectly nested quotation marks', () => { - const quotationErrorFinder = new _privateTestingClasses.QuotationErrorFinder( - defaultLocalizer, - quotationConfig, - stubDiagnosticFactory, - ); + it('creates Diagnostics for incorrectly nested quotation marks', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotes(); + await testEnv.init(); - expect(quotationErrorFinder.produceDiagnostics('\u201CSample text \u201Cwith more text\u201D')).toEqual([ - createUnmatchedOpeningQuoteDiagnostic(0, 1), - createIncorrectlyNestedDiagnostic(13, 14, QuotationDepth.Primary), + expect(testEnv.quotationErrorFinder.produceDiagnostics('\u201CSample text \u201Cwith more text\u201D')).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(0, 1), + testEnv.createIncorrectlyNestedDiagnostic(13, 14, QuotationDepth.Primary), ]); }); - it('creates Diagnostics for ambiguous quotation marks', () => { - const quotationErrorFinder = new _privateTestingClasses.QuotationErrorFinder( - defaultLocalizer, - quotationConfig, - stubDiagnosticFactory, - ); + it('creates Diagnostics for ambiguous quotation marks', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotes(); + await testEnv.init(); - expect(quotationErrorFinder.produceDiagnostics('"Sample text"')).toEqual([ - createAmbiguousDiagnostic(0, 1, '"', '\u201C'), - createAmbiguousDiagnostic(12, 13, '"', '\u201D'), + expect(testEnv.quotationErrorFinder.produceDiagnostics('"Sample text"')).toEqual([ + testEnv.createAmbiguousDiagnostic(0, 1, '"', '\u201C'), + testEnv.createAmbiguousDiagnostic(12, 13, '"', '\u201D'), ]); }); }); describe('For multi-level English quotes', () => { - const quotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .mapAmbiguousQuotationMark("'", '\u2018') - .mapAmbiguousQuotationMark("'", '\u2019') - .setNestingWarningDepth(QuotationDepth.fromNumber(4)) - .build(); - - const quotationErrorFinder = new _privateTestingClasses.QuotationErrorFinder( - defaultLocalizer, - quotationConfig, - stubDiagnosticFactory, - ); + it('creates Diagnostics for unmatched opening quotation marks', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + await testEnv.init(); - it('creates Diagnostics for unmatched opening quotation marks', () => { - expect(quotationErrorFinder.produceDiagnostics('\u201CSample text \u201Cwith more text\u201D')).toEqual([ - createUnmatchedOpeningQuoteDiagnostic(0, 1), - createIncorrectlyNestedDiagnostic(13, 14, QuotationDepth.fromNumber(1)), + expect(testEnv.quotationErrorFinder.produceDiagnostics('\u201CSample text \u201Cwith more text\u201D')).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(0, 1), + testEnv.createIncorrectlyNestedDiagnostic(13, 14, QuotationDepth.fromNumber(1)), ]); expect( - quotationErrorFinder.produceDiagnostics('\u201CThis contains two \u2018levels of unclosed quotes'), - ).toEqual([createUnmatchedOpeningQuoteDiagnostic(0, 1), createUnmatchedOpeningQuoteDiagnostic(19, 20)]); + testEnv.quotationErrorFinder.produceDiagnostics('\u201CThis contains two \u2018levels of unclosed quotes'), + ).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(0, 1), + testEnv.createUnmatchedOpeningQuoteDiagnostic(19, 20), + ]); expect( - quotationErrorFinder.produceDiagnostics('\u201CThis contains an \u2018unclosed second level quote\u201D'), - ).toEqual([createUnmatchedOpeningQuoteDiagnostic(18, 19)]); + testEnv.quotationErrorFinder.produceDiagnostics( + '\u201CThis contains an \u2018unclosed second level quote\u201D', + ), + ).toEqual([testEnv.createUnmatchedOpeningQuoteDiagnostic(18, 19)]); expect( - quotationErrorFinder.produceDiagnostics('\u201CThis contains three \u2018levels of unclosed \u201Cquotes'), + testEnv.quotationErrorFinder.produceDiagnostics( + '\u201CThis contains three \u2018levels of unclosed \u201Cquotes', + ), ).toEqual([ - createUnmatchedOpeningQuoteDiagnostic(0, 1), - createUnmatchedOpeningQuoteDiagnostic(21, 22), - createUnmatchedOpeningQuoteDiagnostic(41, 42), + testEnv.createUnmatchedOpeningQuoteDiagnostic(0, 1), + testEnv.createUnmatchedOpeningQuoteDiagnostic(21, 22), + testEnv.createUnmatchedOpeningQuoteDiagnostic(41, 42), ]); expect( - quotationErrorFinder.produceDiagnostics('\u201CThis contains two \u2018levels of unclosed \u201Cquotes\u201D'), - ).toEqual([createUnmatchedOpeningQuoteDiagnostic(0, 1), createUnmatchedOpeningQuoteDiagnostic(19, 20)]); + testEnv.quotationErrorFinder.produceDiagnostics( + '\u201CThis contains two \u2018levels of unclosed \u201Cquotes\u201D', + ), + ).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(0, 1), + testEnv.createUnmatchedOpeningQuoteDiagnostic(19, 20), + ]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CThis contains one \u2018level of unclosed \u201Cquotes\u201D\u2019', ), - ).toEqual([createUnmatchedOpeningQuoteDiagnostic(0, 1)]); + ).toEqual([testEnv.createUnmatchedOpeningQuoteDiagnostic(0, 1)]); expect( - quotationErrorFinder.produceDiagnostics('\u201CThis contains a nested \u2018unclosed quote\u201D'), - ).toEqual([createUnmatchedOpeningQuoteDiagnostic(24, 25)]); + testEnv.quotationErrorFinder.produceDiagnostics('\u201CThis contains a nested \u2018unclosed quote\u201D'), + ).toEqual([testEnv.createUnmatchedOpeningQuoteDiagnostic(24, 25)]); - expect(quotationErrorFinder.produceDiagnostics('"This has an \u2018ambiguous\u2019 unclosed quote')).toEqual([ - createUnmatchedOpeningQuoteDiagnostic(0, 1), - createAmbiguousDiagnostic(0, 1, '"', '\u201C'), + expect( + testEnv.quotationErrorFinder.produceDiagnostics('"This has an \u2018ambiguous unclosed\u2019 quote'), + ).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(0, 1), + testEnv.createAmbiguousDiagnostic(0, 1, '"', '\u201C'), ]); - expect(quotationErrorFinder.produceDiagnostics("\u201CThis has an 'ambiguous unclosed quote\u201D")).toEqual([ - createUnmatchedOpeningQuoteDiagnostic(13, 14), - createAmbiguousDiagnostic(13, 14, "'", '\u2018'), + expect( + testEnv.quotationErrorFinder.produceDiagnostics("\u201CThis has an 'ambiguous unclosed quote\u201D"), + ).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(13, 14), + testEnv.createAmbiguousDiagnostic(13, 14, "'", '\u2018'), ]); - expect(quotationErrorFinder.produceDiagnostics('\u201CThis text has an ambiguous\u201D unpaired" quote')).toEqual( - [createUnmatchedOpeningQuoteDiagnostic(37, 38), createAmbiguousDiagnostic(37, 38, '"', '\u201C')], - ); + expect( + testEnv.quotationErrorFinder.produceDiagnostics('\u201CThis text has an ambiguous\u201D unpaired" quote'), + ).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(37, 38), + testEnv.createAmbiguousDiagnostic(37, 38, '"', '\u201C'), + ]); - expect(quotationErrorFinder.produceDiagnostics("\u201CThis text has an ambiguous\u201D unpaired' quote")).toEqual( - [ - createUnmatchedOpeningQuoteDiagnostic(37, 38), - createIncorrectlyNestedDiagnostic(37, 38, new QuotationRootLevel()), - createAmbiguousDiagnostic(37, 38, "'", '\u2018'), - ], - ); + expect( + testEnv.quotationErrorFinder.produceDiagnostics("\u201CThis text has an ambiguous\u201D unpaired' quote"), + ).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(37, 38), + testEnv.createIncorrectlyNestedDiagnostic(37, 38, new QuotationRootLevel()), + testEnv.createAmbiguousDiagnostic(37, 38, "'", '\u2018'), + ]); }); - it('creates Diagnostics for unmatched closing quotation marks', () => { - expect(quotationErrorFinder.produceDiagnostics('Text with an \u2018unpaired\u2019 closing quote\u201D')).toEqual([ - createUnmatchedClosingQuoteDiagnostic(37, 38), - createIncorrectlyNestedDiagnostic(13, 14, new QuotationRootLevel()), - ]); + it('creates Diagnostics for unmatched closing quotation marks', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + await testEnv.init(); - expect(quotationErrorFinder.produceDiagnostics('\u201CText with an unpaired\u2019 closing quote\u201D')).toEqual([ - createUnmatchedClosingQuoteDiagnostic(22, 23), + expect( + testEnv.quotationErrorFinder.produceDiagnostics('Text with an \u2018unpaired\u2019 closing quote\u201D'), + ).toEqual([ + testEnv.createUnmatchedClosingQuoteDiagnostic(37, 38), + testEnv.createIncorrectlyNestedDiagnostic(13, 14, new QuotationRootLevel()), ]); - expect(quotationErrorFinder.produceDiagnostics('This text has multiple\u2019 unpaired quotes\u201D')).toEqual([ - createUnmatchedClosingQuoteDiagnostic(22, 23), - createUnmatchedClosingQuoteDiagnostic(39, 40), + expect( + testEnv.quotationErrorFinder.produceDiagnostics('\u201CText with an unpaired\u2019 closing quote\u201D'), + ).toEqual([testEnv.createUnmatchedClosingQuoteDiagnostic(22, 23)]); + + expect( + testEnv.quotationErrorFinder.produceDiagnostics('This text has multiple\u2019 unpaired quotes\u201D'), + ).toEqual([ + testEnv.createUnmatchedClosingQuoteDiagnostic(22, 23), + testEnv.createUnmatchedClosingQuoteDiagnostic(39, 40), ]); expect( - quotationErrorFinder.produceDiagnostics('\u201CThis text has multiple\u201D unpaired\u2019 quotes\u201D'), - ).toEqual([createUnmatchedClosingQuoteDiagnostic(33, 34), createUnmatchedClosingQuoteDiagnostic(41, 42)]); + testEnv.quotationErrorFinder.produceDiagnostics( + '\u201CThis text has multiple\u201D unpaired\u2019 quotes\u201D', + ), + ).toEqual([ + testEnv.createUnmatchedClosingQuoteDiagnostic(33, 34), + testEnv.createUnmatchedClosingQuoteDiagnostic(41, 42), + ]); }); - it('creates Diagnostics for incorrectly nested quotation marks', () => { - expect(quotationErrorFinder.produceDiagnostics('\u2018\u2019')).toEqual([ - createIncorrectlyNestedDiagnostic(0, 1, new QuotationRootLevel()), + it('creates Diagnostics for incorrectly nested quotation marks', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + await testEnv.init(); + + expect(testEnv.quotationErrorFinder.produceDiagnostics('\u2018\u2019')).toEqual([ + testEnv.createIncorrectlyNestedDiagnostic(0, 1, new QuotationRootLevel()), ]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CThis text has a \u2018secondary quote \u2018opening inside a secondary quote\u2019\u2019\u201D', ), ).toEqual([ - createIncorrectlyNestedDiagnostic(34, 35, QuotationDepth.fromNumber(2)), - createTooDeeplyNestedDiagnostic(34, 35), - createTooDeeplyNestedDiagnostic(67, 68), + testEnv.createIncorrectlyNestedDiagnostic(34, 35, QuotationDepth.fromNumber(2)), + testEnv.createTooDeeplyNestedDiagnostic(34, 35), + testEnv.createTooDeeplyNestedDiagnostic(67, 68), ]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CThis text has a \u2018tertiary quote \u201Copening \u201Cinside a tertiary quote\u201D\u201D\u2019\u201D', ), - ).toEqual([createIncorrectlyNestedDiagnostic(42, 43, QuotationDepth.fromNumber(3))]); + ).toEqual([testEnv.createIncorrectlyNestedDiagnostic(42, 43, QuotationDepth.fromNumber(3))]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( "\u201CThis text has a 'secondary quote \u2018opening inside a secondary quote\u2019\u2019\u201D", ), ).toEqual([ - createIncorrectlyNestedDiagnostic(34, 35, QuotationDepth.fromNumber(2)), - createAmbiguousDiagnostic(17, 18, "'", '\u2018'), - createTooDeeplyNestedDiagnostic(34, 35), - createTooDeeplyNestedDiagnostic(67, 68), + testEnv.createIncorrectlyNestedDiagnostic(34, 35, QuotationDepth.fromNumber(2)), + testEnv.createAmbiguousDiagnostic(17, 18, "'", '\u2018'), + testEnv.createTooDeeplyNestedDiagnostic(34, 35), + testEnv.createTooDeeplyNestedDiagnostic(67, 68), ]); }); - it('creates Diagnostics for ambiguous quotation marks', () => { - expect(quotationErrorFinder.produceDiagnostics("\u201CSample 'text'\u201D")).toEqual([ - createAmbiguousDiagnostic(8, 9, "'", '\u2018'), - createAmbiguousDiagnostic(13, 14, "'", '\u2019'), + it('creates Diagnostics for ambiguous quotation marks', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + await testEnv.init(); + + expect(testEnv.quotationErrorFinder.produceDiagnostics("\u201CSample 'text'\u201D")).toEqual([ + testEnv.createAmbiguousDiagnostic(8, 9, "'", '\u2018'), + testEnv.createAmbiguousDiagnostic(13, 14, "'", '\u2019'), ]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CSample \u2018text with an "ambiguous" third level quote\u2019\u201D', ), - ).toEqual([createAmbiguousDiagnostic(22, 23, '"', '\u201C'), createAmbiguousDiagnostic(32, 33, '"', '\u201D')]); + ).toEqual([ + testEnv.createAmbiguousDiagnostic(22, 23, '"', '\u201C'), + testEnv.createAmbiguousDiagnostic(32, 33, '"', '\u201D'), + ]); - expect(quotationErrorFinder.produceDiagnostics('\u201CText with mixed \'ambiguous\u2019 quotes"')).toEqual([ - createAmbiguousDiagnostic(17, 18, "'", '\u2018'), - createAmbiguousDiagnostic(35, 36, '"', '\u201D'), + expect( + testEnv.quotationErrorFinder.produceDiagnostics('\u201CText with \'mixed\u2019 ambiguous quotes"'), + ).toEqual([ + testEnv.createAmbiguousDiagnostic(11, 12, "'", '\u2018'), + testEnv.createAmbiguousDiagnostic(35, 36, '"', '\u201D'), ]); }); - it('creates Diagnostics for quotation marks that are too deeply nested', () => { + it('creates Diagnostics for quotation marks that are too deeply nested', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + await testEnv.init(); + expect( - quotationErrorFinder.produceDiagnostics( - '\u201CThis \u2018has \u201Cfour \u2018levels\u2019 of\u201D quotes\u2019\u201D', + testEnv.quotationErrorFinder.produceDiagnostics( + '\u201CThis \u2018has \u201Cfour \u2018level\u2019 of\u201D quotes\u2019\u201D', ), - ).toEqual([createTooDeeplyNestedDiagnostic(17, 18), createTooDeeplyNestedDiagnostic(24, 25)]); - - expect(quotationErrorFinder.produceDiagnostics('"This \'has "four \'levels\' of" quotes\'"')).toEqual([ - createAmbiguousDiagnostic(0, 1, '"', '\u201C'), - createAmbiguousDiagnostic(6, 7, "'", '\u2018'), - createAmbiguousDiagnostic(11, 12, '"', '\u201C'), - createAmbiguousDiagnostic(17, 18, "'", '\u2018'), - createAmbiguousDiagnostic(24, 25, "'", '\u2019'), - createAmbiguousDiagnostic(28, 29, '"', '\u201D'), - createAmbiguousDiagnostic(36, 37, "'", '\u2019'), - createAmbiguousDiagnostic(37, 38, '"', '\u201D'), - createTooDeeplyNestedDiagnostic(17, 18), - createTooDeeplyNestedDiagnostic(24, 25), + ).toEqual([testEnv.createTooDeeplyNestedDiagnostic(17, 18), testEnv.createTooDeeplyNestedDiagnostic(23, 24)]); + + expect(testEnv.quotationErrorFinder.produceDiagnostics('"This \'has "four \'level\' of" quotes\'"')).toEqual([ + testEnv.createAmbiguousDiagnostic(0, 1, '"', '\u201C'), + testEnv.createAmbiguousDiagnostic(6, 7, "'", '\u2018'), + testEnv.createAmbiguousDiagnostic(11, 12, '"', '\u201C'), + testEnv.createAmbiguousDiagnostic(17, 18, "'", '\u2018'), + testEnv.createAmbiguousDiagnostic(23, 24, "'", '\u2019'), + testEnv.createAmbiguousDiagnostic(27, 28, '"', '\u201D'), + testEnv.createAmbiguousDiagnostic(35, 36, "'", '\u2019'), + testEnv.createAmbiguousDiagnostic(36, 37, '"', '\u201D'), + testEnv.createTooDeeplyNestedDiagnostic(17, 18), + testEnv.createTooDeeplyNestedDiagnostic(23, 24), ]); }); }); describe('For a system with different 1st- and 3rd-level quotes', () => { - const quotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201E', - closingPunctuationMark: '\u201F', - }) - .build(); - const quotationErrorFinder = new _privateTestingClasses.QuotationErrorFinder( - defaultLocalizer, - quotationConfig, - stubDiagnosticFactory, - ); + it('creates Diagnostics for unmatched opening quotation marks', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithDifferentThirdLevelQuotes(); + await testEnv.init(); - it('creates Diagnostics for unmatched opening quotation marks', () => { expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CThis text has a \u2018primary \u201Equote\u201D closing inside a tertiary quote', ), - ).toEqual([createUnmatchedOpeningQuoteDiagnostic(26, 27), createUnmatchedOpeningQuoteDiagnostic(17, 18)]); + ).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(26, 27), + testEnv.createUnmatchedOpeningQuoteDiagnostic(17, 18), + ]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CThis text has a \u2018primary \u201Equote\u2019 closing inside a tertiary quote\u201D', ), - ).toEqual([createUnmatchedOpeningQuoteDiagnostic(26, 27)]); + ).toEqual([testEnv.createUnmatchedOpeningQuoteDiagnostic(26, 27)]); }); - it('creates Diagnostics for unmatched closing quotation marks', () => { + it('creates Diagnostics for unmatched closing quotation marks', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithDifferentThirdLevelQuotes(); + await testEnv.init(); + expect( - quotationErrorFinder.produceDiagnostics('\u201CText \u2018with an unpaired\u201F\u2019 closing quote\u201D'), - ).toEqual([createUnmatchedClosingQuoteDiagnostic(23, 24)]); + testEnv.quotationErrorFinder.produceDiagnostics( + '\u201CText \u2018with an unpaired\u201F\u2019 closing quote\u201D', + ), + ).toEqual([testEnv.createUnmatchedClosingQuoteDiagnostic(23, 24)]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CThis text has a\u2019 secondary quote closing inside a primary quote\u201D', ), - ).toEqual([createUnmatchedClosingQuoteDiagnostic(16, 17)]); + ).toEqual([testEnv.createUnmatchedClosingQuoteDiagnostic(16, 17)]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CThis text has a\u2018 tertiary quote\u201F closing inside a secondary quote\u2019\u201D', ), - ).toEqual([createUnmatchedClosingQuoteDiagnostic(32, 33)]); + ).toEqual([testEnv.createUnmatchedClosingQuoteDiagnostic(32, 33)]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CThis text has a tertiary quote\u201F closing inside a primary quote\u201D', ), - ).toEqual([createUnmatchedClosingQuoteDiagnostic(31, 32)]); + ).toEqual([testEnv.createUnmatchedClosingQuoteDiagnostic(31, 32)]); }); - it('creates Diagnostics for incorrectly nested quotation marks', () => { - expect(quotationErrorFinder.produceDiagnostics('\u201E\u201F')).toEqual([ - createIncorrectlyNestedDiagnostic(0, 1, new QuotationRootLevel()), + it('creates Diagnostics for incorrectly nested quotation marks', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithDifferentThirdLevelQuotes(); + await testEnv.init(); + + expect(testEnv.quotationErrorFinder.produceDiagnostics('\u201E\u201F')).toEqual([ + testEnv.createIncorrectlyNestedDiagnostic(0, 1, new QuotationRootLevel()), ]); - expect(quotationErrorFinder.produceDiagnostics('\u201C\u201E\u201F\u201D')).toEqual([ - createIncorrectlyNestedDiagnostic(1, 2, QuotationDepth.fromNumber(1)), + expect(testEnv.quotationErrorFinder.produceDiagnostics('\u201C\u201E\u201F\u201D')).toEqual([ + testEnv.createIncorrectlyNestedDiagnostic(1, 2, QuotationDepth.fromNumber(1)), ]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CThis text has a \u201Etertiary quote opening in a primary context\u201F\u201D', ), - ).toEqual([createIncorrectlyNestedDiagnostic(17, 18, QuotationDepth.fromNumber(1))]); + ).toEqual([testEnv.createIncorrectlyNestedDiagnostic(17, 18, QuotationDepth.fromNumber(1))]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CThis text \u2018contains a \u201Esecond-level \u2018quote\u2019 inside a third-level quote\u201F\u2019\u201D', ), - ).toEqual([createIncorrectlyNestedDiagnostic(37, 38, QuotationDepth.fromNumber(3))]); + ).toEqual([testEnv.createIncorrectlyNestedDiagnostic(37, 38, QuotationDepth.fromNumber(3))]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CThis text \u2018contains a \u201Efirst-level \u201Cquote\u201D inside a third-level quote\u201F\u2019\u201D', ), - ).toEqual([createIncorrectlyNestedDiagnostic(36, 37, QuotationDepth.fromNumber(3))]); + ).toEqual([testEnv.createIncorrectlyNestedDiagnostic(36, 37, QuotationDepth.fromNumber(3))]); expect( - quotationErrorFinder.produceDiagnostics( + testEnv.quotationErrorFinder.produceDiagnostics( '\u201CThis text \u2018contains a first-level \u201Cquote\u201D inside a second-level quote\u2019\u201D', ), - ).toEqual([createIncorrectlyNestedDiagnostic(35, 36, QuotationDepth.fromNumber(2))]); + ).toEqual([testEnv.createIncorrectlyNestedDiagnostic(35, 36, QuotationDepth.fromNumber(2))]); }); }); }); describe('QuotationChecker tests', () => { - const stubDocumentManager: DocumentManager = new StubDocumentManager(new TextDocumentFactory()); - const quotationConfig: QuotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201E', - closingPunctuationMark: '\u201F', - }) - .build(); - it('provides DiagnosticFixes to remove the character for mismatched quotes', async () => { - const localizer: Localizer = new Localizer(); - const quotationChecker: QuotationChecker = new QuotationChecker(localizer, stubDocumentManager, quotationConfig); - await quotationChecker.init(); - await localizer.init(); + const testEnv: TestEnvironment = TestEnvironment.createWithDifferentThirdLevelQuotes(); + await testEnv.init(); + const unmatchedOpeningQuoteDiagnostic: Diagnostic = { code: 'unmatched-opening-quotation-mark', severity: DiagnosticSeverity.Error, @@ -568,7 +401,7 @@ describe('QuotationChecker tests', () => { message: `Opening quotation mark with no closing mark.`, }; - expect(await quotationChecker.getDiagnosticFixes('', unmatchedOpeningQuoteDiagnostic)).toEqual([ + expect(await testEnv.quotationChecker.getDiagnosticFixes('', unmatchedOpeningQuoteDiagnostic)).toEqual([ { title: 'Delete punctuation mark', isPreferred: false, @@ -582,7 +415,7 @@ describe('QuotationChecker tests', () => { }, ]); - expect(await quotationChecker.getDiagnosticFixes('', unmatchedClosingQuoteDiagnostic)).toEqual([ + expect(await testEnv.quotationChecker.getDiagnosticFixes('', unmatchedClosingQuoteDiagnostic)).toEqual([ { title: 'Delete punctuation mark', isPreferred: false, @@ -598,10 +431,8 @@ describe('QuotationChecker tests', () => { }); it('provides DiagnosticFixes to remove or replace the character for incorrectly nested quotes', async () => { - const localizer: Localizer = new Localizer(); - const quotationChecker: QuotationChecker = new QuotationChecker(localizer, stubDocumentManager, quotationConfig); - await quotationChecker.init(); - await localizer.init(); + const testEnv: TestEnvironment = TestEnvironment.createWithDifferentThirdLevelQuotes(); + await testEnv.init(); const incorrectlyNestedDiagnostic: Diagnostic = { code: 'incorrectly-nested-quotation-mark', @@ -623,7 +454,7 @@ describe('QuotationChecker tests', () => { }, }; - expect(await quotationChecker.getDiagnosticFixes('', incorrectlyNestedDiagnostic)).toEqual([ + expect(await testEnv.quotationChecker.getDiagnosticFixes('', incorrectlyNestedDiagnostic)).toEqual([ { title: 'Delete punctuation mark', isPreferred: false, @@ -650,10 +481,8 @@ describe('QuotationChecker tests', () => { }); it('provides DiagnosticFixes to replace the character for ambiguous quotes', async () => { - const localizer: Localizer = new Localizer(); - const quotationChecker: QuotationChecker = new QuotationChecker(localizer, stubDocumentManager, quotationConfig); - await quotationChecker.init(); - await localizer.init(); + const testEnv: TestEnvironment = TestEnvironment.createWithDifferentThirdLevelQuotes(); + await testEnv.init(); const ambiguousDiagnostic: Diagnostic = { code: 'ambiguous-quotation-mark', @@ -676,7 +505,7 @@ describe('QuotationChecker tests', () => { }, }; - expect(await quotationChecker.getDiagnosticFixes('', ambiguousDiagnostic)).toEqual([ + expect(await testEnv.quotationChecker.getDiagnosticFixes('', ambiguousDiagnostic)).toEqual([ { title: 'Replace this character with \u201C', isPreferred: true, @@ -692,12 +521,8 @@ describe('QuotationChecker tests', () => { }); it('provides no DiagnosticFixes for too deeply nested quotes', async () => { - const quotationChecker: QuotationChecker = new QuotationChecker( - defaultLocalizer, - stubDocumentManager, - quotationConfig, - ); - await quotationChecker.init(); + const testEnv: TestEnvironment = TestEnvironment.createWithDifferentThirdLevelQuotes(); + await testEnv.init(); const tooDeeplyNestedDiagnostic: Diagnostic = { code: 'deeply-nested-quotation-mark', @@ -716,63 +541,16 @@ describe('QuotationChecker tests', () => { message: `Too many levels of quotation marks. Consider rephrasing to avoid this.`, }; - expect(await quotationChecker.getDiagnosticFixes('', tooDeeplyNestedDiagnostic)).toEqual([]); + expect(await testEnv.quotationChecker.getDiagnosticFixes('', tooDeeplyNestedDiagnostic)).toEqual([]); }); }); describe('ScriptureDocument tests', () => { - const stylesheet = new UsfmStylesheet('usfm.sty'); - const documentFactory = new UsfmDocumentFactory(stylesheet); - const quotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .mapAmbiguousQuotationMark("'", '\u2018') - .mapAmbiguousQuotationMark("'", '\u2019') - .ignoreMatchingQuotationMarks( - // possessives and contractions - new StringContextMatcher.Builder() - .setCenterContent(/^['\u2019]$/) - .setLeftContext(/\w$/) - .setRightContext(/^\w/) - .build(), - ) - .ignoreMatchingQuotationMarks( - // for possessives ending in "s", e.g. "Moses'" - new StringContextMatcher.Builder() - .setCenterContent(/^['\u2019]$/) - .setLeftContext(/\ws$/) - .setRightContext(/^[ \n,.:;]/) - .build(), - ) - .setNestingWarningDepth(QuotationDepth.fromNumber(4)) - .build(); - const quotationErrorFinder = new _privateTestingClasses.QuotationErrorFinder( - defaultLocalizer, - quotationConfig, - stubDiagnosticFactory, - ); - - it('produces no errors for well-formed text', () => { - const scriptureDocument: ScriptureDocument = documentFactory.create( - 'test-uri', - 'usfm', - 1, + it('produces no errors for well-formed text', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + await testEnv.init(); + + const scriptureDocument: ScriptureDocument = testEnv.createScriptureDocument( `\\id GEN \\toc3 Gen \\toc2 Genesis @@ -785,14 +563,14 @@ describe('ScriptureDocument tests', () => { \\v 1 The servant said to him, “Perhaps the woman may not be willing to follow me to this land. Must I then take your son back to the land from which you came?”`, ); - expect(quotationErrorFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([]); + expect(testEnv.quotationErrorFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([]); }); - it('identifies quotation errors in a single verse', () => { - const scriptureDocument: ScriptureDocument = documentFactory.create( - 'test-uri', - 'usfm', - 1, + it('identifies quotation errors in a single verse', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + await testEnv.init(); + + const scriptureDocument: ScriptureDocument = testEnv.createScriptureDocument( `\\id GEN \\toc3 Gen \\toc2 Genesis @@ -805,16 +583,16 @@ describe('ScriptureDocument tests', () => { \\v 1 The servant said to him, “Perhaps the woman may not be ‘willing to follow me to this land. Must I then take your son back to the land from which you came?”`, ); - expect(quotationErrorFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([ - createUnmatchedOpeningQuoteDiagnostic(215, 216), + expect(testEnv.quotationErrorFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(215, 216), ]); }); - it('identifies quotation errors that occur in non-verse portions', () => { - const scriptureDocument: ScriptureDocument = documentFactory.create( - 'test-uri', - 'usfm', - 1, + it('identifies quotation errors that occur in non-verse portions', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + await testEnv.init(); + + const scriptureDocument: ScriptureDocument = testEnv.createScriptureDocument( `\\id GEN \\toc3 “Gen \\toc2 Genesis” @@ -827,17 +605,17 @@ describe('ScriptureDocument tests', () => { \\v 1 The servant said to him, “Perhaps the woman may not be willing to follow me to this land. Must I then take your son back to the land from which you came?”`, ); - expect(quotationErrorFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([ - createUnmatchedOpeningQuoteDiagnostic(128, 129), - createIncorrectlyNestedDiagnostic(192, 193, QuotationDepth.Primary), + expect(testEnv.quotationErrorFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([ + testEnv.createUnmatchedOpeningQuoteDiagnostic(128, 129), + testEnv.createIncorrectlyNestedDiagnostic(192, 193, QuotationDepth.Primary), ]); }); - it('produces no issues for well-formed quotes that span across verses', () => { - const scriptureDocument: ScriptureDocument = documentFactory.create( - 'test-uri', - 'usfm', - 1, + it('produces no issues for well-formed quotes that span across verses', async () => { + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + await testEnv.init(); + + const scriptureDocument: ScriptureDocument = testEnv.createScriptureDocument( `\\id GEN \\toc3 Gen \\toc2 Genesis @@ -852,6 +630,253 @@ describe('ScriptureDocument tests', () => { \v 3 But if the woman is not willing to follow you, then you will be free from this oath of mine; only you must not take my son back there.”`, ); - expect(quotationErrorFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([]); + expect(testEnv.quotationErrorFinder.produceDiagnostics(scriptureDocument.getText())).toEqual([]); }); }); + +class TestEnvironment { + readonly quotationErrorFinder; + readonly quotationChecker: QuotationChecker; + + private readonly quotationErrorFinderLocalizer: Localizer; // we have separate localizers for the two classes + private readonly quotationCheckerLocalizer: Localizer; // since QuotationChecker populates the localizer on its own + + private readonly scriptureDocumentFactory: UsfmDocumentFactory; + + constructor(private readonly quotationConfig: QuotationConfig) { + this.quotationErrorFinderLocalizer = this.createDefaultLocalizer(); + this.quotationCheckerLocalizer = new Localizer(); + const stubDiagnosticFactory = this.createStubDiagnosticFactory(); + + this.quotationErrorFinder = new _privateTestingClasses.QuotationErrorFinder( + this.quotationErrorFinderLocalizer, + this.quotationConfig, + stubDiagnosticFactory, + ); + + const stubDocumentManager: DocumentManager = new StubDocumentManager(new TextDocumentFactory()); + this.quotationChecker = new QuotationChecker(this.quotationCheckerLocalizer, stubDocumentManager, quotationConfig); + + const stylesheet = new UsfmStylesheet('usfm.sty'); + this.scriptureDocumentFactory = new UsfmDocumentFactory(stylesheet); + } + + public async init(): Promise { + await this.quotationChecker.init(); + await this.quotationErrorFinderLocalizer.init(); + await this.quotationCheckerLocalizer.init(); + } + + private createDefaultLocalizer(): Localizer { + const defaultLocalizer: Localizer = new Localizer(); + defaultLocalizer.addNamespace('quotation', (_language: string) => { + return { + diagnosticMessagesByCode: { + 'unmatched-opening-quotation-mark': 'Opening quotation mark with no closing mark.', + 'unmatched-closing-quotation-mark': 'Closing quotation mark with no opening mark.', + 'incorrectly-nested-quotation-mark': 'Incorrectly nested quotation mark.', + 'ambiguous-quotation-mark': 'This quotation mark is ambiguous.', + 'deeply-nested-quotation-mark': 'Too many levels of quotation marks. Consider rephrasing to avoid this.', + }, + }; + }); + return defaultLocalizer; + } + + private createStubDiagnosticFactory(): DiagnosticFactory { + return new DiagnosticFactory( + 'quotation-mark-checker', + new StubSingleLineTextDocument(''), // passing an empty document is fine here since we don't use getText() + ); + } + + static createWithTopLevelQuotes() { + return new TestEnvironment( + new QuotationConfig.Builder() + .setTopLevelQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .mapAmbiguousQuotationMark('"', '\u201C') + .mapAmbiguousQuotationMark('"', '\u201D') + .build(), + ); + } + + static createWithFullEnglishQuotes() { + return new TestEnvironment( + new QuotationConfig.Builder() + .setTopLevelQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .mapAmbiguousQuotationMark('"', '\u201C') + .mapAmbiguousQuotationMark('"', '\u201D') + .mapAmbiguousQuotationMark("'", '\u2018') + .mapAmbiguousQuotationMark("'", '\u2019') + .ignoreMatchingQuotationMarks( + // possessives and contractions + new StringContextMatcher.Builder() + .setCenterContent(/^['\u2019]$/) + .setLeftContext(/\w$/) + .setRightContext(/^\w/) + .build(), + ) + .ignoreMatchingQuotationMarks( + // for possessives ending in "s", e.g. "Moses'" + new StringContextMatcher.Builder() + .setCenterContent(/^['\u2019]$/) + .setLeftContext(/\ws$/) + .setRightContext(/^[ \n,.:;]/) + .build(), + ) + .setNestingWarningDepth(QuotationDepth.fromNumber(4)) + .build(), + ); + } + + static createWithDifferentThirdLevelQuotes() { + return new TestEnvironment( + new QuotationConfig.Builder() + .setTopLevelQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u201E', + closingPunctuationMark: '\u201F', + }) + .build(), + ); + } + + createUnmatchedOpeningQuoteDiagnostic(startOffset: number, endOffset: number): Diagnostic { + return { + code: 'unmatched-opening-quotation-mark', + severity: DiagnosticSeverity.Error, + range: { + start: { + line: 0, + character: startOffset, + }, + end: { + line: 0, + character: endOffset, + }, + }, + source: 'quotation-mark-checker', + message: `Opening quotation mark with no closing mark.`, + data: '', + }; + } + + createUnmatchedClosingQuoteDiagnostic(startOffset: number, endOffset: number): Diagnostic { + return { + code: 'unmatched-closing-quotation-mark', + severity: DiagnosticSeverity.Error, + range: { + start: { + line: 0, + character: startOffset, + }, + end: { + line: 0, + character: endOffset, + }, + }, + source: 'quotation-mark-checker', + message: `Closing quotation mark with no opening mark.`, + data: '', + }; + } + + createIncorrectlyNestedDiagnostic(startOffset: number, endOffset: number, parentDepth: QuotationDepth): Diagnostic { + return { + code: 'incorrectly-nested-quotation-mark', + severity: DiagnosticSeverity.Warning, + range: { + start: { + line: 0, + character: startOffset, + }, + end: { + line: 0, + character: endOffset, + }, + }, + source: 'quotation-mark-checker', + message: `Incorrectly nested quotation mark.`, + data: { + depth: parentDepth.asNumber(), + }, + }; + } + + createAmbiguousDiagnostic( + startOffset: number, + endOffset: number, + ambiguousMark: string, + unambiguousMark: string, + ): Diagnostic { + return { + code: 'ambiguous-quotation-mark', + severity: DiagnosticSeverity.Warning, + range: { + start: { + line: 0, + character: startOffset, + }, + end: { + line: 0, + character: endOffset, + }, + }, + source: 'quotation-mark-checker', + message: `This quotation mark is ambiguous.`, + data: { + existingQuotationMark: ambiguousMark, + correctedQuotationMark: unambiguousMark, + }, + }; + } + + createTooDeeplyNestedDiagnostic(startOffset: number, endOffset: number): Diagnostic { + return { + code: 'deeply-nested-quotation-mark', + severity: DiagnosticSeverity.Warning, + range: { + start: { + line: 0, + character: startOffset, + }, + end: { + line: 0, + character: endOffset, + }, + }, + source: 'quotation-mark-checker', + message: 'Too many levels of quotation marks. Consider rephrasing to avoid this.', + data: '', + }; + } + + createScriptureDocument(usfm: string): ScriptureDocument { + return this.scriptureDocumentFactory.create('test-uri', 'usfm', 1, usfm); + } +} diff --git a/packages/punctuation-checker/test/quotation/quotation-corrector.test.ts b/packages/punctuation-checker/test/quotation/quotation-corrector.test.ts index e97e1c1..3f41880 100644 --- a/packages/punctuation-checker/test/quotation/quotation-corrector.test.ts +++ b/packages/punctuation-checker/test/quotation/quotation-corrector.test.ts @@ -5,53 +5,26 @@ import { QuotationConfig } from '../../src/quotation/quotation-config'; import { QuotationCorrector } from '../../src/quotation/quotation-corrector'; import { StubDocumentManager } from '../test-utils'; -const stubDocumentManager: DocumentManager = new StubDocumentManager(new TextDocumentFactory()); - -function createExpectedEdit(character: string, start: number, end: number) { - return { - range: { - start: { line: 0, character: start }, - end: { line: 0, character: end }, - }, - newText: character, - }; -} - describe('QuotationCorrector tests', () => { it('produces no output for text with no unambiguous quotes', async () => { - const quotationConfig: QuotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .mapAmbiguousQuotationMark("'", '\u2018') - .mapAmbiguousQuotationMark("'", '\u2019') - .build(); - - const quotationCorector: QuotationCorrector = new QuotationCorrector(stubDocumentManager, quotationConfig); + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); const arbitraryPosition: Position = { line: 0, character: 0 }; const arbitraryCharacter = 'a'; expect( - await quotationCorector.getOnTypeEdits('Once upon a time...', arbitraryPosition, arbitraryCharacter), + await testEnv.quotationCorrector.getOnTypeEdits('Once upon a time...', arbitraryPosition, arbitraryCharacter), ).toEqual(undefined); expect( - await quotationCorector.getOnTypeEdits('\u201COnce upon a time...\u201D', arbitraryPosition, arbitraryCharacter), + await testEnv.quotationCorrector.getOnTypeEdits( + '\u201COnce upon a time...\u201D', + arbitraryPosition, + arbitraryCharacter, + ), ).toEqual(undefined); expect( - await quotationCorector.getOnTypeEdits( + await testEnv.quotationCorrector.getOnTypeEdits( '\u201COnce upon a \u2018time\u2019...\u201D', arbitraryPosition, arbitraryCharacter, @@ -60,234 +33,255 @@ describe('QuotationCorrector tests', () => { }); it('corrects ambiguous quotation marks', async () => { - const quotationConfig: QuotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .mapAmbiguousQuotationMark("'", '\u2018') - .mapAmbiguousQuotationMark("'", '\u2019') - .build(); - - const quotationCorector: QuotationCorrector = new QuotationCorrector(stubDocumentManager, quotationConfig); + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); const arbitraryPosition: Position = { line: 0, character: 0 }; const arbitraryCharacter = 'a'; expect( - await quotationCorector.getOnTypeEdits('"Once upon a time...', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201C', 0, 1)]); + await testEnv.quotationCorrector.getOnTypeEdits('"Once upon a time...', arbitraryPosition, arbitraryCharacter), + ).toEqual([testEnv.createExpectedEdit('\u201C', 0, 1)]); expect( - await quotationCorector.getOnTypeEdits('"Once upon a time..."', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201C', 0, 1), createExpectedEdit('\u201D', 20, 21)]); + await testEnv.quotationCorrector.getOnTypeEdits('"Once upon a time..."', arbitraryPosition, arbitraryCharacter), + ).toEqual([testEnv.createExpectedEdit('\u201C', 0, 1), testEnv.createExpectedEdit('\u201D', 20, 21)]); expect( - await quotationCorector.getOnTypeEdits('\u201COnce upon a time..."', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201D', 20, 21)]); + await testEnv.quotationCorrector.getOnTypeEdits( + '\u201COnce upon a time..."', + arbitraryPosition, + arbitraryCharacter, + ), + ).toEqual([testEnv.createExpectedEdit('\u201D', 20, 21)]); expect( - await quotationCorector.getOnTypeEdits('"Once upon a time...\u201D', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201C', 0, 1)]); + await testEnv.quotationCorrector.getOnTypeEdits( + '"Once upon a time...\u201D', + arbitraryPosition, + arbitraryCharacter, + ), + ).toEqual([testEnv.createExpectedEdit('\u201C', 0, 1)]); expect( - await quotationCorector.getOnTypeEdits( + await testEnv.quotationCorrector.getOnTypeEdits( "\u201CIt was the best of times, 'it was the worst of times\u2019\u201D", arbitraryPosition, arbitraryCharacter, ), - ).toEqual([createExpectedEdit('\u2018', 27, 28)]); + ).toEqual([testEnv.createExpectedEdit('\u2018', 27, 28)]); expect( - await quotationCorector.getOnTypeEdits( + await testEnv.quotationCorrector.getOnTypeEdits( "\u201CIt was the best of times, \u2018it was the worst of times'\u201D", arbitraryPosition, arbitraryCharacter, ), - ).toEqual([createExpectedEdit('\u2019', 53, 54)]); + ).toEqual([testEnv.createExpectedEdit('\u2019', 53, 54)]); expect( - await quotationCorector.getOnTypeEdits( + await testEnv.quotationCorrector.getOnTypeEdits( '\u201CIt was the best of times, \u2018it was the worst of times\u2019"', arbitraryPosition, arbitraryCharacter, ), - ).toEqual([createExpectedEdit('\u201D', 54, 55)]); + ).toEqual([testEnv.createExpectedEdit('\u201D', 54, 55)]); expect( - await quotationCorector.getOnTypeEdits( + await testEnv.quotationCorrector.getOnTypeEdits( '"It was the best of times, \'it was the worst of times\'"', arbitraryPosition, arbitraryCharacter, ), ).toEqual([ - createExpectedEdit('\u201C', 0, 1), - createExpectedEdit('\u2018', 27, 28), - createExpectedEdit('\u2019', 53, 54), - createExpectedEdit('\u201D', 54, 55), + testEnv.createExpectedEdit('\u201C', 0, 1), + testEnv.createExpectedEdit('\u2018', 27, 28), + testEnv.createExpectedEdit('\u2019', 53, 54), + testEnv.createExpectedEdit('\u201D', 54, 55), ]); }); it('does not depend on whitespace', async () => { - const quotationConfig: QuotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .mapAmbiguousQuotationMark("'", '\u2018') - .mapAmbiguousQuotationMark("'", '\u2019') - .build(); - - const quotationCorector: QuotationCorrector = new QuotationCorrector(stubDocumentManager, quotationConfig); + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); const arbitraryPosition: Position = { line: 0, character: 0 }; const arbitraryCharacter = 'a'; expect( - await quotationCorector.getOnTypeEdits('"Once upon a time...', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201C', 0, 1)]); + await testEnv.quotationCorrector.getOnTypeEdits('"Once upon a time...', arbitraryPosition, arbitraryCharacter), + ).toEqual([testEnv.createExpectedEdit('\u201C', 0, 1)]); expect( - await quotationCorector.getOnTypeEdits(' "Once upon a time...', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201C', 1, 2)]); + await testEnv.quotationCorrector.getOnTypeEdits(' "Once upon a time...', arbitraryPosition, arbitraryCharacter), + ).toEqual([testEnv.createExpectedEdit('\u201C', 1, 2)]); expect( - await quotationCorector.getOnTypeEdits('" Once upon a time...', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201C', 0, 1)]); + await testEnv.quotationCorrector.getOnTypeEdits('" Once upon a time...', arbitraryPosition, arbitraryCharacter), + ).toEqual([testEnv.createExpectedEdit('\u201C', 0, 1)]); expect( - await quotationCorector.getOnTypeEdits('Once upon a time..."', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201C', 19, 20)]); + await testEnv.quotationCorrector.getOnTypeEdits('Once upon a time..."', arbitraryPosition, arbitraryCharacter), + ).toEqual([testEnv.createExpectedEdit('\u201C', 19, 20)]); expect( - await quotationCorector.getOnTypeEdits('\u201COnce upon a time..."', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201D', 20, 21)]); + await testEnv.quotationCorrector.getOnTypeEdits( + '\u201COnce upon a time..."', + arbitraryPosition, + arbitraryCharacter, + ), + ).toEqual([testEnv.createExpectedEdit('\u201D', 20, 21)]); expect( - await quotationCorector.getOnTypeEdits('\u201COnce upon a time"', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201D', 17, 18)]); + await testEnv.quotationCorrector.getOnTypeEdits('\u201COnce upon a time"', arbitraryPosition, arbitraryCharacter), + ).toEqual([testEnv.createExpectedEdit('\u201D', 17, 18)]); expect( - await quotationCorector.getOnTypeEdits('\u201COnce upon a time" ', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201D', 17, 18)]); + await testEnv.quotationCorrector.getOnTypeEdits( + '\u201COnce upon a time" ', + arbitraryPosition, + arbitraryCharacter, + ), + ).toEqual([testEnv.createExpectedEdit('\u201D', 17, 18)]); expect( - await quotationCorector.getOnTypeEdits('\u201COnce upon a time "', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201D', 18, 19)]); + await testEnv.quotationCorrector.getOnTypeEdits( + '\u201COnce upon a time "', + arbitraryPosition, + arbitraryCharacter, + ), + ).toEqual([testEnv.createExpectedEdit('\u201D', 18, 19)]); expect( - await quotationCorector.getOnTypeEdits('Once" upon a time "there was', arbitraryPosition, arbitraryCharacter), - ).toEqual([createExpectedEdit('\u201C', 4, 5), createExpectedEdit('\u201D', 18, 19)]); + await testEnv.quotationCorrector.getOnTypeEdits( + 'Once" upon a time "there was', + arbitraryPosition, + arbitraryCharacter, + ), + ).toEqual([testEnv.createExpectedEdit('\u201C', 4, 5), testEnv.createExpectedEdit('\u201D', 18, 19)]); expect( - await quotationCorector.getOnTypeEdits( + await testEnv.quotationCorrector.getOnTypeEdits( "\u201COnce upon' a time there \u2019was\u201D", arbitraryPosition, arbitraryCharacter, ), - ).toEqual([createExpectedEdit('\u2018', 10, 11)]); + ).toEqual([testEnv.createExpectedEdit('\u2018', 10, 11)]); expect( - await quotationCorector.getOnTypeEdits( + await testEnv.quotationCorrector.getOnTypeEdits( "\u201COnce upon 'a time there \u2019was\u201D", arbitraryPosition, arbitraryCharacter, ), - ).toEqual([createExpectedEdit('\u2018', 11, 12)]); + ).toEqual([testEnv.createExpectedEdit('\u2018', 11, 12)]); }); it('does not depend on the position or character passed', async () => { - const quotationConfig: QuotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .mapAmbiguousQuotationMark("'", '\u2018') - .mapAmbiguousQuotationMark("'", '\u2019') - .build(); - - const quotationCorrector: QuotationCorrector = new QuotationCorrector(stubDocumentManager, quotationConfig); + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); expect( - await quotationCorrector.getOnTypeEdits('It was the "best of times', { line: 0, character: 10 }, 'c'), - ).toEqual([createExpectedEdit('\u201C', 11, 12)]); + await testEnv.quotationCorrector.getOnTypeEdits('It was the "best of times', { line: 0, character: 10 }, 'c'), + ).toEqual([testEnv.createExpectedEdit('\u201C', 11, 12)]); expect( - await quotationCorrector.getOnTypeEdits('It was the "best of times', { line: 10, character: 0 }, '"'), - ).toEqual([createExpectedEdit('\u201C', 11, 12)]); + await testEnv.quotationCorrector.getOnTypeEdits('It was the "best of times', { line: 10, character: 0 }, '"'), + ).toEqual([testEnv.createExpectedEdit('\u201C', 11, 12)]); expect( - await quotationCorrector.getOnTypeEdits('It was the "best of times', { line: 25, character: 35 }, '\u201D'), - ).toEqual([createExpectedEdit('\u201C', 11, 12)]); + await testEnv.quotationCorrector.getOnTypeEdits( + 'It was the "best of times', + { line: 25, character: 35 }, + '\u201D', + ), + ).toEqual([testEnv.createExpectedEdit('\u201C', 11, 12)]); expect( - await quotationCorrector.getOnTypeEdits('It was the "best of times', { line: 250, character: 350 }, '\u201C'), - ).toEqual([createExpectedEdit('\u201C', 11, 12)]); + await testEnv.quotationCorrector.getOnTypeEdits( + 'It was the "best of times', + { line: 250, character: 350 }, + '\u201C', + ), + ).toEqual([testEnv.createExpectedEdit('\u201C', 11, 12)]); }); it('adheres to the QuotationConfig', async () => { - const quotationConfig: QuotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .mapAmbiguousQuotationMark('+', '\u201C') - .mapAmbiguousQuotationMark('+', '\u201D') - .mapAmbiguousQuotationMark('-', '\u2018') - .mapAmbiguousQuotationMark('-', '\u2019') - .build(); - - const quotationCorrector: QuotationCorrector = new QuotationCorrector(stubDocumentManager, quotationConfig); + const testEnv: TestEnvironment = TestEnvironment.createWithAlternativeAmbiguousQuotes(); const arbitraryPosition: Position = { line: 0, character: 0 }; const arbitraryCharacter = 'a'; - expect(await quotationCorrector.getOnTypeEdits('Once +upon a time', arbitraryPosition, arbitraryCharacter)).toEqual( - [createExpectedEdit('\u201C', 5, 6)], - ); + expect( + await testEnv.quotationCorrector.getOnTypeEdits('Once +upon a time', arbitraryPosition, arbitraryCharacter), + ).toEqual([testEnv.createExpectedEdit('\u201C', 5, 6)]); expect( - await quotationCorrector.getOnTypeEdits('Once +upon- a time-+', arbitraryPosition, arbitraryCharacter), + await testEnv.quotationCorrector.getOnTypeEdits('Once +upon- a time-+', arbitraryPosition, arbitraryCharacter), ).toEqual([ - createExpectedEdit('\u201C', 5, 6), - createExpectedEdit('\u2018', 10, 11), - createExpectedEdit('\u2019', 18, 19), - createExpectedEdit('\u201D', 19, 20), + testEnv.createExpectedEdit('\u201C', 5, 6), + testEnv.createExpectedEdit('\u2018', 10, 11), + testEnv.createExpectedEdit('\u2019', 18, 19), + testEnv.createExpectedEdit('\u201D', 19, 20), ]); }); }); + +class TestEnvironment { + readonly quotationCorrector: QuotationCorrector; + + private constructor(private readonly quotationConfig: QuotationConfig) { + const stubDocumentManager: DocumentManager = new StubDocumentManager(new TextDocumentFactory()); + this.quotationCorrector = new QuotationCorrector(stubDocumentManager, quotationConfig); + } + + static createWithFullEnglishQuotes(): TestEnvironment { + return new TestEnvironment( + new QuotationConfig.Builder() + .setTopLevelQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .mapAmbiguousQuotationMark('"', '\u201C') + .mapAmbiguousQuotationMark('"', '\u201D') + .mapAmbiguousQuotationMark("'", '\u2018') + .mapAmbiguousQuotationMark("'", '\u2019') + .build(), + ); + } + + static createWithAlternativeAmbiguousQuotes(): TestEnvironment { + return new TestEnvironment( + new QuotationConfig.Builder() + .setTopLevelQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .mapAmbiguousQuotationMark('+', '\u201C') + .mapAmbiguousQuotationMark('+', '\u201D') + .mapAmbiguousQuotationMark('-', '\u2018') + .mapAmbiguousQuotationMark('-', '\u2019') + .build(), + ); + } + + createExpectedEdit(character: string, start: number, end: number) { + return { + range: { + start: { line: 0, character: start }, + end: { line: 0, character: end }, + }, + newText: character, + }; + } +} diff --git a/packages/punctuation-checker/test/quotation/quotation-utils.test.ts b/packages/punctuation-checker/test/quotation/quotation-utils.test.ts index 24dead5..9f1c391 100644 --- a/packages/punctuation-checker/test/quotation/quotation-utils.test.ts +++ b/packages/punctuation-checker/test/quotation/quotation-utils.test.ts @@ -11,27 +11,22 @@ import { PairedPunctuationDirection, StringContextMatcher } from '../../src/util describe('QuotationIterator tests', () => { describe('Top-level English quotation marks', () => { - const quotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .build(); - describe('Identification of unpaired quotation marks', () => { it('does not identify quotes in quote-less text', () => { - const emptyStringIterator: QuotationIterator = new QuotationIterator(quotationConfig, ''); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotesAndNoAmbiguity(); + const emptyStringIterator: QuotationIterator = testEnv.newQuotationIterator(''); expect(emptyStringIterator.hasNext()).toBe(false); - const noQuotationsIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const noQuotationsIterator: QuotationIterator = testEnv.newQuotationIterator( 'There are no quotes in this text.', ); expect(noQuotationsIterator.hasNext()).toBe(false); }); it('identifies unpaired quotation marks in otherwise empty strings', () => { - const openingQuotationIterator: QuotationIterator = new QuotationIterator(quotationConfig, '\u201C'); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotesAndNoAmbiguity(); + + const openingQuotationIterator: QuotationIterator = testEnv.newQuotationIterator('\u201C'); expect(openingQuotationIterator.hasNext()).toBe(true); expect(openingQuotationIterator.next()).toEqual( new UnresolvedQuoteMetadata.Builder() @@ -47,7 +42,7 @@ describe('QuotationIterator tests', () => { openingQuotationIterator.next(); }).toThrowError(/QuoteIterator's next\(\) function called after hasNext\(\) returned false/); - const closingQuotationIterator: QuotationIterator = new QuotationIterator(quotationConfig, '\u201D'); + const closingQuotationIterator: QuotationIterator = testEnv.newQuotationIterator('\u201D'); expect(closingQuotationIterator.hasNext()).toBe(true); expect(closingQuotationIterator.next()).toEqual( new UnresolvedQuoteMetadata.Builder() @@ -62,10 +57,10 @@ describe('QuotationIterator tests', () => { }); it('identifies unpaired quotation marks in non-empty strings', () => { - const previousContextOpeningQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, - 'Extra text, \u201C', - ); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotesAndNoAmbiguity(); + + const previousContextOpeningQuotationIterator: QuotationIterator = + testEnv.newQuotationIterator('Extra text, \u201C'); expect(previousContextOpeningQuotationIterator.hasNext()).toBe(true); expect(previousContextOpeningQuotationIterator.next()).toEqual( new UnresolvedQuoteMetadata.Builder() @@ -78,8 +73,7 @@ describe('QuotationIterator tests', () => { ); expect(previousContextOpeningQuotationIterator.hasNext()).toBe(false); - const trailingContextOpeningQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const trailingContextOpeningQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201CWith extra text at the end', ); expect(trailingContextOpeningQuotationIterator.hasNext()).toBe(true); @@ -94,8 +88,7 @@ describe('QuotationIterator tests', () => { ); expect(trailingContextOpeningQuotationIterator.hasNext()).toBe(false); - const twoSidedContextOpeningQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const twoSidedContextOpeningQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( 'Text at the start, \u201CWith extra text at the end', ); expect(twoSidedContextOpeningQuotationIterator.hasNext()).toBe(true); @@ -110,10 +103,8 @@ describe('QuotationIterator tests', () => { ); expect(twoSidedContextOpeningQuotationIterator.hasNext()).toBe(false); - const previousContextClosingQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, - ' starting text\u201D', - ); + const previousContextClosingQuotationIterator: QuotationIterator = + testEnv.newQuotationIterator(' starting text\u201D'); expect(previousContextClosingQuotationIterator.hasNext()).toBe(true); expect(previousContextClosingQuotationIterator.next()).toEqual( new UnresolvedQuoteMetadata.Builder() @@ -126,10 +117,8 @@ describe('QuotationIterator tests', () => { ); expect(previousContextClosingQuotationIterator.hasNext()).toBe(false); - const trailingContextClosingQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, - '\u201Dwith extra at the end.', - ); + const trailingContextClosingQuotationIterator: QuotationIterator = + testEnv.newQuotationIterator('\u201Dwith extra at the end.'); expect(trailingContextClosingQuotationIterator.hasNext()).toBe(true); expect(trailingContextClosingQuotationIterator.next()).toEqual( new UnresolvedQuoteMetadata.Builder() @@ -142,8 +131,7 @@ describe('QuotationIterator tests', () => { ); expect(trailingContextClosingQuotationIterator.hasNext()).toBe(false); - const twoSidedContextClosingQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const twoSidedContextClosingQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( 'Prior text \u201Dwith extra at the end.', ); expect(twoSidedContextClosingQuotationIterator.hasNext()).toBe(true); @@ -160,8 +148,9 @@ describe('QuotationIterator tests', () => { }); it('does not depend on correct formatting of whitespace', () => { - const noWhitespaceOpeningQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotesAndNoAmbiguity(); + + const noWhitespaceOpeningQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( 'Text immediately prior\u201Cwith text immediately afterward', ); expect(noWhitespaceOpeningQuotationIterator.hasNext()).toBe(true); @@ -176,8 +165,7 @@ describe('QuotationIterator tests', () => { ); expect(noWhitespaceOpeningQuotationIterator.hasNext()).toBe(false); - const reversedWhitespaceOpeningQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const reversedWhitespaceOpeningQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( 'No previous space\u201C but a space afterward', ); expect(reversedWhitespaceOpeningQuotationIterator.hasNext()).toBe(true); @@ -192,8 +180,7 @@ describe('QuotationIterator tests', () => { ); expect(reversedWhitespaceOpeningQuotationIterator.hasNext()).toBe(false); - const noWhitespaceClosingQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const noWhitespaceClosingQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( 'Text immediately prior\u201Dwith text immediately afterward', ); expect(noWhitespaceClosingQuotationIterator.hasNext()).toBe(true); @@ -208,8 +195,7 @@ describe('QuotationIterator tests', () => { ); expect(noWhitespaceClosingQuotationIterator.hasNext()).toBe(false); - const reversedWhitespaceClosingQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const reversedWhitespaceClosingQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( 'A previous space \u201Dbut no space afterward', ); expect(reversedWhitespaceClosingQuotationIterator.hasNext()).toBe(true); @@ -228,7 +214,8 @@ describe('QuotationIterator tests', () => { describe('Identification of multiple quotation marks', () => { it('identifies a pair of quotation marks in otherwise empty strings', () => { - const quotationPairIterator: QuotationIterator = new QuotationIterator(quotationConfig, '\u201C\u201D'); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotesAndNoAmbiguity(); + const quotationPairIterator: QuotationIterator = testEnv.newQuotationIterator('\u201C\u201D'); expect(quotationPairIterator.hasNext()).toBe(true); expect(quotationPairIterator.next()).toEqual( @@ -254,10 +241,8 @@ describe('QuotationIterator tests', () => { }); it('identifies a pair of quotation marks in non-empty strings', () => { - const quotationPairIterator: QuotationIterator = new QuotationIterator( - quotationConfig, - '\u201CSample text.\u201D', - ); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotesAndNoAmbiguity(); + const quotationPairIterator: QuotationIterator = testEnv.newQuotationIterator('\u201CSample text.\u201D'); expect(quotationPairIterator.hasNext()).toBe(true); expect(quotationPairIterator.next()).toEqual( new UnresolvedQuoteMetadata.Builder() @@ -280,8 +265,7 @@ describe('QuotationIterator tests', () => { ); expect(quotationPairIterator.hasNext()).toBe(false); - const previousContextQuotationPairIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const previousContextQuotationPairIterator: QuotationIterator = testEnv.newQuotationIterator( 'Context on the left, \u201CSample text.\u201D', ); expect(previousContextQuotationPairIterator.hasNext()).toBe(true); @@ -306,8 +290,7 @@ describe('QuotationIterator tests', () => { ); expect(previousContextQuotationPairIterator.hasNext()).toBe(false); - const trailingContextQuotationPairIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const trailingContextQuotationPairIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201CSample text.\u201D with context on the right.', ); expect(trailingContextQuotationPairIterator.hasNext()).toBe(true); @@ -332,8 +315,7 @@ describe('QuotationIterator tests', () => { ); expect(trailingContextQuotationPairIterator.hasNext()).toBe(false); - const twoSidedContextQuotationPairIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const twoSidedContextQuotationPairIterator: QuotationIterator = testEnv.newQuotationIterator( 'Context on the left, \u201CSample text.\u201D with context on the right', ); expect(twoSidedContextQuotationPairIterator.hasNext()).toBe(true); @@ -360,8 +342,9 @@ describe('QuotationIterator tests', () => { }); it('identifies multiple pairs of quotation marks', () => { - const quotationPairIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotesAndNoAmbiguity(); + + const quotationPairIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201CSample text.\u201D and, \u201Cmore sample text\u201D', ); expect(quotationPairIterator.hasNext()).toBe(true); @@ -409,20 +392,10 @@ describe('QuotationIterator tests', () => { }); describe('Identification of ambiguous quotation marks', () => { - const ambiguousQuotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .build(); - it('identifies ambiguous quotes in text with no unambiguous quotes', () => { - const singleQuotationIterator: QuotationIterator = new QuotationIterator( - ambiguousQuotationConfig, - 'Sample" text.', - ); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotes(); + + const singleQuotationIterator: QuotationIterator = testEnv.newQuotationIterator('Sample" text.'); expect(singleQuotationIterator.hasNext()).toBe(true); expect(singleQuotationIterator.next()).toEqual( new UnresolvedQuoteMetadata.Builder() @@ -437,10 +410,7 @@ describe('QuotationIterator tests', () => { ); expect(singleQuotationIterator.hasNext()).toBe(false); - const quotationPairIterator: QuotationIterator = new QuotationIterator( - ambiguousQuotationConfig, - '"Sample text."', - ); + const quotationPairIterator: QuotationIterator = testEnv.newQuotationIterator('"Sample text."'); expect(quotationPairIterator.hasNext()).toBe(true); expect(quotationPairIterator.next()).toEqual( new UnresolvedQuoteMetadata.Builder() @@ -469,8 +439,9 @@ describe('QuotationIterator tests', () => { }); it('identifies ambiguous quotes in text containing unambiugous quotes', () => { - const mixedQuotationIterator: QuotationIterator = new QuotationIterator( - ambiguousQuotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotes(); + + const mixedQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( 'This text \u201Ccontains "straight" and curly quotes.\u201D', ); expect(mixedQuotationIterator.hasNext()).toBe(true); @@ -523,8 +494,8 @@ describe('QuotationIterator tests', () => { describe('Identification of ill-formed quotation marks', () => { it('identifies quotation marks that are improperly nested', () => { - const improperlyNestedOpeningQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotesAndNoAmbiguity(); + const improperlyNestedOpeningQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201CThis contains an \u201C improperly nested quotation mark.\u201D', ); expect(improperlyNestedOpeningQuotationIterator.hasNext()).toBe(true); @@ -559,8 +530,7 @@ describe('QuotationIterator tests', () => { ); expect(improperlyNestedOpeningQuotationIterator.hasNext()).toBe(false); - const improperlyNestedClosingQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const improperlyNestedClosingQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201CThis contains an \u201D improperly nested quotation mark.\u201D', ); expect(improperlyNestedClosingQuotationIterator.hasNext()).toBe(true); @@ -597,8 +567,8 @@ describe('QuotationIterator tests', () => { }); it('identifies unpaired quotation marks', () => { - const unpairedOpeningQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotesAndNoAmbiguity(); + const unpairedOpeningQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201CThe second quotation mark\u201D is \u201C never closed.', ); expect(unpairedOpeningQuotationIterator.hasNext()).toBe(true); @@ -633,8 +603,7 @@ describe('QuotationIterator tests', () => { ); expect(unpairedOpeningQuotationIterator.hasNext()).toBe(false); - const unpairedClosingQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const unpairedClosingQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( 'The first \u201D quotation mark \u201Cis unpaired.\u201D', ); expect(unpairedClosingQuotationIterator.hasNext()).toBe(true); @@ -673,14 +642,13 @@ describe('QuotationIterator tests', () => { describe('Adherence to the QuotationConfig', () => { it('does not identify single quotes', () => { - const singleQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotesAndNoAmbiguity(); + const singleQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( '\u2018single quoted text\u2019', ); expect(singleQuotationIterator.hasNext()).toBe(false); - const singleAndDoubleQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const singleAndDoubleQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201Cdouble and \u2018single quoted text\u201D', ); expect(singleAndDoubleQuotationIterator.next()).toEqual( @@ -706,20 +674,14 @@ describe('QuotationIterator tests', () => { }); it('does not identify straight quotes', () => { - const straightDoubleQuoteIterator: QuotationIterator = new QuotationIterator( - quotationConfig, - '"straight double quotes"', - ); + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotesAndNoAmbiguity(); + const straightDoubleQuoteIterator: QuotationIterator = testEnv.newQuotationIterator('"straight double quotes"'); expect(straightDoubleQuoteIterator.hasNext()).toBe(false); - const straightSingleQuoteIterator: QuotationIterator = new QuotationIterator( - quotationConfig, - "'straight double quotes'", - ); + const straightSingleQuoteIterator: QuotationIterator = testEnv.newQuotationIterator("'straight double quotes'"); expect(straightSingleQuoteIterator.hasNext()).toBe(false); - const curlyAndStraightDoubleQuoteIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const curlyAndStraightDoubleQuoteIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201Ccurly and straight quotes used here"', ); expect(curlyAndStraightDoubleQuoteIterator.next()).toEqual( @@ -733,8 +695,7 @@ describe('QuotationIterator tests', () => { ); expect(curlyAndStraightDoubleQuoteIterator.hasNext()).toBe(false); - const curlyDoubleAndStraightSingleQuoteIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const curlyDoubleAndStraightSingleQuoteIterator: QuotationIterator = testEnv.newQuotationIterator( "\u201Ccurly double and 'straight' quotes used here", ); expect(curlyDoubleAndStraightSingleQuoteIterator.next()).toEqual( @@ -750,86 +711,40 @@ describe('QuotationIterator tests', () => { }); it('does not identify alternative top-level quotes', () => { - const guillemetsIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithTopLevelQuotesAndNoAmbiguity(); + const guillemetsIterator: QuotationIterator = testEnv.newQuotationIterator( '\u00ABFrench-style guillemets\u00BB', ); expect(guillemetsIterator.hasNext()).toBe(false); - const germanQuotesIterator: QuotationIterator = new QuotationIterator( - quotationConfig, - '\u201EGerman-style quotes\u201F', - ); + const germanQuotesIterator: QuotationIterator = testEnv.newQuotationIterator('\u201EGerman-style quotes\u201F'); expect(germanQuotesIterator.hasNext()).toBe(false); }); }); }); describe('Multi-level English quotation marks', () => { - const quotationConfig: QuotationConfig = new QuotationConfig.Builder() - .setTopLevelQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u201C', - closingPunctuationMark: '\u201D', - }) - .addNestedQuotationMarks({ - openingPunctuationMark: '\u2018', - closingPunctuationMark: '\u2019', - }) - .mapAmbiguousQuotationMark('"', '\u201C') - .mapAmbiguousQuotationMark('"', '\u201D') - .mapAmbiguousQuotationMark("'", '\u2018') - .mapAmbiguousQuotationMark("'", '\u2019') - .ignoreMatchingQuotationMarks( - // possessives and contractions - new StringContextMatcher.Builder() - .setCenterContent(/^['\u2019]$/) - .setLeftContext(/\w$/) - .setRightContext(/^\w/) - .build(), - ) - .ignoreMatchingQuotationMarks( - // for possessives ending in "s", e.g. "Moses'" - new StringContextMatcher.Builder() - .setCenterContent(/^['\u2019]$/) - .setLeftContext(/\ws$/) - .setRightContext(/^[ \n,.:;]/) - .build(), - ) - .setNestingWarningDepth(QuotationDepth.fromNumber(4)) - .build(); - it('skips over apostrophes', () => { - const basicPossessiveIterator: QuotationIterator = new QuotationIterator(quotationConfig, "Abraham's servant"); + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + const basicPossessiveIterator: QuotationIterator = testEnv.newQuotationIterator("Abraham's servant"); expect(basicPossessiveIterator.hasNext()).toBe(false); - const basicPossessiveIterator2: QuotationIterator = new QuotationIterator( - quotationConfig, - 'Abraham\u2019s servant', - ); + const basicPossessiveIterator2: QuotationIterator = testEnv.newQuotationIterator('Abraham\u2019s servant'); expect(basicPossessiveIterator2.hasNext()).toBe(false); - const basicContractionIterator: QuotationIterator = new QuotationIterator(quotationConfig, "they're"); + const basicContractionIterator: QuotationIterator = testEnv.newQuotationIterator("they're"); expect(basicContractionIterator.hasNext()).toBe(false); - const basicContractionIterator2: QuotationIterator = new QuotationIterator(quotationConfig, 'they\u2019re'); + const basicContractionIterator2: QuotationIterator = testEnv.newQuotationIterator('they\u2019re'); expect(basicContractionIterator2.hasNext()).toBe(false); - const sPossessiveIterator: QuotationIterator = new QuotationIterator(quotationConfig, "your sons' wives"); + const sPossessiveIterator: QuotationIterator = testEnv.newQuotationIterator("your sons' wives"); expect(sPossessiveIterator.hasNext()).toBe(false); - const sPossessiveIterator2: QuotationIterator = new QuotationIterator(quotationConfig, 'your sons\u2019 wives'); + const sPossessiveIterator2: QuotationIterator = testEnv.newQuotationIterator('your sons\u2019 wives'); expect(sPossessiveIterator2.hasNext()).toBe(false); - const quotedApostropheIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const quotedApostropheIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201CThis is one of the Hebrews\u2019 children.\u201D', ); expect(quotedApostropheIterator.hasNext()).toBe(true); @@ -857,10 +772,8 @@ describe('QuotationIterator tests', () => { describe('Identification of multiple quotation marks', () => { it('identifies multiple levels of quotes in otherwise empty text', () => { - const quotationPairIterator: QuotationIterator = new QuotationIterator( - quotationConfig, - '\u201C\u2018\u2019\u201D', - ); + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + const quotationPairIterator: QuotationIterator = testEnv.newQuotationIterator('\u201C\u2018\u2019\u201D'); expect(quotationPairIterator.hasNext()).toBe(true); expect(quotationPairIterator.next()).toEqual( @@ -904,8 +817,7 @@ describe('QuotationIterator tests', () => { ); expect(quotationPairIterator.hasNext()).toBe(false); - const threeLevelQuotationPairIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const threeLevelQuotationPairIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201C\u2018\u201C\u201D\u2019\u201D', ); @@ -973,8 +885,9 @@ describe('QuotationIterator tests', () => { }); it('identifies multiple levels of quotes in non-empty text', () => { - const quotationPairIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + + const quotationPairIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201CThis text \u2018contains multiple\u2019 quote levels\u201D', ); @@ -1020,8 +933,7 @@ describe('QuotationIterator tests', () => { ); expect(quotationPairIterator.hasNext()).toBe(false); - const threeLevelQuotationPairIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const threeLevelQuotationPairIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201CThis text \u2018has \u201Cthree levels\u201D of quotations\u2019\u201D', ); @@ -1091,8 +1003,9 @@ describe('QuotationIterator tests', () => { describe('Identification of ambiguous quotation marks', () => { it('identifies ambiguous quotes in text with no unambiguous quotes', () => { - const quotationPairIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + + const quotationPairIterator: QuotationIterator = testEnv.newQuotationIterator( 'This text "has \'multiple levels of "ambiguous" quotations\'"', ); @@ -1165,8 +1078,9 @@ describe('QuotationIterator tests', () => { }); it('identifies ambiguous quotes in text with other unambiguous quotes', () => { - const quotationPairIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + + const quotationPairIterator: QuotationIterator = testEnv.newQuotationIterator( 'This text "has \u2018multiple levels of \u201Cambiguous" quotations\'\u201D', ); @@ -1238,8 +1152,9 @@ describe('QuotationIterator tests', () => { describe('Identification of ill-formed quotation marks', () => { it('identifies quotation marks that are improperly nested', () => { - const improperlyNestedSecondaryQuoteIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + + const improperlyNestedSecondaryQuoteIterator: QuotationIterator = testEnv.newQuotationIterator( 'This contains an \u2018 improperly nested quote\u2019', ); expect(improperlyNestedSecondaryQuoteIterator.hasNext()).toBe(true); @@ -1264,8 +1179,7 @@ describe('QuotationIterator tests', () => { ); expect(improperlyNestedSecondaryQuoteIterator.hasNext()).toBe(false); - const improperlyNestedTertiaryQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const improperlyNestedTertiaryQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201CThis contains an \u201C improperly nested quotation mark.\u201D', ); expect(improperlyNestedTertiaryQuotationIterator.hasNext()).toBe(true); @@ -1302,8 +1216,9 @@ describe('QuotationIterator tests', () => { }); it('identifies unpaired quotation marks', () => { - const unpairedOpeningQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + + const unpairedOpeningQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201CThis has three \u2018unclosed \u201Cquotations', ); expect(unpairedOpeningQuotationIterator.hasNext()).toBe(true); @@ -1338,8 +1253,7 @@ describe('QuotationIterator tests', () => { ); expect(unpairedOpeningQuotationIterator.hasNext()).toBe(false); - const unpairedClosingQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const unpairedClosingQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( 'This has\u201D three\u2019 unpaired\u201D closing quotes', ); expect(unpairedClosingQuotationIterator.hasNext()).toBe(true); @@ -1376,8 +1290,9 @@ describe('QuotationIterator tests', () => { }); it('identifies quotation marks that are nested beyond the warning depth', () => { - const tooDeeplyNestedQuotationIterator: QuotationIterator = new QuotationIterator( - quotationConfig, + const testEnv: TestEnvironment = TestEnvironment.createWithFullEnglishQuotes(); + + const tooDeeplyNestedQuotationIterator: QuotationIterator = testEnv.newQuotationIterator( '\u201CThis has \u2018four \u201Clevels of \u2018quotation marks\u2019\u201D\u2019\u201D', ); expect(tooDeeplyNestedQuotationIterator.hasNext()).toBe(true); @@ -1623,18 +1538,18 @@ describe('UnresolvedQuoteMetadata tests', () => { }); }); - const unresolvedQuoteMetadata: UnresolvedQuoteMetadata = new UnresolvedQuoteMetadata.Builder() - .addDepth(QuotationDepth.fromNumber(3)) - .addDepth(QuotationDepth.fromNumber(1)) - .addDirection(PairedPunctuationDirection.Opening) - .addDirection(PairedPunctuationDirection.Closing) - .setStartIndex(3) - .setEndIndex(6) - .setText('"') - .markAsAutocorrectable() - .build(); - it('gives the correct number of options for depth and direction', () => { + const unresolvedQuoteMetadata: UnresolvedQuoteMetadata = new UnresolvedQuoteMetadata.Builder() + .addDepth(QuotationDepth.fromNumber(3)) + .addDepth(QuotationDepth.fromNumber(1)) + .addDirection(PairedPunctuationDirection.Opening) + .addDirection(PairedPunctuationDirection.Closing) + .setStartIndex(3) + .setEndIndex(6) + .setText('"') + .markAsAutocorrectable() + .build(); + expect(unresolvedQuoteMetadata.numPossibleDepths()).toEqual(2); expect(unresolvedQuoteMetadata.numPossibleDirections()).toEqual(2); @@ -1654,6 +1569,17 @@ describe('UnresolvedQuoteMetadata tests', () => { }); it('tells whether the given depth/direction is possible', () => { + const unresolvedQuoteMetadata: UnresolvedQuoteMetadata = new UnresolvedQuoteMetadata.Builder() + .addDepth(QuotationDepth.fromNumber(3)) + .addDepth(QuotationDepth.fromNumber(1)) + .addDirection(PairedPunctuationDirection.Opening) + .addDirection(PairedPunctuationDirection.Closing) + .setStartIndex(3) + .setEndIndex(6) + .setText('"') + .markAsAutocorrectable() + .build(); + expect(unresolvedQuoteMetadata.isDepthPossible(QuotationDepth.Primary)).toBe(true); expect(unresolvedQuoteMetadata.isDepthPossible(QuotationDepth.Secondary)).toBe(false); expect(unresolvedQuoteMetadata.isDepthPossible(QuotationDepth.Tertiary)).toBe(true); @@ -1664,6 +1590,17 @@ describe('UnresolvedQuoteMetadata tests', () => { }); it('resolves into a QuoteMetadata object with the choices specified', () => { + const unresolvedQuoteMetadata: UnresolvedQuoteMetadata = new UnresolvedQuoteMetadata.Builder() + .addDepth(QuotationDepth.fromNumber(3)) + .addDepth(QuotationDepth.fromNumber(1)) + .addDirection(PairedPunctuationDirection.Opening) + .addDirection(PairedPunctuationDirection.Closing) + .setStartIndex(3) + .setEndIndex(6) + .setText('"') + .markAsAutocorrectable() + .build(); + expect(unresolvedQuoteMetadata.resolve(QuotationDepth.fromNumber(1), PairedPunctuationDirection.Opening)).toEqual({ depth: QuotationDepth.Primary, direction: PairedPunctuationDirection.Opening, @@ -1699,6 +1636,17 @@ describe('UnresolvedQuoteMetadata tests', () => { }); it('selects the best depth when given a scoring function', () => { + const unresolvedQuoteMetadata: UnresolvedQuoteMetadata = new UnresolvedQuoteMetadata.Builder() + .addDepth(QuotationDepth.fromNumber(3)) + .addDepth(QuotationDepth.fromNumber(1)) + .addDirection(PairedPunctuationDirection.Opening) + .addDirection(PairedPunctuationDirection.Closing) + .setStartIndex(3) + .setEndIndex(6) + .setText('"') + .markAsAutocorrectable() + .build(); + expect(unresolvedQuoteMetadata.findBestDepth((depth: QuotationDepth) => depth.asNumber())).toEqual( QuotationDepth.fromNumber(3), ); @@ -1710,6 +1658,17 @@ describe('UnresolvedQuoteMetadata tests', () => { }); it('selects the best direction when given a scoring function', () => { + const unresolvedQuoteMetadata: UnresolvedQuoteMetadata = new UnresolvedQuoteMetadata.Builder() + .addDepth(QuotationDepth.fromNumber(3)) + .addDepth(QuotationDepth.fromNumber(1)) + .addDirection(PairedPunctuationDirection.Opening) + .addDirection(PairedPunctuationDirection.Closing) + .setStartIndex(3) + .setEndIndex(6) + .setText('"') + .markAsAutocorrectable() + .build(); + expect( unresolvedQuoteMetadata.findBestDirection((direction: PairedPunctuationDirection) => direction === PairedPunctuationDirection.Opening ? 1 : 0, @@ -1727,6 +1686,17 @@ describe('UnresolvedQuoteMetadata tests', () => { }); it('errors when it cannot be resolved as called', () => { + const unresolvedQuoteMetadata: UnresolvedQuoteMetadata = new UnresolvedQuoteMetadata.Builder() + .addDepth(QuotationDepth.fromNumber(3)) + .addDepth(QuotationDepth.fromNumber(1)) + .addDirection(PairedPunctuationDirection.Opening) + .addDirection(PairedPunctuationDirection.Closing) + .setStartIndex(3) + .setEndIndex(6) + .setText('"') + .markAsAutocorrectable() + .build(); + expect(() => unresolvedQuoteMetadata.resolve(QuotationDepth.fromNumber(2), PairedPunctuationDirection.Opening), ).toThrowError('Cannot resolve quote metadata with depth 2, as this depth is not possible.'); @@ -1786,3 +1756,79 @@ describe('QuotationDepth tests', () => { ); }); }); + +class TestEnvironment { + private constructor(private readonly quotationConfig: QuotationConfig) {} + + public newQuotationIterator(text: string): QuotationIterator { + return new QuotationIterator(this.quotationConfig, text); + } + + static createWithTopLevelQuotesAndNoAmbiguity(): TestEnvironment { + return new TestEnvironment( + new QuotationConfig.Builder() + .setTopLevelQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .build(), + ); + } + + static createWithTopLevelQuotes(): TestEnvironment { + return new TestEnvironment( + new QuotationConfig.Builder() + .setTopLevelQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .mapAmbiguousQuotationMark('"', '\u201C') + .mapAmbiguousQuotationMark('"', '\u201D') + .build(), + ); + } + + static createWithFullEnglishQuotes(): TestEnvironment { + return new TestEnvironment( + new QuotationConfig.Builder() + .setTopLevelQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u201C', + closingPunctuationMark: '\u201D', + }) + .addNestedQuotationMarks({ + openingPunctuationMark: '\u2018', + closingPunctuationMark: '\u2019', + }) + .mapAmbiguousQuotationMark('"', '\u201C') + .mapAmbiguousQuotationMark('"', '\u201D') + .mapAmbiguousQuotationMark("'", '\u2018') + .mapAmbiguousQuotationMark("'", '\u2019') + .ignoreMatchingQuotationMarks( + // possessives and contractions + new StringContextMatcher.Builder() + .setCenterContent(/^['\u2019]$/) + .setLeftContext(/\w$/) + .setRightContext(/^\w/) + .build(), + ) + .ignoreMatchingQuotationMarks( + // for possessives ending in "s", e.g. "Moses'" + new StringContextMatcher.Builder() + .setCenterContent(/^['\u2019]$/) + .setLeftContext(/\ws$/) + .setRightContext(/^[ \n,.:;]/) + .build(), + ) + .setNestingWarningDepth(QuotationDepth.fromNumber(4)) + .build(), + ); + } +}