diff --git a/__tests__/models.spec.ts b/__tests__/models.spec.ts index ba0fa2d..5fcb8ec 100644 --- a/__tests__/models.spec.ts +++ b/__tests__/models.spec.ts @@ -156,6 +156,54 @@ describe('audios model', () => { }) }) +/** Register the four built-in handlers, the way each suite below does */ +async function registerAll() { + const [images, videos, audios, sheetmusic] = await Promise.all([ + import('../lib/models/images.ts'), + import('../lib/models/videos.ts'), + import('../lib/models/audios.ts'), + import('../lib/models/sheetmusic.ts'), + ]) + images.registerImageHandler() + videos.registerVideoHandler() + audios.registerAudioHandler() + sheetmusic.registerSheetmusicHandler() +} + +describe('a selection of more than one file', () => { + // Every handler answers for the whole selection, so one file it cannot + // open has to disqualify the lot. Written for all of them at once + // because `every` reads exactly like `some` until something asks. + it.each([ + ['images', 'image/jpeg', 'application/pdf'], + ['videos', 'video/mp4', 'application/pdf'], + ['audios', 'audio/mpeg', 'application/pdf'], + ['sheetmusic', 'application/vnd.recordare.musicxml', 'application/pdf'], + ])('%s refuses a selection it can only partly open', async (id, supported, other) => { + await registerAll() + const handler = handlerById(id) + + expect(handler.enabled([makeFile({ mime: supported })])).toBe(true) + expect(handler.enabled([ + makeFile({ mime: supported }), + makeFile({ mime: other }), + ])).toBe(false) + }) + + it.each([ + ['images', 'image/jpeg', 'image/png'], + ['audios', 'audio/mpeg', 'audio/flac'], + ])('%s takes a selection it can open all of', async (id, first, second) => { + await registerAll() + const handler = handlerById(id) + + expect(handler.enabled([ + makeFile({ mime: first }), + makeFile({ mime: second }), + ])).toBe(true) + }) +}) + describe('sheetmusic model', () => { it.each([ 'application/vnd.recordare.musicxml', diff --git a/__tests__/scope.spec.ts b/__tests__/scope.spec.ts index b199055..e9783da 100644 --- a/__tests__/scope.spec.ts +++ b/__tests__/scope.spec.ts @@ -20,6 +20,31 @@ describe('compareVersions', () => { it('ranks a release above its own prereleases', () => { expect(compareVersions('2.0.0', '2.0.0-beta.1')).toBeGreaterThan(0) + // Both ways round. Only one direction was asserted, and the election + // asks in whichever order the two copies happen to load, so a beta + // could have won a page against a release with nothing to show for it + expect(compareVersions('2.0.0-beta.1', '2.0.0')).toBeLessThan(0) + }) + + it('is the same comparison whichever way it is asked', () => { + // Adding zero, because Math.sign(0) is 0 and negating it is -0, and + // the two are not the same value to a strict comparison + const sign = (value: number) => Math.sign(value) + 0 + const versions = ['1.9.9', '2.0.0-1', '2.0.0-alpha', '2.0.0-beta.2', '2.0.0-beta.10', '2.0.0', '2.0.1'] + for (const a of versions) { + for (const b of versions) { + expect(sign(compareVersions(a, b)), `${a} against ${b}`) + .toBe(-sign(compareVersions(b, a)) + 0) + } + } + }) + + it('orders a full run of versions the way semver does', () => { + // Sorting the list is the question the election actually asks + const ordered = ['1.9.9', '2.0.0-1', '2.0.0-alpha', '2.0.0-beta.2', '2.0.0-beta.10', '2.0.0', '2.0.1'] + const shuffled = ['2.0.0', '2.0.0-beta.10', '1.9.9', '2.0.1', '2.0.0-alpha', '2.0.0-1', '2.0.0-beta.2'] + + expect([...shuffled].sort(compareVersions)).toEqual(ordered) }) it('compares prerelease identifiers the way semver does', () => { diff --git a/stryker.config.json b/stryker.config.json index c0b700f..d3c5468 100644 --- a/stryker.config.json +++ b/stryker.config.json @@ -1,6 +1,6 @@ { "$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json", - "_comment": "Audits the tests rather than the code: every mutant that survives is a change to the library that no test noticed. Scoped to the pure logic, where it is fast and where a surviving mutant almost always means a real gap rather than an equivalent one.", + "_comment": "Audits the tests rather than the code: a mutant that survives is a change no test noticed. Scoped to the logic, where a survivor usually means a real gap. Components and views are left out: most of their mutants are DOM glue that a unit test cannot observe, so the score would measure the wrong thing.", "packageManager": "npm", "testRunner": "vitest", "inPlace": true, @@ -17,10 +17,15 @@ }, "coverageAnalysis": "perTest", "mutate": [ + "lib/*.ts", "lib/utils/**/*.ts", "lib/composables/**/*.ts", + "lib/models/**/*.ts", + "lib/services/**/*.ts", + "lib/helpers/**/*.ts", "!lib/**/*.d.ts", - "!lib/utils/plyrTranslations.ts" + "!lib/utils/plyrTranslations.ts", + "!lib/services/logger.ts" ], "thresholds": { "high": 90,