From 6cfe4091fa3178296ad8f753dcd1de9d7786cc5c Mon Sep 17 00:00:00 2001 From: Jiminy Panoz Date: Fri, 11 Sep 2026 09:37:19 +0200 Subject: [PATCH 1/7] Silence accessibility warnings in test output --- shared/test/accessibility/Localization.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shared/test/accessibility/Localization.test.ts b/shared/test/accessibility/Localization.test.ts index f132d56f..f971b9cf 100644 --- a/shared/test/accessibility/Localization.test.ts +++ b/shared/test/accessibility/Localization.test.ts @@ -2,10 +2,17 @@ import { SUPPORTED_LANGUAGES } from '../../src/publication/accessibility/Support import { Localization } from '../../src/publication/accessibility/Localization'; describe('Localization', () => { + let warnSpy: jest.SpyInstance; + // Reset the singleton instance before each test beforeEach(() => { // @ts-ignore - Accessing private member for testing Localization.instance = undefined; + warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + + afterEach(() => { + warnSpy.mockRestore(); }); it('should be a singleton', () => { From 9b8f455efb541e3d5398e58e4def587dc325e1f2 Mon Sep 17 00:00:00 2001 From: Jiminy Panoz Date: Fri, 11 Sep 2026 09:37:47 +0200 Subject: [PATCH 2/7] Correct deserialization of accessibility --- .../accessibility/Accessibility.ts | 42 ++++++++++++------- .../test/accessibility/Accessibility.test.ts | 23 ++++++++++ 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/shared/src/publication/accessibility/Accessibility.ts b/shared/src/publication/accessibility/Accessibility.ts index 1482c088..42647b4e 100644 --- a/shared/src/publication/accessibility/Accessibility.ts +++ b/shared/src/publication/accessibility/Accessibility.ts @@ -3,6 +3,8 @@ * available in the LICENSE file present in the Github repository of the project. */ +import { arrayfromJSON, arrayfromJSONorString } from '../../util/JSONParse.ts'; + /** * Holds the accessibility metadata of a Publication. * @@ -77,7 +79,7 @@ export class Accessibility { if (!json || typeof json !== 'object') return; type AccessibilityJson = { - conformsTo?: string[]; + conformsTo?: string | string[]; certification?: { certifiedBy: string; credential: string; @@ -85,7 +87,7 @@ export class Accessibility { }; summary?: string; accessMode?: string[]; - accessModeSufficient?: string[][]; + accessModeSufficient?: (string | string[])[]; feature?: string[]; hazard?: string[]; exemption?: string[]; @@ -93,33 +95,41 @@ export class Accessibility { const accessibilityJson = json as AccessibilityJson; + // Per the RWPM a11y schema, conformsTo may be a bare string or an array of strings. + const conformsTo = arrayfromJSONorString(accessibilityJson.conformsTo); + const accessMode = arrayfromJSON(accessibilityJson.accessMode); + const accessModeSufficient = arrayfromJSON(accessibilityJson.accessModeSufficient); + const feature = arrayfromJSON(accessibilityJson.feature); + const hazard = arrayfromJSON(accessibilityJson.hazard); + const exemption = arrayfromJSON(accessibilityJson.exemption); + return new Accessibility({ - conformsTo: accessibilityJson.conformsTo - ? accessibilityJson.conformsTo.map(uri => AccessibilityProfile.deserialize(uri)) + conformsTo: conformsTo + ? conformsTo.map(uri => AccessibilityProfile.deserialize(uri)) .filter((profile): profile is AccessibilityProfile => profile !== undefined) : undefined, - certification: accessibilityJson.certification + certification: accessibilityJson.certification ? Certification.deserialize(accessibilityJson.certification) : undefined, summary: accessibilityJson.summary, - accessMode: accessibilityJson.accessMode - ? accessibilityJson.accessMode.map(value => AccessMode.deserialize(value)) + accessMode: accessMode + ? accessMode.map(value => AccessMode.deserialize(value)) .filter((mode): mode is AccessMode => mode !== undefined) : undefined, - accessModeSufficient: accessibilityJson.accessModeSufficient - ? accessibilityJson.accessModeSufficient.map(modes => PrimaryAccessMode.deserialize(modes)) - .filter((mode): mode is PrimaryAccessMode => mode !== undefined) + accessModeSufficient: accessModeSufficient + ? accessModeSufficient.map(modes => PrimaryAccessMode.deserialize(modes)) + .filter((mode): mode is PrimaryAccessMode => mode !== undefined) : undefined, - feature: accessibilityJson.feature - ? accessibilityJson.feature.map(value => Feature.deserialize(value)) + feature: feature + ? feature.map(value => Feature.deserialize(value)) .filter((feature): feature is Feature => feature !== undefined) : undefined, - hazard: accessibilityJson.hazard - ? accessibilityJson.hazard.map(value => Hazard.deserialize(value)) + hazard: hazard + ? hazard.map(value => Hazard.deserialize(value)) .filter((hazard): hazard is Hazard => hazard !== undefined) : undefined, - exemption: accessibilityJson.exemption - ? accessibilityJson.exemption.map(value => Exemption.deserialize(value)) + exemption: exemption + ? exemption.map(value => Exemption.deserialize(value)) .filter((exemption): exemption is Exemption => exemption !== undefined) : undefined }); diff --git a/shared/test/accessibility/Accessibility.test.ts b/shared/test/accessibility/Accessibility.test.ts index e5ec3cf3..16013725 100644 --- a/shared/test/accessibility/Accessibility.test.ts +++ b/shared/test/accessibility/Accessibility.test.ts @@ -256,6 +256,29 @@ describe('Accessibility Tests', () => { expect(Accessibility.deserialize(json2)).toEqual(expected2); }); + it('parse conformsTo serialized as a bare string', () => { + const json = { + conformsTo: 'https://www.w3.org/TR/epub-a11y-11#wcag-2.0-a' + }; + + const expected = new Accessibility({ + conformsTo: [new AccessibilityProfile('https://www.w3.org/TR/epub-a11y-11#wcag-2.0-a')] + }); + + expect(Accessibility.deserialize(json)).toEqual(expected); + }); + + it('does not throw when accessMode/feature/hazard are serialized as a bare string', () => { + const json = { + accessMode: 'textual', + feature: 'alternativeText', + hazard: 'none', + accessModeSufficient: 'textual' + }; + + expect(Accessibility.deserialize(json)).toEqual(new Accessibility()); + }); + it('serialize Profile', () => { const accessibility = new Accessibility({ conformsTo: [ From f0e55c8562ee7c1d20cebeaaa307f4090f4b7432 Mon Sep 17 00:00:00 2001 From: Jiminy Panoz Date: Fri, 11 Sep 2026 09:53:04 +0200 Subject: [PATCH 3/7] Add accessibilityModeSufficient tests --- .../test/accessibility/Accessibility.test.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/shared/test/accessibility/Accessibility.test.ts b/shared/test/accessibility/Accessibility.test.ts index 16013725..682c754f 100644 --- a/shared/test/accessibility/Accessibility.test.ts +++ b/shared/test/accessibility/Accessibility.test.ts @@ -272,13 +272,35 @@ describe('Accessibility Tests', () => { const json = { accessMode: 'textual', feature: 'alternativeText', - hazard: 'none', + hazard: 'none' + }; + + expect(Accessibility.deserialize(json)).toEqual(new Accessibility()); + }); + + it('does not throw when accessModeSufficient itself is serialized as a bare string', () => { + const json = { accessModeSufficient: 'textual' }; expect(Accessibility.deserialize(json)).toEqual(new Accessibility()); }); + it('still parses accessModeSufficient items as string or array per the spec oneOf', () => { + const json = { + accessModeSufficient: ['textual', ['visual', 'auditory']] + }; + + const expected = new Accessibility({ + accessModeSufficient: [ + new PrimaryAccessMode('textual'), + new PrimaryAccessMode(['visual', 'auditory']) + ] + }); + + expect(Accessibility.deserialize(json)).toEqual(expected); + }); + it('serialize Profile', () => { const accessibility = new Accessibility({ conformsTo: [ From de70ebcb2642d7b8fe7ab2884023b3aecd70c246 Mon Sep 17 00:00:00 2001 From: Jiminy Panoz Date: Mon, 14 Sep 2026 12:37:53 +0200 Subject: [PATCH 4/7] Move to updated schema --- .../accessibility/Accessibility.ts | 18 ++++++------- .../test/accessibility/Accessibility.test.ts | 27 ++++++++++++++++--- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/shared/src/publication/accessibility/Accessibility.ts b/shared/src/publication/accessibility/Accessibility.ts index 42647b4e..d7080ece 100644 --- a/shared/src/publication/accessibility/Accessibility.ts +++ b/shared/src/publication/accessibility/Accessibility.ts @@ -86,22 +86,22 @@ export class Accessibility { report: string; }; summary?: string; - accessMode?: string[]; + accessMode?: string | string[]; accessModeSufficient?: (string | string[])[]; - feature?: string[]; - hazard?: string[]; - exemption?: string[]; + feature?: string | string[]; + hazard?: string | string[]; + exemption?: string | string[]; }; const accessibilityJson = json as AccessibilityJson; - // Per the RWPM a11y schema, conformsTo may be a bare string or an array of strings. + // Per the RWPM a11y schema, these fields may be a bare string or an array of strings. const conformsTo = arrayfromJSONorString(accessibilityJson.conformsTo); - const accessMode = arrayfromJSON(accessibilityJson.accessMode); + const accessMode = arrayfromJSONorString(accessibilityJson.accessMode); const accessModeSufficient = arrayfromJSON(accessibilityJson.accessModeSufficient); - const feature = arrayfromJSON(accessibilityJson.feature); - const hazard = arrayfromJSON(accessibilityJson.hazard); - const exemption = arrayfromJSON(accessibilityJson.exemption); + const feature = arrayfromJSONorString(accessibilityJson.feature); + const hazard = arrayfromJSONorString(accessibilityJson.hazard); + const exemption = arrayfromJSONorString(accessibilityJson.exemption); return new Accessibility({ conformsTo: conformsTo diff --git a/shared/test/accessibility/Accessibility.test.ts b/shared/test/accessibility/Accessibility.test.ts index 682c754f..c4163b8c 100644 --- a/shared/test/accessibility/Accessibility.test.ts +++ b/shared/test/accessibility/Accessibility.test.ts @@ -268,14 +268,22 @@ describe('Accessibility Tests', () => { expect(Accessibility.deserialize(json)).toEqual(expected); }); - it('does not throw when accessMode/feature/hazard are serialized as a bare string', () => { + it('parses accessMode/feature/hazard/exemption serialized as a bare string', () => { const json = { accessMode: 'textual', feature: 'alternativeText', - hazard: 'none' + hazard: 'none', + exemption: 'eaa-microenterprise' }; - expect(Accessibility.deserialize(json)).toEqual(new Accessibility()); + const expected = new Accessibility({ + accessMode: [new AccessMode('textual')], + feature: [new Feature('alternativeText')], + hazard: [new Hazard('none')], + exemption: [new Exemption('eaa-microenterprise')] + }); + + expect(Accessibility.deserialize(json)).toEqual(expected); }); it('does not throw when accessModeSufficient itself is serialized as a bare string', () => { @@ -286,6 +294,19 @@ describe('Accessibility Tests', () => { expect(Accessibility.deserialize(json)).toEqual(new Accessibility()); }); + it('does not throw and ignores malformed non-string, non-array values', () => { + const json = { + conformsTo: 42, + accessMode: null, + accessModeSufficient: { foo: 'bar' }, + feature: true, + hazard: 42, + exemption: {} + }; + + expect(Accessibility.deserialize(json)).toEqual(new Accessibility()); + }); + it('still parses accessModeSufficient items as string or array per the spec oneOf', () => { const json = { accessModeSufficient: ['textual', ['visual', 'auditory']] From 09f0596d082bae1b96c022c59e1217578a497a2a Mon Sep 17 00:00:00 2001 From: Jiminy Panoz Date: Mon, 14 Sep 2026 13:12:14 +0200 Subject: [PATCH 5/7] Bump versions --- decorator/CHANGELOG.MD | 6 ++++++ decorator/package.json | 2 +- navigator-html-injectables/CHANGELOG.MD | 6 ++++++ navigator-html-injectables/package.json | 2 +- navigator/CHANGELOG.MD | 6 ++++++ navigator/package.json | 2 +- shared/CHANGELOG.MD | 6 ++++++ shared/package.json | 2 +- 8 files changed, 28 insertions(+), 4 deletions(-) diff --git a/decorator/CHANGELOG.MD b/decorator/CHANGELOG.MD index 0cd0f137..0aa0c48a 100644 --- a/decorator/CHANGELOG.MD +++ b/decorator/CHANGELOG.MD @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.1] – 2026-09-14 + +### Fixed + +- Republished against `@readium/navigator-html-injectables@2.8.1`/`@readium/shared@2.5.1` ([#254](https://github.com/readium/ts-toolkit/pull/254)) + ## [1.2.0] – 2026-09-10 ### Added diff --git a/decorator/package.json b/decorator/package.json index eac5a57e..18e849e3 100644 --- a/decorator/package.json +++ b/decorator/package.json @@ -1,6 +1,6 @@ { "name": "@readium/decorator", - "version": "1.2.0", + "version": "1.2.1", "type": "module", "description": "Standalone decoration controller and renderer for HTML content", "author": "readium", diff --git a/navigator-html-injectables/CHANGELOG.MD b/navigator-html-injectables/CHANGELOG.MD index 6e501eed..5c1470f2 100644 --- a/navigator-html-injectables/CHANGELOG.MD +++ b/navigator-html-injectables/CHANGELOG.MD @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.8.1] – 2026-09-14 + +### Fixed + +- Republished against `@readium/shared@2.5.1` ([#254](https://github.com/readium/ts-toolkit/pull/254)) + ## [2.8.0] – 2026-09-10 ### Added diff --git a/navigator-html-injectables/package.json b/navigator-html-injectables/package.json index 45940e64..9c89e1aa 100644 --- a/navigator-html-injectables/package.json +++ b/navigator-html-injectables/package.json @@ -1,6 +1,6 @@ { "name": "@readium/navigator-html-injectables", - "version": "2.8.0", + "version": "2.8.1", "type": "module", "description": "An embeddable solution for connecting frames of HTML publications with a Readium Navigator", "author": "readium", diff --git a/navigator/CHANGELOG.MD b/navigator/CHANGELOG.MD index d4e07073..fe08b09d 100644 --- a/navigator/CHANGELOG.MD +++ b/navigator/CHANGELOG.MD @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.10.1] – 2026-09-14 + +### Fixed + +- Republished against `@readium/decorator@1.2.1`/`@readium/navigator-html-injectables@2.8.1`/`@readium/shared@2.5.1` ([#254](https://github.com/readium/ts-toolkit/pull/254)) + ## [2.10.0] – 2026-09-10 ### Added diff --git a/navigator/package.json b/navigator/package.json index bb939fc1..45866535 100644 --- a/navigator/package.json +++ b/navigator/package.json @@ -1,6 +1,6 @@ { "name": "@readium/navigator", - "version": "2.10.0", + "version": "2.10.1", "type": "module", "description": "Next generation SDK for publications in Web Apps", "author": "readium", diff --git a/shared/CHANGELOG.MD b/shared/CHANGELOG.MD index fad0483d..4d706833 100644 --- a/shared/CHANGELOG.MD +++ b/shared/CHANGELOG.MD @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.5.1] – 2026-09-14 + +### Fixed + +- `Accessibility.deserialize` no longer throws when `conformsTo`, `accessMode`, `feature`, `hazard`, `exemption`, or `accessModeSufficient` are serialized as a bare string or another non-array value instead of an array ([#254](https://github.com/readium/ts-toolkit/pull/254)) + ## [2.5.0] – 2026-09-08 ### Added diff --git a/shared/package.json b/shared/package.json index de153056..4ca49b9a 100644 --- a/shared/package.json +++ b/shared/package.json @@ -1,6 +1,6 @@ { "name": "@readium/shared", - "version": "2.5.0", + "version": "2.5.1", "type": "module", "description": "Shared models to be used across other Readium projects and implementations in Typescript", "author": "readium", From 1592a4e199e7cfd6271aea882236b0081dffc2d4 Mon Sep 17 00:00:00 2001 From: Jiminy Panoz Date: Tue, 15 Sep 2026 13:50:13 +0200 Subject: [PATCH 6/7] Update date in changelogs --- decorator/CHANGELOG.MD | 2 +- navigator-html-injectables/CHANGELOG.MD | 2 +- navigator/CHANGELOG.MD | 2 +- shared/CHANGELOG.MD | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/decorator/CHANGELOG.MD b/decorator/CHANGELOG.MD index 0aa0c48a..cc71622d 100644 --- a/decorator/CHANGELOG.MD +++ b/decorator/CHANGELOG.MD @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.2.1] – 2026-09-14 +## [1.2.1] – 2026-09-15 ### Fixed diff --git a/navigator-html-injectables/CHANGELOG.MD b/navigator-html-injectables/CHANGELOG.MD index 5c1470f2..afcd2367 100644 --- a/navigator-html-injectables/CHANGELOG.MD +++ b/navigator-html-injectables/CHANGELOG.MD @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2.8.1] – 2026-09-14 +## [2.8.1] – 2026-09-15 ### Fixed diff --git a/navigator/CHANGELOG.MD b/navigator/CHANGELOG.MD index fe08b09d..0ba629e6 100644 --- a/navigator/CHANGELOG.MD +++ b/navigator/CHANGELOG.MD @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2.10.1] – 2026-09-14 +## [2.10.1] – 2026-09-15 ### Fixed diff --git a/shared/CHANGELOG.MD b/shared/CHANGELOG.MD index 4d706833..37a254d2 100644 --- a/shared/CHANGELOG.MD +++ b/shared/CHANGELOG.MD @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2.5.1] – 2026-09-14 +## [2.5.1] – 2026-09-15 ### Fixed From 0b96baa4564d632985a2e529eb982f8c619cccc5 Mon Sep 17 00:00:00 2001 From: Jiminy Panoz Date: Tue, 15 Sep 2026 13:57:57 +0200 Subject: [PATCH 7/7] Correct PrimaryAccessMode deserialize --- .../src/publication/accessibility/Accessibility.ts | 3 +++ shared/test/accessibility/Accessibility.test.ts | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/shared/src/publication/accessibility/Accessibility.ts b/shared/src/publication/accessibility/Accessibility.ts index d7080ece..f1f3890d 100644 --- a/shared/src/publication/accessibility/Accessibility.ts +++ b/shared/src/publication/accessibility/Accessibility.ts @@ -437,6 +437,9 @@ export class PrimaryAccessMode { if (!json) return; if (typeof json === 'string') { + if (!PrimaryAccessMode.VALID_MODES.has(json.toLowerCase())) { + return undefined; + } return new PrimaryAccessMode(json); } diff --git a/shared/test/accessibility/Accessibility.test.ts b/shared/test/accessibility/Accessibility.test.ts index c4163b8c..dc318a56 100644 --- a/shared/test/accessibility/Accessibility.test.ts +++ b/shared/test/accessibility/Accessibility.test.ts @@ -222,6 +222,18 @@ describe('Accessibility Tests', () => { expect(Accessibility.deserialize(json2)).toEqual(expected2); }); + it('parse drops invalid bare-string PrimaryAccessMode entries', () => { + const json = { + accessModeSufficient: ['invalid', 'textual'] + }; + + const expected = new Accessibility({ + accessModeSufficient: [new PrimaryAccessMode('textual')] + }); + + expect(Accessibility.deserialize(json)).toEqual(expected); + }); + it('parse PrimaryAccessMode with multiple arrays', () => { const json = { accessModeSufficient: [['textual', 'visual'], ['auditory']]