diff --git a/.vscode/settings.json b/.vscode/settings.json index 2fb8b48f..397b9618 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,5 +16,6 @@ }, "editor.formatOnSave": true, "editor.detectIndentation": true, - "editor.tabSize": 2 + "editor.tabSize": 2, + "javascript.format.semicolons": "remove" } \ No newline at end of file diff --git a/test/server/finders/BookFinder.test.js b/test/server/finders/BookFinder.test.js index 01dcb575..2728f174 100644 --- a/test/server/finders/BookFinder.test.js +++ b/test/server/finders/BookFinder.test.js @@ -1,344 +1,344 @@ -const sinon = require('sinon'); -const chai = require('chai'); -const expect = chai.expect; -const bookFinder = require('../../../server/finders/BookFinder'); +const sinon = require('sinon') +const chai = require('chai') +const expect = chai.expect +const bookFinder = require('../../../server/finders/BookFinder') const { LogLevel } = require('../../../server/utils/constants') const Logger = require('../../../server/Logger') Logger.setLogLevel(LogLevel.INFO) - describe('TitleCandidates', () => { - describe('cleanAuthor non-empty', () => { - let titleCandidates; - const cleanAuthor = 'leo tolstoy'; +describe('TitleCandidates', () => { + describe('cleanAuthor non-empty', () => { + let titleCandidates + const cleanAuthor = 'leo tolstoy' - beforeEach(() => { - titleCandidates = new bookFinder.constructor.TitleCandidates(cleanAuthor); - }); + beforeEach(() => { + titleCandidates = new bookFinder.constructor.TitleCandidates(cleanAuthor) + }) - describe('no adds', () => { - it('returns no candidates', () => { - expect(titleCandidates.getCandidates()).to.deep.equal([]); - }) - }) - - describe('single add', () => { - [ - ['adds candidate', 'anna karenina', ['anna karenina']], - ['adds lowercased candidate', 'ANNA KARENINA', ['anna karenina']], - ['adds candidate, removing redundant spaces', 'anna karenina', ['anna karenina']], - ['adds candidate, removing author', `anna karenina by ${cleanAuthor}`, ['anna karenina']], - ['does not add empty candidate after removing author', cleanAuthor, []], - ['adds candidate, removing subtitle', 'anna karenina: subtitle', ['anna karenina']], - ['adds candidate + variant, removing "by ..."', 'anna karenina by arnold schwarzenegger', ['anna karenina', 'anna karenina by arnold schwarzenegger']], - ['adds candidate + variant, removing bitrate', 'anna karenina 64kbps', ['anna karenina', 'anna karenina 64kbps']], - ['adds candidate + variant, removing edition 1', 'anna karenina 2nd edition', ['anna karenina', 'anna karenina 2nd edition']], - ['adds candidate + variant, removing edition 2', 'anna karenina 4th ed.', ['anna karenina', 'anna karenina 4th ed.']], - ['adds candidate + variant, removing fie type', 'anna karenina.mp3', ['anna karenina', 'anna karenina.mp3']], - ['adds candidate + variant, removing "a novel"', 'anna karenina a novel', ['anna karenina', 'anna karenina a novel']], - ['adds candidate + variant, removing preceding/trailing numbers', '1 anna karenina 2', ['anna karenina', '1 anna karenina 2']], - ['does not add empty candidate', '', []], - ['does not add spaces-only candidate', ' ', []], - ['does not add empty variant', '1984', ['1984']], - ].forEach(([name, title, expected]) => it(name, () => { - titleCandidates.add(title); - expect(titleCandidates.getCandidates()).to.deep.equal(expected); - })); - }) - - describe('multiple adds', () => { - [ - ['demotes digits-only candidates', ['01', 'anna karenina'], ['anna karenina', '01']], - ['promotes transformed variants', ['title1 1', 'title2 1'], ['title1', 'title2', 'title1 1', 'title2 1']], - ['orders by position', ['title2', 'title1'], ['title2', 'title1']], - ['dedupes candidates', ['title1', 'title1'], ['title1']], - ].forEach(([name, titles, expected]) => it(name, () => { - for (const title of titles) titleCandidates.add(title) - expect(titleCandidates.getCandidates()).to.deep.equal(expected); - })); + describe('no adds', () => { + it('returns no candidates', () => { + expect(titleCandidates.getCandidates()).to.deep.equal([]) }) }) - describe('cleanAuthor empty', () => { - let titleCandidates - let cleanAuthor = '' - - beforeEach(() => { - titleCandidates = new bookFinder.constructor.TitleCandidates(cleanAuthor) - }) - - describe('single add', () => { - [ - ['adds a candidate', 'leo tolstoy', ['leo tolstoy']], - ].forEach(([name, title, expected]) => it(name, () => { - titleCandidates.add(title) - expect(titleCandidates.getCandidates()).to.deep.equal(expected); - })) - }) + describe('single add', () => { + [ + ['adds candidate', 'anna karenina', ['anna karenina']], + ['adds lowercased candidate', 'ANNA KARENINA', ['anna karenina']], + ['adds candidate, removing redundant spaces', 'anna karenina', ['anna karenina']], + ['adds candidate, removing author', `anna karenina by ${cleanAuthor}`, ['anna karenina']], + ['does not add empty candidate after removing author', cleanAuthor, []], + ['adds candidate, removing subtitle', 'anna karenina: subtitle', ['anna karenina']], + ['adds candidate + variant, removing "by ..."', 'anna karenina by arnold schwarzenegger', ['anna karenina', 'anna karenina by arnold schwarzenegger']], + ['adds candidate + variant, removing bitrate', 'anna karenina 64kbps', ['anna karenina', 'anna karenina 64kbps']], + ['adds candidate + variant, removing edition 1', 'anna karenina 2nd edition', ['anna karenina', 'anna karenina 2nd edition']], + ['adds candidate + variant, removing edition 2', 'anna karenina 4th ed.', ['anna karenina', 'anna karenina 4th ed.']], + ['adds candidate + variant, removing fie type', 'anna karenina.mp3', ['anna karenina', 'anna karenina.mp3']], + ['adds candidate + variant, removing "a novel"', 'anna karenina a novel', ['anna karenina', 'anna karenina a novel']], + ['adds candidate + variant, removing preceding/trailing numbers', '1 anna karenina 2', ['anna karenina', '1 anna karenina 2']], + ['does not add empty candidate', '', []], + ['does not add spaces-only candidate', ' ', []], + ['does not add empty variant', '1984', ['1984']], + ].forEach(([name, title, expected]) => it(name, () => { + titleCandidates.add(title) + expect(titleCandidates.getCandidates()).to.deep.equal(expected) + })) + }) + + describe('multiple adds', () => { + [ + ['demotes digits-only candidates', ['01', 'anna karenina'], ['anna karenina', '01']], + ['promotes transformed variants', ['title1 1', 'title2 1'], ['title1', 'title2', 'title1 1', 'title2 1']], + ['orders by position', ['title2', 'title1'], ['title2', 'title1']], + ['dedupes candidates', ['title1', 'title1'], ['title1']], + ].forEach(([name, titles, expected]) => it(name, () => { + for (const title of titles) titleCandidates.add(title) + expect(titleCandidates.getCandidates()).to.deep.equal(expected) + })) }) }) - describe('AuthorCandidates', () => { - let authorCandidates; - const audnexus = { - authorASINsRequest: sinon.stub().resolves([ - { name: 'Leo Tolstoy' }, - { name: 'Nikolai Gogol' }, - { name: 'J. K. Rowling' }, - ]), - }; - - describe('cleanAuthor is null', () => { - beforeEach(() => { - authorCandidates = new bookFinder.constructor.AuthorCandidates(null, audnexus); - }); - - describe('no adds', () => { - [ - ['returns empty author candidate', []], - ].forEach(([name, expected]) => it(name, async () => { - expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) - })) - }); - - describe('single add', () => { - [ - ['adds recognized candidate', 'nikolai gogol', ['nikolai gogol']], - ['does not add unrecognized candidate', 'fyodor dostoevsky', []], - ['adds recognized author if candidate is a superstring', 'dr. nikolai gogol', ['nikolai gogol']], - ['adds candidate if it is a substring of recognized author', 'gogol', ['gogol']], - ['adds recognized author if edit distance from candidate is small', 'nicolai gogol', ['nikolai gogol']], - ['does not add candidate if edit distance from any recognized author is large', 'nikolai google', []], - ['adds normalized recognized candidate (contains redundant spaces)', 'nikolai gogol', ['nikolai gogol']], - ['adds normalized recognized candidate (normalized initials)', 'j.k. rowling', ['j. k. rowling']], - ].forEach(([name, author, expected]) => it(name, async () => { - authorCandidates.add(author) - expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) - })); - }) - - describe('multi add', () => { - [ - ['adds recognized author candidates', ['nikolai gogol', 'leo tolstoy'], ['nikolai gogol', 'leo tolstoy']], - ['dedupes author candidates', ['nikolai gogol', 'nikolai gogol'], ['nikolai gogol']], - ].forEach(([name, authors, expected]) => it(name, async () => { - for (const author of authors) authorCandidates.add(author) - expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) - })) - }) - }); - - describe('cleanAuthor is a recognized author', () => { - const cleanAuthor = 'leo tolstoy'; - - beforeEach(() => { - authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus); - }); - - describe('no adds', () => { - [ - ['adds cleanAuthor as candidate', [cleanAuthor]], - ].forEach(([name, expected]) => it(name, async () => { - expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) - })) - }) - - describe('single add', () => { - [ - ['adds recognized candidate', 'nikolai gogol', [cleanAuthor, 'nikolai gogol']], - ['does not add candidate if it is a dupe of cleanAuthor', cleanAuthor, [cleanAuthor]], - ].forEach(([name, author, expected]) => it(name, async () => { - authorCandidates.add(author) - expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) - })) - }) - }); - - describe('cleanAuthor is an unrecognized author', () => { - const cleanAuthor = 'Fyodor Dostoevsky'; - - beforeEach(() => { - authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus); - }); - - describe('no adds', () => { - [ - ['adds cleanAuthor as candidate', [cleanAuthor]], - ].forEach(([name, expected]) => it(name, async () => { - expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) - })) - }) - - describe('single add', () => { - [ - ['adds recognized candidate and removes cleanAuthor', 'nikolai gogol', ['nikolai gogol']], - ['does not add unrecognized candidate', 'jackie chan', [cleanAuthor]], - ].forEach(([name, author, expected]) => it(name, async () => { - authorCandidates.add(author) - expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) - })) - }) - }); - - describe('cleanAuthor is unrecognized and dirty', () => { - describe('no adds', () => { - [ - ['adds aggressively cleaned cleanAuthor', 'fyodor dostoevsky, translated by jackie chan', ['fyodor dostoevsky']], - ['adds cleanAuthor if aggresively cleaned cleanAuthor is empty', ', jackie chan', [', jackie chan']], - ].forEach(([name, cleanAuthor, expected]) => it(name, async () => { - authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus) - expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) - })) - }) - - describe('single add', () => { - [ - ['adds recognized candidate and removes cleanAuthor', 'fyodor dostoevsky, translated by jackie chan', 'nikolai gogol', ['nikolai gogol']], - ].forEach(([name, cleanAuthor, author, expected]) => it(name, async () => { - authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus) - authorCandidates.add(author) - expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) - })) - }) - }); - }); - - describe('search', () => { - const t = 'title'; - const a = 'author'; - const u = 'unrecognized'; - const r = ['book']; - - const runSearchStub = sinon.stub(bookFinder, 'runSearch') - runSearchStub.resolves([]) - runSearchStub.withArgs(t, a).resolves(r); - runSearchStub.withArgs(t, u).resolves(r); - - const audnexusStub = sinon.stub(bookFinder.audnexus, 'authorASINsRequest') - audnexusStub.resolves([ { name: a } ]) + describe('cleanAuthor empty', () => { + let titleCandidates + let cleanAuthor = '' beforeEach(() => { - bookFinder.runSearch.resetHistory(); - }); + titleCandidates = new bookFinder.constructor.TitleCandidates(cleanAuthor) + }) - describe('search title is empty', () => { - it('returns empty result', async () => { - expect(await bookFinder.search('', '', a)).to.deep.equal([]); - sinon.assert.callCount(bookFinder.runSearch, 0); - }); - }); - - describe('search title is a recognized title and search author is a recognized author', () => { - it('returns non-empty result (no fuzzy searches)', async () => { - expect(await bookFinder.search('', t, a)).to.deep.equal(r); - sinon.assert.callCount(bookFinder.runSearch, 1); - }); - }); - - describe('search title contains recognized title and search author is a recognized author', () => { + describe('single add', () => { [ - [`${t} -`], - [`${t} - ${a}`], - [`${a} - ${t}`], - [`${t}- ${a}`], - [`${t} -${a}`], - [`${t} ${a}`], - [`${a} - ${t} (unabridged)`], - [`${a} - ${t} (subtitle) - mp3`], - [`${t} {narrator} - series-01 64kbps 10:00:00`], - [`${a} - ${t} (2006) narrated by narrator [unabridged]`], - [`${t} - ${a} 2022 mp3`], - [`01 ${t}`], - [`2022_${t}_HQ`], - ].forEach(([searchTitle]) => { - it(`search('${searchTitle}', '${a}') returns non-empty result (with 1 fuzzy search)`, async () => { - expect(await bookFinder.search('', searchTitle, a)).to.deep.equal(r); - sinon.assert.callCount(bookFinder.runSearch, 2); - }); - }); + ['adds a candidate', 'leo tolstoy', ['leo tolstoy']], + ].forEach(([name, title, expected]) => it(name, () => { + titleCandidates.add(title) + expect(titleCandidates.getCandidates()).to.deep.equal(expected) + })) + }) + }) +}) +describe('AuthorCandidates', () => { + let authorCandidates + const audnexus = { + authorASINsRequest: sinon.stub().resolves([ + { name: 'Leo Tolstoy' }, + { name: 'Nikolai Gogol' }, + { name: 'J. K. Rowling' }, + ]), + } + + describe('cleanAuthor is null', () => { + beforeEach(() => { + authorCandidates = new bookFinder.constructor.AuthorCandidates(null, audnexus) + }) + + describe('no adds', () => { + [ + ['returns empty author candidate', []], + ].forEach(([name, expected]) => it(name, async () => { + expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) + })) + }) + + describe('single add', () => { + [ + ['adds recognized candidate', 'nikolai gogol', ['nikolai gogol']], + ['does not add unrecognized candidate', 'fyodor dostoevsky', []], + ['adds recognized author if candidate is a superstring', 'dr. nikolai gogol', ['nikolai gogol']], + ['adds candidate if it is a substring of recognized author', 'gogol', ['gogol']], + ['adds recognized author if edit distance from candidate is small', 'nicolai gogol', ['nikolai gogol']], + ['does not add candidate if edit distance from any recognized author is large', 'nikolai google', []], + ['adds normalized recognized candidate (contains redundant spaces)', 'nikolai gogol', ['nikolai gogol']], + ['adds normalized recognized candidate (normalized initials)', 'j.k. rowling', ['j. k. rowling']], + ].forEach(([name, author, expected]) => it(name, async () => { + authorCandidates.add(author) + expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) + })) + }) + + describe('multi add', () => { + [ + ['adds recognized author candidates', ['nikolai gogol', 'leo tolstoy'], ['nikolai gogol', 'leo tolstoy']], + ['dedupes author candidates', ['nikolai gogol', 'nikolai gogol'], ['nikolai gogol']], + ].forEach(([name, authors, expected]) => it(name, async () => { + for (const author of authors) authorCandidates.add(author) + expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) + })) + }) + }) + + describe('cleanAuthor is a recognized author', () => { + const cleanAuthor = 'leo tolstoy' + + beforeEach(() => { + authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus) + }) + + describe('no adds', () => { + [ + ['adds cleanAuthor as candidate', [cleanAuthor]], + ].forEach(([name, expected]) => it(name, async () => { + expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) + })) + }) + + describe('single add', () => { + [ + ['adds recognized candidate', 'nikolai gogol', [cleanAuthor, 'nikolai gogol']], + ['does not add candidate if it is a dupe of cleanAuthor', cleanAuthor, [cleanAuthor]], + ].forEach(([name, author, expected]) => it(name, async () => { + authorCandidates.add(author) + expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) + })) + }) + }) + + describe('cleanAuthor is an unrecognized author', () => { + const cleanAuthor = 'Fyodor Dostoevsky' + + beforeEach(() => { + authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus) + }) + + describe('no adds', () => { + [ + ['adds cleanAuthor as candidate', [cleanAuthor]], + ].forEach(([name, expected]) => it(name, async () => { + expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) + })) + }) + + describe('single add', () => { + [ + ['adds recognized candidate and removes cleanAuthor', 'nikolai gogol', ['nikolai gogol']], + ['does not add unrecognized candidate', 'jackie chan', [cleanAuthor]], + ].forEach(([name, author, expected]) => it(name, async () => { + authorCandidates.add(author) + expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) + })) + }) + }) + + describe('cleanAuthor is unrecognized and dirty', () => { + describe('no adds', () => { + [ + ['adds aggressively cleaned cleanAuthor', 'fyodor dostoevsky, translated by jackie chan', ['fyodor dostoevsky']], + ['adds cleanAuthor if aggresively cleaned cleanAuthor is empty', ', jackie chan', [', jackie chan']], + ].forEach(([name, cleanAuthor, expected]) => it(name, async () => { + authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus) + expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) + })) + }) + + describe('single add', () => { + [ + ['adds recognized candidate and removes cleanAuthor', 'fyodor dostoevsky, translated by jackie chan', 'nikolai gogol', ['nikolai gogol']], + ].forEach(([name, cleanAuthor, author, expected]) => it(name, async () => { + authorCandidates = new bookFinder.constructor.AuthorCandidates(cleanAuthor, audnexus) + authorCandidates.add(author) + expect(await authorCandidates.getCandidates()).to.deep.equal([...expected, '']) + })) + }) + }) +}) + +describe('search', () => { + const t = 'title' + const a = 'author' + const u = 'unrecognized' + const r = ['book'] + + const runSearchStub = sinon.stub(bookFinder, 'runSearch') + runSearchStub.resolves([]) + runSearchStub.withArgs(t, a).resolves(r) + runSearchStub.withArgs(t, u).resolves(r) + + const audnexusStub = sinon.stub(bookFinder.audnexus, 'authorASINsRequest') + audnexusStub.resolves([{ name: a }]) + + beforeEach(() => { + bookFinder.runSearch.resetHistory() + }) + + describe('search title is empty', () => { + it('returns empty result', async () => { + expect(await bookFinder.search('', '', a)).to.deep.equal([]) + sinon.assert.callCount(bookFinder.runSearch, 0) + }) + }) + + describe('search title is a recognized title and search author is a recognized author', () => { + it('returns non-empty result (no fuzzy searches)', async () => { + expect(await bookFinder.search('', t, a)).to.deep.equal(r) + sinon.assert.callCount(bookFinder.runSearch, 1) + }) + }) + + describe('search title contains recognized title and search author is a recognized author', () => { + [ + [`${t} -`], + [`${t} - ${a}`], + [`${a} - ${t}`], + [`${t}- ${a}`], + [`${t} -${a}`], + [`${t} ${a}`], + [`${a} - ${t} (unabridged)`], + [`${a} - ${t} (subtitle) - mp3`], + [`${t} {narrator} - series-01 64kbps 10:00:00`], + [`${a} - ${t} (2006) narrated by narrator [unabridged]`], + [`${t} - ${a} 2022 mp3`], + [`01 ${t}`], + [`2022_${t}_HQ`], + ].forEach(([searchTitle]) => { + it(`search('${searchTitle}', '${a}') returns non-empty result (with 1 fuzzy search)`, async () => { + expect(await bookFinder.search('', searchTitle, a)).to.deep.equal(r) + sinon.assert.callCount(bookFinder.runSearch, 2) + }) + }); + + [ + [`s-01 - ${t} (narrator) 64kbps 10:00:00`], + [`${a} - series 01 - ${t}`], + ].forEach(([searchTitle]) => { + it(`search('${searchTitle}', '${a}') returns non-empty result (with 2 fuzzy searches)`, async () => { + expect(await bookFinder.search('', searchTitle, a)).to.deep.equal(r) + sinon.assert.callCount(bookFinder.runSearch, 3) + }) + }); + + [ + [`${t}-${a}`], + [`${t} junk`], + ].forEach(([searchTitle]) => { + it(`search('${searchTitle}', '${a}') returns an empty result`, async () => { + expect(await bookFinder.search('', searchTitle, a)).to.deep.equal([]) + }) + }) + + describe('maxFuzzySearches = 0', () => { + [ + [`${t} - ${a}`], + ].forEach(([searchTitle]) => { + it(`search('${searchTitle}', '${a}') returns an empty result (with no fuzzy searches)`, async () => { + expect(await bookFinder.search('', searchTitle, a, null, null, { maxFuzzySearches: 0 })).to.deep.equal([]) + sinon.assert.callCount(bookFinder.runSearch, 1) + }) + }) + }) + + describe('maxFuzzySearches = 1', () => { [ [`s-01 - ${t} (narrator) 64kbps 10:00:00`], [`${a} - series 01 - ${t}`], ].forEach(([searchTitle]) => { - it(`search('${searchTitle}', '${a}') returns non-empty result (with 2 fuzzy searches)`, async () => { - expect(await bookFinder.search('', searchTitle, a)).to.deep.equal(r); - sinon.assert.callCount(bookFinder.runSearch, 3); - }); - }); + it(`search('${searchTitle}', '${a}') returns an empty result (1 fuzzy search)`, async () => { + expect(await bookFinder.search('', searchTitle, a, null, null, { maxFuzzySearches: 1 })).to.deep.equal([]) + sinon.assert.callCount(bookFinder.runSearch, 2) + }) + }) + }) + }) - [ - [`${t}-${a}`], - [`${t} junk`], - ].forEach(([searchTitle]) => { - it(`search('${searchTitle}', '${a}') returns an empty result`, async () => { - expect(await bookFinder.search('', searchTitle, a)).to.deep.equal([]); - }); - }); - - describe('maxFuzzySearches = 0', () => { - [ - [`${t} - ${a}`], - ].forEach(([searchTitle]) => { - it(`search('${searchTitle}', '${a}') returns an empty result (with no fuzzy searches)`, async () => { - expect(await bookFinder.search('', searchTitle, a, null, null, { maxFuzzySearches: 0 })).to.deep.equal([]); - sinon.assert.callCount(bookFinder.runSearch, 1); - }); - }); - }); - - describe('maxFuzzySearches = 1', () => { - [ - [`s-01 - ${t} (narrator) 64kbps 10:00:00`], - [`${a} - series 01 - ${t}`], - ].forEach(([searchTitle]) => { - it(`search('${searchTitle}', '${a}') returns an empty result (1 fuzzy search)`, async () => { - expect(await bookFinder.search('', searchTitle, a, null, null, { maxFuzzySearches: 1 })).to.deep.equal([]); - sinon.assert.callCount(bookFinder.runSearch, 2); - }); - }); - }); + describe('search title contains recognized title and search author is empty', () => { + [ + [`${t} - ${a}`], + [`${a} - ${t}`], + ].forEach(([searchTitle]) => { + it(`search('${searchTitle}', '') returns a non-empty result (1 fuzzy search)`, async () => { + expect(await bookFinder.search('', searchTitle, '')).to.deep.equal(r) + sinon.assert.callCount(bookFinder.runSearch, 2) + }) }); - describe('search title contains recognized title and search author is empty', () => { - [ - [`${t} - ${a}`], - [`${a} - ${t}`], - ].forEach(([searchTitle]) => { - it(`search('${searchTitle}', '') returns a non-empty result (1 fuzzy search)`, async () => { - expect(await bookFinder.search('', searchTitle, '')).to.deep.equal(r); - sinon.assert.callCount(bookFinder.runSearch, 2); - }); - }); + [ + [`${t}`], + [`${t} - ${u}`], + [`${u} - ${t}`] + ].forEach(([searchTitle]) => { + it(`search('${searchTitle}', '') returns an empty result`, async () => { + expect(await bookFinder.search('', searchTitle, '')).to.deep.equal([]) + }) + }) + }) - [ - [`${t}`], - [`${t} - ${u}`], - [`${u} - ${t}`] - ].forEach(([searchTitle]) => { - it(`search('${searchTitle}', '') returns an empty result`, async () => { - expect(await bookFinder.search('', searchTitle, '')).to.deep.equal([]); - }); - }); + describe('search title contains recognized title and search author is an unrecognized author', () => { + [ + [`${t} - ${u}`], + [`${u} - ${t}`] + ].forEach(([searchTitle]) => { + it(`search('${searchTitle}', '${u}') returns a non-empty result (1 fuzzy search)`, async () => { + expect(await bookFinder.search('', searchTitle, u)).to.deep.equal(r) + sinon.assert.callCount(bookFinder.runSearch, 2) + }) }); - describe('search title contains recognized title and search author is an unrecognized author', () => { - [ - [`${t} - ${u}`], - [`${u} - ${t}`] - ].forEach(([searchTitle]) => { - it(`search('${searchTitle}', '${u}') returns a non-empty result (1 fuzzy search)`, async () => { - expect(await bookFinder.search('', searchTitle, u)).to.deep.equal(r); - sinon.assert.callCount(bookFinder.runSearch, 2); - }); - }); - - [ - [`${t}`] - ].forEach(([searchTitle]) => { - it(`search('${searchTitle}', '${u}') returns a non-empty result (no fuzzy search)`, async () => { - expect(await bookFinder.search('', searchTitle, u)).to.deep.equal(r); - sinon.assert.callCount(bookFinder.runSearch, 1); - }); - }); - }); - }); + [ + [`${t}`] + ].forEach(([searchTitle]) => { + it(`search('${searchTitle}', '${u}') returns a non-empty result (no fuzzy search)`, async () => { + expect(await bookFinder.search('', searchTitle, u)).to.deep.equal(r) + sinon.assert.callCount(bookFinder.runSearch, 1) + }) + }) + }) +})