From ce43e83fb2eeea6da494acbba87f192c9490699a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 22 Sep 2020 19:19:02 +0200 Subject: [PATCH] fix(algolia): warn when an attribute cannot be highlighted --- jest.config.js | 5 +- package.json | 7 +- .../src/highlight/__tests__/highlight.test.ts | 413 ------------------ .../parseAlgoliaHitHighlight.test.ts | 152 +++++++ .../parseAlgoliaHitReverseHighlight.test.ts | 169 +++++++ .../parseAlgoliaHitReverseSnippet.test.ts | 143 ++++++ .../__tests__/parseAlgoliaHitSnippet.test.ts | 143 ++++++ .../src/highlight/getAttributeValueByPath.ts | 13 +- .../src/highlight/parseAlgoliaHitHighlight.ts | 19 +- .../src/highlight/parseAlgoliaHitSnippet.ts | 19 +- .../src/utils/__tests__/warn.test.ts | 26 ++ .../src/utils/index.ts | 1 + .../src/utils/warn.ts | 15 + .../jest/matchers/__tests__/toWarn.test.ts | 48 ++ scripts/jest/matchers/index.ts | 1 + scripts/jest/matchers/toWarn.d.ts | 10 + scripts/jest/matchers/toWarn.ts | 50 +++ scripts/jest/setupTests.ts | 9 + yarn.lock | 108 +++-- 19 files changed, 884 insertions(+), 467 deletions(-) delete mode 100644 packages/autocomplete-preset-algolia/src/highlight/__tests__/highlight.test.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitHighlight.test.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseHighlight.test.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseSnippet.test.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitSnippet.test.ts create mode 100644 packages/autocomplete-preset-algolia/src/utils/__tests__/warn.test.ts create mode 100644 packages/autocomplete-preset-algolia/src/utils/index.ts create mode 100644 packages/autocomplete-preset-algolia/src/utils/warn.ts create mode 100644 scripts/jest/matchers/__tests__/toWarn.test.ts create mode 100644 scripts/jest/matchers/index.ts create mode 100644 scripts/jest/matchers/toWarn.d.ts create mode 100644 scripts/jest/matchers/toWarn.ts create mode 100644 scripts/jest/setupTests.ts diff --git a/jest.config.js b/jest.config.js index 667499ede..a2112a701 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,7 +2,10 @@ module.exports = { rootDir: process.cwd(), - setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'], + setupFilesAfterEnv: [ + '@testing-library/jest-dom/extend-expect', + './scripts/jest/setupTests.ts', + ], testPathIgnorePatterns: ['node_modules/', 'dist/', 'cypress/'], watchPlugins: [ 'jest-watch-typeahead/filename', diff --git a/package.json b/package.json index fb2a21053..cb978b820 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "@testing-library/jest-dom": "5.11.4", "@testing-library/preact": "2.0.0", "@testing-library/user-event": "12.1.3", + "@types/jest": "^26.0.14", + "@types/jest-diff": "^24.3.0", "@types/react": "^16.9.49", "@types/react-dom": "^16.9.8", "@typescript-eslint/eslint-plugin": "2.19.0", @@ -56,12 +58,13 @@ "eslint-plugin-cypress": "2.11.1", "eslint-plugin-eslint-comments": "3.2.0", "eslint-plugin-import": "2.22.0", - "eslint-plugin-jest": "23.20.0", + "eslint-plugin-jest": "24.0.2", "eslint-plugin-prettier": "3.1.4", "eslint-plugin-react": "7.20.6", "eslint-plugin-react-hooks": "2.3.0", "jest": "26.4.2", - "jest-watch-typeahead": "0.6.0", + "jest-diff": "26.4.2", + "jest-watch-typeahead": "0.6.1", "lerna": "3.22.1", "prettier": "2.1.1", "react": "16.13.1", diff --git a/packages/autocomplete-preset-algolia/src/highlight/__tests__/highlight.test.ts b/packages/autocomplete-preset-algolia/src/highlight/__tests__/highlight.test.ts deleted file mode 100644 index 1f13575d3..000000000 --- a/packages/autocomplete-preset-algolia/src/highlight/__tests__/highlight.test.ts +++ /dev/null @@ -1,413 +0,0 @@ -import { parseAlgoliaHitHighlight } from '../parseAlgoliaHitHighlight'; -import { parseAlgoliaHitReverseHighlight } from '../parseAlgoliaHitReverseHighlight'; -import { parseAlgoliaHitReverseSnippet } from '../parseAlgoliaHitReverseSnippet'; -import { parseAlgoliaHitSnippet } from '../parseAlgoliaHitSnippet'; - -describe('highlight', () => { - describe('parseAlgoliaHitHighlight', () => { - test('returns the highlighted parts of the hit', () => { - expect( - parseAlgoliaHitHighlight({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _highlightResult: { - title: { - value: - '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', - matchLevel: 'partial', - matchedWords: [], - fullyHighlighted: false, - }, - }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": true, - "value": "He", - }, - Object { - "isHighlighted": false, - "value": "llo t", - }, - Object { - "isHighlighted": true, - "value": "he", - }, - Object { - "isHighlighted": false, - "value": "re", - }, - ] - `); - }); - - test('escapes characters', () => { - expect( - parseAlgoliaHitHighlight({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _highlightResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": true, - "value": "Food", - }, - Object { - "isHighlighted": false, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - - test('do not escape ignored characters', () => { - expect( - parseAlgoliaHitHighlight({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _highlightResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - ignoreEscape: ["'"], - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": true, - "value": "Food", - }, - Object { - "isHighlighted": false, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - }); - - describe('parseAlgoliaHitReverseHighlight', () => { - test('returns the reverse-highlighted parts of the hit', () => { - expect( - parseAlgoliaHitReverseHighlight({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _highlightResult: { - title: { - value: - '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', - }, - }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": false, - "value": "He", - }, - Object { - "isHighlighted": true, - "value": "llo t", - }, - Object { - "isHighlighted": false, - "value": "he", - }, - Object { - "isHighlighted": true, - "value": "re", - }, - ] - `); - }); - - test('returns the non-highlighted parts when every part matches', () => { - expect( - parseAlgoliaHitReverseHighlight({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _highlightResult: { title: { value: 'Hello' } }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": false, - "value": "Hello", - }, - ] - `); - }); - - test('escapes characters', () => { - expect( - parseAlgoliaHitReverseHighlight({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _highlightResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": false, - "value": "Food", - }, - Object { - "isHighlighted": true, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - - test('do not escape ignored characters', () => { - expect( - parseAlgoliaHitReverseHighlight({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _highlightResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - ignoreEscape: ["'"], - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": false, - "value": "Food", - }, - Object { - "isHighlighted": true, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - }); - - describe('parseAlgoliaHitReverseSnippet', () => { - test('returns the highlighted snippet parts of the hit', () => { - expect( - parseAlgoliaHitReverseSnippet({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _snippetResult: { - title: { - value: - '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', - }, - }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": false, - "value": "He", - }, - Object { - "isHighlighted": true, - "value": "llo t", - }, - Object { - "isHighlighted": false, - "value": "he", - }, - Object { - "isHighlighted": true, - "value": "re", - }, - ] - `); - }); - - test('escapes characters', () => { - expect( - parseAlgoliaHitReverseSnippet({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _snippetResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": false, - "value": "Food", - }, - Object { - "isHighlighted": true, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - - test('do not escape ignored characters', () => { - expect( - parseAlgoliaHitReverseSnippet({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _snippetResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - ignoreEscape: ["'"], - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": false, - "value": "Food", - }, - Object { - "isHighlighted": true, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - }); - - describe('parseAlgoliaHitSnippet', () => { - test('returns the highlighted snippet parts of the hit', () => { - expect( - parseAlgoliaHitSnippet({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _snippetResult: { - title: { - value: - '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', - }, - }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": true, - "value": "He", - }, - Object { - "isHighlighted": false, - "value": "llo t", - }, - Object { - "isHighlighted": true, - "value": "he", - }, - Object { - "isHighlighted": false, - "value": "re", - }, - ] - `); - }); - }); - - test('escapes characters', () => { - expect( - parseAlgoliaHitSnippet({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _snippetResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": true, - "value": "Food", - }, - Object { - "isHighlighted": false, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - - test('do not escape ignored characters', () => { - expect( - parseAlgoliaHitSnippet({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _snippetResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - ignoreEscape: ["'"], - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": true, - "value": "Food", - }, - Object { - "isHighlighted": false, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); -}); diff --git a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitHighlight.test.ts b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitHighlight.test.ts new file mode 100644 index 000000000..9aeb5509c --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitHighlight.test.ts @@ -0,0 +1,152 @@ +import { warnCache } from '../../utils'; +import { parseAlgoliaHitHighlight } from '../parseAlgoliaHitHighlight'; + +describe('parseAlgoliaHitHighlight', () => { + afterEach(() => { + warnCache.current = {}; + }); + + test('returns the highlighted parts of the hit', () => { + expect( + parseAlgoliaHitHighlight({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _highlightResult: { + title: { + value: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + matchLevel: 'partial', + matchedWords: [], + fullyHighlighted: false, + }, + }, + }, + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": true, + "value": "He", + }, + Object { + "isHighlighted": false, + "value": "llo t", + }, + Object { + "isHighlighted": true, + "value": "he", + }, + Object { + "isHighlighted": false, + "value": "re", + }, + ] + `); + }); + + test('escapes characters', () => { + expect( + parseAlgoliaHitHighlight({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _highlightResult: { + title: { + value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, + }, + }, + }, + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": true, + "value": "Food", + }, + Object { + "isHighlighted": false, + "value": " & <Drinks> 'n' "Music"", + }, + ] + `); + }); + + test('does not escape ignored characters', () => { + expect( + parseAlgoliaHitHighlight({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _highlightResult: { + title: { + value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, + }, + }, + }, + ignoreEscape: ["'"], + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": true, + "value": "Food", + }, + Object { + "isHighlighted": false, + "value": " & <Drinks> 'n' "Music"", + }, + ] + `); + }); + + test('returns empty if the attribute cannot be highlighted', () => { + expect( + parseAlgoliaHitHighlight({ + attribute: 'description', + hit: { + objectID: '1', + title: 'Hello there', + description: 'Welcome all', + _highlightResult: { + title: { + value: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + matchLevel: 'partial', + matchedWords: [], + fullyHighlighted: false, + }, + }, + }, + }) + ).toEqual([]); + }); + + test('warns if the attribute cannot be highlighted', () => { + expect(() => { + parseAlgoliaHitHighlight({ + attribute: 'description', + hit: { + objectID: '1', + title: 'Hello there', + description: 'Welcome all', + _highlightResult: { + title: { + value: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + matchLevel: 'partial', + matchedWords: [], + fullyHighlighted: false, + }, + }, + }, + }); + }).toWarn( + 'The attribute "_highlightResult.description.value" does not exist on the hit. Did you set it in `attributesToHighlight`?' + + '\nSee https://www.algolia.com/doc/api-reference/api-parameters/attributesToHighlight/' + ); + }); +}); diff --git a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseHighlight.test.ts b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseHighlight.test.ts new file mode 100644 index 000000000..9eb3dd93e --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseHighlight.test.ts @@ -0,0 +1,169 @@ +import { warnCache } from '../../utils'; +import { parseAlgoliaHitReverseHighlight } from '../parseAlgoliaHitReverseHighlight'; + +describe('parseAlgoliaHitReverseHighlight', () => { + afterEach(() => { + warnCache.current = {}; + }); + + test('returns the reverse-highlighted parts of the hit', () => { + expect( + parseAlgoliaHitReverseHighlight({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _highlightResult: { + title: { + value: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + }, + }, + }, + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": false, + "value": "He", + }, + Object { + "isHighlighted": true, + "value": "llo t", + }, + Object { + "isHighlighted": false, + "value": "he", + }, + Object { + "isHighlighted": true, + "value": "re", + }, + ] + `); + }); + + test('returns the non-highlighted parts when every part matches', () => { + expect( + parseAlgoliaHitReverseHighlight({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _highlightResult: { title: { value: 'Hello' } }, + }, + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": false, + "value": "Hello", + }, + ] + `); + }); + + test('escapes characters', () => { + expect( + parseAlgoliaHitReverseHighlight({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _highlightResult: { + title: { + value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, + }, + }, + }, + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": false, + "value": "Food", + }, + Object { + "isHighlighted": true, + "value": " & <Drinks> 'n' "Music"", + }, + ] + `); + }); + + test('does not escape ignored characters', () => { + expect( + parseAlgoliaHitReverseHighlight({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _highlightResult: { + title: { + value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, + }, + }, + }, + ignoreEscape: ["'"], + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": false, + "value": "Food", + }, + Object { + "isHighlighted": true, + "value": " & <Drinks> 'n' "Music"", + }, + ] + `); + }); + + test('returns empty if the attribute cannot be highlighted', () => { + expect( + parseAlgoliaHitReverseHighlight({ + attribute: 'description', + hit: { + objectID: '1', + title: 'Hello there', + description: 'Welcome all', + _highlightResult: { + title: { + value: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + matchLevel: 'partial', + matchedWords: [], + fullyHighlighted: false, + }, + }, + }, + }) + ).toEqual([]); + }); + + test('warns if the attribute cannot be highlighted', () => { + expect(() => { + parseAlgoliaHitReverseHighlight({ + attribute: 'description', + hit: { + objectID: '1', + title: 'Hello there', + description: 'Welcome all', + _highlightResult: { + title: { + value: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + matchLevel: 'partial', + matchedWords: [], + fullyHighlighted: false, + }, + }, + }, + }); + }).toWarn( + 'The attribute "_highlightResult.description.value" does not exist on the hit. Did you set it in `attributesToHighlight`?' + + '\nSee https://www.algolia.com/doc/api-reference/api-parameters/attributesToHighlight/' + ); + }); +}); diff --git a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseSnippet.test.ts b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseSnippet.test.ts new file mode 100644 index 000000000..befaaf59a --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseSnippet.test.ts @@ -0,0 +1,143 @@ +import { warnCache } from '../../utils'; +import { parseAlgoliaHitReverseSnippet } from '../parseAlgoliaHitReverseSnippet'; + +describe('parseAlgoliaHitReverseSnippet', () => { + afterEach(() => { + warnCache.current = {}; + }); + + test('returns the highlighted snippet parts of the hit', () => { + expect( + parseAlgoliaHitReverseSnippet({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _snippetResult: { + title: { + value: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + }, + }, + }, + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": false, + "value": "He", + }, + Object { + "isHighlighted": true, + "value": "llo t", + }, + Object { + "isHighlighted": false, + "value": "he", + }, + Object { + "isHighlighted": true, + "value": "re", + }, + ] + `); + }); + + test('escapes characters', () => { + expect( + parseAlgoliaHitReverseSnippet({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _snippetResult: { + title: { + value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, + }, + }, + }, + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": false, + "value": "Food", + }, + Object { + "isHighlighted": true, + "value": " & <Drinks> 'n' "Music"", + }, + ] + `); + }); + + test('does not escape ignored characters', () => { + expect( + parseAlgoliaHitReverseSnippet({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _snippetResult: { + title: { + value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, + }, + }, + }, + ignoreEscape: ["'"], + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": false, + "value": "Food", + }, + Object { + "isHighlighted": true, + "value": " & <Drinks> 'n' "Music"", + }, + ] + `); + }); + + test('returns empty if the attribute cannot be snippeted', () => { + expect( + parseAlgoliaHitReverseSnippet({ + attribute: 'description', + hit: { + objectID: '1', + title: 'Hello there', + description: 'Welcome all', + _snippetResult: { + title: { + value: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + }, + }, + }, + }) + ).toEqual([]); + }); + + test('warns if the attribute cannot be snippeted', () => { + expect(() => { + parseAlgoliaHitReverseSnippet({ + attribute: 'description', + hit: { + objectID: '1', + title: 'Hello there', + description: 'Welcome all', + _snippetResult: { + title: { + value: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + }, + }, + }, + }); + }).toWarn( + 'The attribute "_snippetResult.description.value" does not exist on the hit. Did you set it in `attributesToSnippet`?' + + '\nSee https://www.algolia.com/doc/api-reference/api-parameters/attributesToSnippet/' + ); + }); +}); diff --git a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitSnippet.test.ts b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitSnippet.test.ts new file mode 100644 index 000000000..e897c2e81 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitSnippet.test.ts @@ -0,0 +1,143 @@ +import { warnCache } from '../../utils'; +import { parseAlgoliaHitSnippet } from '../parseAlgoliaHitSnippet'; + +describe('parseAlgoliaHitSnippet', () => { + afterEach(() => { + warnCache.current = {}; + }); + + test('returns the highlighted snippet parts of the hit', () => { + expect( + parseAlgoliaHitSnippet({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _snippetResult: { + title: { + value: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + }, + }, + }, + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": true, + "value": "He", + }, + Object { + "isHighlighted": false, + "value": "llo t", + }, + Object { + "isHighlighted": true, + "value": "he", + }, + Object { + "isHighlighted": false, + "value": "re", + }, + ] + `); + }); + + test('escapes characters', () => { + expect( + parseAlgoliaHitSnippet({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _snippetResult: { + title: { + value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, + }, + }, + }, + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": true, + "value": "Food", + }, + Object { + "isHighlighted": false, + "value": " & <Drinks> 'n' "Music"", + }, + ] + `); + }); + + test('does not escape ignored characters', () => { + expect( + parseAlgoliaHitSnippet({ + attribute: 'title', + hit: { + objectID: '1', + title: 'Hello there', + _snippetResult: { + title: { + value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, + }, + }, + }, + ignoreEscape: ["'"], + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "isHighlighted": true, + "value": "Food", + }, + Object { + "isHighlighted": false, + "value": " & <Drinks> 'n' "Music"", + }, + ] + `); + }); + + test('returns empty if the attribute cannot be snippeted', () => { + expect( + parseAlgoliaHitSnippet({ + attribute: 'description', + hit: { + objectID: '1', + title: 'Hello there', + description: 'Welcome all', + _snippetResult: { + title: { + value: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + }, + }, + }, + }) + ).toEqual([]); + }); + + test('warns if the attribute cannot be snippeted', () => { + expect(() => { + parseAlgoliaHitSnippet({ + attribute: 'description', + hit: { + objectID: '1', + title: 'Hello there', + description: 'Welcome all', + _snippetResult: { + title: { + value: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + }, + }, + }, + }); + }).toWarn( + 'The attribute "_snippetResult.description.value" does not exist on the hit. Did you set it in `attributesToSnippet`?' + + '\nSee https://www.algolia.com/doc/api-reference/api-parameters/attributesToSnippet/' + ); + }); +}); diff --git a/packages/autocomplete-preset-algolia/src/highlight/getAttributeValueByPath.ts b/packages/autocomplete-preset-algolia/src/highlight/getAttributeValueByPath.ts index 1c7606466..5d19c4e52 100644 --- a/packages/autocomplete-preset-algolia/src/highlight/getAttributeValueByPath.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/getAttributeValueByPath.ts @@ -1,15 +1,6 @@ -export function getAttributeValueByPath(hit: THit, path: string): string { +export function getAttributeValueByPath(hit: THit, path: string): any { const parts = path.split('.'); - const value = parts.reduce( - (current, key) => current && current[key], - hit as any - ); - - if (typeof value !== 'string') { - throw new Error( - `The attribute ${JSON.stringify(path)} does not exist on the hit.` - ); - } + const value = parts.reduce((current, key) => current && current[key], hit); return value; } diff --git a/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitHighlight.ts b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitHighlight.ts index 25091ba35..f275e0c4c 100644 --- a/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitHighlight.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitHighlight.ts @@ -1,5 +1,7 @@ import { Hit } from '@algolia/client-search'; +import { warn } from '../utils'; + import { getAttributeValueByPath } from './getAttributeValueByPath'; import { ParseAlgoliaHitParams } from './ParseAlgoliaHitParams'; import { parseAttribute } from './parseAttribute'; @@ -10,10 +12,19 @@ export function parseAlgoliaHitHighlight>({ attribute, ignoreEscape, }: ParseAlgoliaHitParams): ParsedAttribute[] { - const highlightedValue = getAttributeValueByPath( - hit, - `_highlightResult.${attribute}.value` - ); + const path = `_highlightResult.${attribute}.value`; + let highlightedValue = getAttributeValueByPath(hit, path); + + if (typeof highlightedValue !== 'string') { + warn( + `The attribute ${JSON.stringify( + path + )} does not exist on the hit. Did you set it in \`attributesToHighlight\`?` + + '\nSee https://www.algolia.com/doc/api-reference/api-parameters/attributesToHighlight/' + ); + + highlightedValue = ''; + } return parseAttribute({ highlightedValue, diff --git a/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitSnippet.ts b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitSnippet.ts index 81f8b8b22..ea03d426f 100644 --- a/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitSnippet.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitSnippet.ts @@ -1,5 +1,7 @@ import { Hit } from '@algolia/client-search'; +import { warn } from '../utils'; + import { getAttributeValueByPath } from './getAttributeValueByPath'; import { ParseAlgoliaHitParams } from './ParseAlgoliaHitParams'; import { parseAttribute } from './parseAttribute'; @@ -10,10 +12,19 @@ export function parseAlgoliaHitSnippet>({ attribute, ignoreEscape, }: ParseAlgoliaHitParams): ParsedAttribute[] { - const highlightedValue = getAttributeValueByPath( - hit, - `_snippetResult.${attribute}.value` - ); + const path = `_snippetResult.${attribute}.value`; + let highlightedValue = getAttributeValueByPath(hit, path); + + if (typeof highlightedValue !== 'string') { + warn( + `The attribute ${JSON.stringify( + path + )} does not exist on the hit. Did you set it in \`attributesToSnippet\`?` + + '\nSee https://www.algolia.com/doc/api-reference/api-parameters/attributesToSnippet/' + ); + + highlightedValue = ''; + } return parseAttribute({ highlightedValue, diff --git a/packages/autocomplete-preset-algolia/src/utils/__tests__/warn.test.ts b/packages/autocomplete-preset-algolia/src/utils/__tests__/warn.test.ts new file mode 100644 index 000000000..255922067 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/utils/__tests__/warn.test.ts @@ -0,0 +1,26 @@ +/* eslint-disable no-console */ + +import { warn } from '../warn'; + +describe('warn', () => { + test('trims the message', () => { + expect(() => { + warn('\nwarning! '); + }).toWarn('warning!'); + }); + + test('warns a message a single time', () => { + const originalConsoleWarn = console.warn; + console.warn = jest.fn(); + + warn('warning1'); + warn('warning1'); + warn('warning2'); + + expect(console.warn).toHaveBeenCalledTimes(2); + expect(console.warn).toHaveBeenNthCalledWith(1, 'warning1'); + expect(console.warn).toHaveBeenNthCalledWith(2, 'warning2'); + + console.warn = originalConsoleWarn; + }); +}); diff --git a/packages/autocomplete-preset-algolia/src/utils/index.ts b/packages/autocomplete-preset-algolia/src/utils/index.ts new file mode 100644 index 000000000..4ecb46b44 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/utils/index.ts @@ -0,0 +1 @@ +export * from './warn'; diff --git a/packages/autocomplete-preset-algolia/src/utils/warn.ts b/packages/autocomplete-preset-algolia/src/utils/warn.ts new file mode 100644 index 000000000..353fcbdd0 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/utils/warn.ts @@ -0,0 +1,15 @@ +export const warnCache = { + current: {}, +}; + +export function warn(message: string) { + const sanitizedMessage = message.trim(); + const hasAlreadyPrinted = warnCache.current[sanitizedMessage]; + + if (!hasAlreadyPrinted) { + warnCache.current[sanitizedMessage] = true; + + // eslint-disable-next-line no-console + console.warn(sanitizedMessage); + } +} diff --git a/scripts/jest/matchers/__tests__/toWarn.test.ts b/scripts/jest/matchers/__tests__/toWarn.test.ts new file mode 100644 index 000000000..9f79a2d14 --- /dev/null +++ b/scripts/jest/matchers/__tests__/toWarn.test.ts @@ -0,0 +1,48 @@ +/* eslint-disable no-console */ + +describe('toWarn', () => { + describe('usage', () => { + test('fails with incorrect type of message', () => { + expect(() => { + // @ts-ignore:next-line + expect(() => {}).toWarn(false); + }).toThrowErrorMatchingInlineSnapshot( + `"toWarn() requires a parameter of type string but was given boolean."` + ); + }); + }); + + describe('without message', () => { + test('does not fail if called', () => { + expect(() => { + expect(() => { + console.warn('warning'); + }).toWarn(); + }).not.toThrow(); + }); + + test('fails if not called', () => { + expect(() => { + expect(() => {}).toWarn(); + }).toThrowErrorMatchingInlineSnapshot(`"No warning recorded."`); + }); + }); + + describe('with message', () => { + test('does not fail with correct message', () => { + expect(() => { + expect(() => { + console.warn('warning'); + }).toWarn('warning'); + }).not.toThrow(); + }); + + test('fails if a warning is not correct', () => { + expect(() => { + expect(() => { + console.warn('warning'); + }).toWarn('another warning'); + }).toThrow(/Unexpected warning recorded./); + }); + }); +}); diff --git a/scripts/jest/matchers/index.ts b/scripts/jest/matchers/index.ts new file mode 100644 index 000000000..b1f767298 --- /dev/null +++ b/scripts/jest/matchers/index.ts @@ -0,0 +1 @@ +export * from './toWarn'; diff --git a/scripts/jest/matchers/toWarn.d.ts b/scripts/jest/matchers/toWarn.d.ts new file mode 100644 index 000000000..f64ed98e3 --- /dev/null +++ b/scripts/jest/matchers/toWarn.d.ts @@ -0,0 +1,10 @@ +/* eslint-disable @typescript-eslint/generic-type-naming */ + +declare namespace jest { + interface Matchers { + /** + * Ensures that a warning is triggered when the callback is called. + */ + toWarn(expectedMessage?: string): CustomMatcherResult; + } +} diff --git a/scripts/jest/matchers/toWarn.ts b/scripts/jest/matchers/toWarn.ts new file mode 100644 index 000000000..b134777fc --- /dev/null +++ b/scripts/jest/matchers/toWarn.ts @@ -0,0 +1,50 @@ +/* eslint-disable no-console */ + +import jestDiff from 'jest-diff'; + +export const toWarn: jest.CustomMatcher = ( + callback: () => void, + expectedMessage: string +) => { + if (expectedMessage !== undefined && typeof expectedMessage !== 'string') { + throw new Error( + `toWarn() requires a parameter of type string but was given ${typeof expectedMessage}.` + ); + } + + const originalWarnMethod = console.warn; + let calledTimes = 0; + let actualWarning = ''; + + console.warn = (message: string) => { + calledTimes++; + actualWarning = message; + }; + + callback(); + + console.warn = originalWarnMethod; + + // Expectation without any message. + // We only check that `console.warn` was called. + if (expectedMessage === undefined && calledTimes === 0) { + return { + pass: false, + message: () => 'No warning recorded.', + }; + } + + // Expectation with a message. + if (expectedMessage !== undefined && actualWarning !== expectedMessage) { + return { + pass: false, + message: () => `Unexpected warning recorded. + +Difference: + +${jestDiff(expectedMessage, actualWarning)}`, + }; + } + + return { pass: true, message: () => '' }; +}; diff --git a/scripts/jest/setupTests.ts b/scripts/jest/setupTests.ts new file mode 100644 index 000000000..227e0ef8b --- /dev/null +++ b/scripts/jest/setupTests.ts @@ -0,0 +1,9 @@ +import { toWarn } from './matchers'; + +expect.extend({ toWarn }); + +global.console.warn = jest.fn(); +global.console.log = jest.fn(); +global.console.error = jest.fn(); +global.console.info = jest.fn(); +global.console.debug = jest.fn(); diff --git a/yarn.lock b/yarn.lock index 42f00e82b..53837445c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3441,6 +3441,13 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest-diff@^24.3.0": + version "24.3.0" + resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-24.3.0.tgz#29e237a3d954babfe6e23cc59b57ecd8ca8d858d" + integrity sha512-vx1CRDeDUwQ0Pc7v+hS61O1ETA81kD04IMEC0hS1kPyVtHDdZrokAvpF7MT9VI/fVSzicelUZNCepDvhRV1PeA== + dependencies: + jest-diff "*" + "@types/jest@*": version "26.0.13" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.13.tgz#5a7b9d5312f5dd521a38329c38ee9d3802a0b85e" @@ -3449,6 +3456,14 @@ jest-diff "^25.2.1" pretty-format "^25.2.1" +"@types/jest@^26.0.14": + version "26.0.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.14.tgz#078695f8f65cb55c5a98450d65083b2b73e5a3f3" + integrity sha512-Hz5q8Vu0D288x3iWXePSn53W7hAjP0H7EQ6QvDO9c7t46mR0lNOLlfuwQ+JkVxuhygHzlzPX+0jKdA3ZgSh+Vg== + dependencies: + jest-diff "^25.2.1" + pretty-format "^25.2.1" + "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5": version "7.0.6" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" @@ -3637,13 +3652,15 @@ "@typescript-eslint/typescript-estree" "2.19.0" eslint-scope "^5.0.0" -"@typescript-eslint/experimental-utils@^2.5.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" - integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== +"@typescript-eslint/experimental-utils@^4.0.1": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.2.0.tgz#3d0b5cd4aa61f5eb7aa1e873dea0db1410b062d2" + integrity sha512-5BBj6BjgHEndBaQQpUVzRIPERz03LBc0MCQkHwUaH044FJFL08SwWv/sQftk7gf0ShZ2xZysz0LTwCwNt4Xu3w== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.34.0" + "@typescript-eslint/scope-manager" "4.2.0" + "@typescript-eslint/types" "4.2.0" + "@typescript-eslint/typescript-estree" "4.2.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -3657,6 +3674,19 @@ "@typescript-eslint/typescript-estree" "2.19.0" eslint-visitor-keys "^1.1.0" +"@typescript-eslint/scope-manager@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.2.0.tgz#d10e6854a65e175b22a28265d372a97c8cce4bfc" + integrity sha512-Tb402cxxObSxWIVT+PnBp5ruT2V/36yj6gG4C9AjkgRlZpxrLAzWDk3neen6ToMBGeGdxtnfFLoJRUecGz9mYQ== + dependencies: + "@typescript-eslint/types" "4.2.0" + "@typescript-eslint/visitor-keys" "4.2.0" + +"@typescript-eslint/types@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.2.0.tgz#6f6b094329e72040f173123832397c7c0b910fc8" + integrity sha512-xkv5nIsxfI/Di9eVwN+G9reWl7Me9R5jpzmZUch58uQ7g0/hHVuGUbbn4NcxcM5y/R4wuJIIEPKPDb5l4Fdmwg== + "@typescript-eslint/typescript-estree@2.19.0": version "2.19.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.19.0.tgz#6bd7310b9827e04756fe712909f26956aac4b196" @@ -3670,19 +3700,28 @@ semver "^6.3.0" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" - integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== +"@typescript-eslint/typescript-estree@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.2.0.tgz#9d746240991c305bf225ad5e96cbf57e7fea0551" + integrity sha512-iWDLCB7z4MGkLipduF6EOotdHNtgxuNKnYD54nMS/oitFnsk4S3S/TE/UYXQTra550lHtlv9eGmp+dvN9pUDtA== dependencies: + "@typescript-eslint/types" "4.2.0" + "@typescript-eslint/visitor-keys" "4.2.0" debug "^4.1.1" - eslint-visitor-keys "^1.1.0" - glob "^7.1.6" + globby "^11.0.1" is-glob "^4.0.1" lodash "^4.17.15" semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/visitor-keys@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.2.0.tgz#ae13838e3a260b63ae51021ecaf1d0cdea8dbba5" + integrity sha512-WIf4BNOlFOH2W+YqGWa6YKLcK/EB3gEj2apCrqLw6mme1RzBy0jtJ9ewJgnrZDB640zfnv8L+/gwGH5sYp/rGw== + dependencies: + "@typescript-eslint/types" "4.2.0" + eslint-visitor-keys "^2.0.0" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -7531,12 +7570,12 @@ eslint-plugin-import@2.22.0: resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-plugin-jest@23.20.0: - version "23.20.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.20.0.tgz#e1d69c75f639e99d836642453c4e75ed22da4099" - integrity sha512-+6BGQt85OREevBDWCvhqj1yYA4+BFK4XnRZSGJionuEYmcglMZYLNNBBemwzbqUAckURaHdJSBcjHPyrtypZOw== +eslint-plugin-jest@24.0.2: + version "24.0.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.0.2.tgz#4bf0fcdc86289d702a7dacb430b4363482af773b" + integrity sha512-DSBLNpkKDOpUJQkTGSs5sVJWsu0nDyQ2rYxkr0Eh7nrkc5bMUr/dlDbtTj3l8y6UaCVsem6rryF1OZrKnz1S5g== dependencies: - "@typescript-eslint/experimental-utils" "^2.5.0" + "@typescript-eslint/experimental-utils" "^4.0.1" eslint-plugin-prettier@3.1.4: version "3.1.4" @@ -7602,6 +7641,11 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + eslint@6.8.0: version "6.8.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" @@ -10332,6 +10376,16 @@ jest-config@^26.4.2: micromatch "^4.0.2" pretty-format "^26.4.2" +jest-diff@*, jest-diff@26.4.2, jest-diff@^26.4.2: + version "26.4.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.4.2.tgz#a1b7b303bcc534aabdb3bd4a7caf594ac059f5aa" + integrity sha512-6T1XQY8U28WH0Z5rGpQ+VqZSZz8EN8rZcBtfvXaOkbwxIEeRre6qnuZQlbY1AJ4MKDxQF8EkrCvK+hL/VkyYLQ== + dependencies: + chalk "^4.0.0" + diff-sequences "^26.3.0" + jest-get-type "^26.3.0" + pretty-format "^26.4.2" + jest-diff@^25.2.1: version "25.5.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" @@ -10342,16 +10396,6 @@ jest-diff@^25.2.1: jest-get-type "^25.2.6" pretty-format "^25.5.0" -jest-diff@^26.4.2: - version "26.4.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.4.2.tgz#a1b7b303bcc534aabdb3bd4a7caf594ac059f5aa" - integrity sha512-6T1XQY8U28WH0Z5rGpQ+VqZSZz8EN8rZcBtfvXaOkbwxIEeRre6qnuZQlbY1AJ4MKDxQF8EkrCvK+hL/VkyYLQ== - dependencies: - chalk "^4.0.0" - diff-sequences "^26.3.0" - jest-get-type "^26.3.0" - pretty-format "^26.4.2" - jest-docblock@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" @@ -10634,20 +10678,20 @@ jest-validate@^26.4.2: leven "^3.1.0" pretty-format "^26.4.2" -jest-watch-typeahead@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-0.6.0.tgz#cb4a2d14def3fb64950fec22edb912f93246b24f" - integrity sha512-mY0u5D1U/mEzeO/tpcDWnWaakuQiBp8gZY1K0HxduMeKSYm4WvbBVfU15a/hWMMs4J9FrXaYjAWs5XGlOpx5IQ== +jest-watch-typeahead@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-0.6.1.tgz#45221b86bb6710b7e97baaa1640ae24a07785e63" + integrity sha512-ITVnHhj3Jd/QkqQcTqZfRgjfyRhDFM/auzgVo2RKvSwi18YMvh0WvXDJFoFED6c7jd/5jxtu4kSOb9PTu2cPVg== dependencies: ansi-escapes "^4.3.1" chalk "^4.0.0" jest-regex-util "^26.0.0" - jest-watcher "^26.0.0" + jest-watcher "^26.3.0" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" -jest-watcher@^26.0.0, jest-watcher@^26.3.0: +jest-watcher@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.3.0.tgz#f8ef3068ddb8af160ef868400318dc4a898eed08" integrity sha512-XnLdKmyCGJ3VoF6G/p5ohbJ04q/vv5aH9ENI+i6BL0uu9WWB6Z7Z2lhQQk0d2AVZcRGp1yW+/TsoToMhBFPRdQ==