diff --git a/shared/src/publication/AltIdentifier.ts b/shared/src/publication/AltIdentifier.ts new file mode 100644 index 00000000..83acc5db --- /dev/null +++ b/shared/src/publication/AltIdentifier.ts @@ -0,0 +1,58 @@ +/* Copyright 2025 Readium Foundation. All rights reserved. + * Use of this source code is governed by a BSD-style license, + * available in the LICENSE file present in the Github repository of the project. + */ + +/** + * Represents an alternate identifier for a publication. + * https://readium.org/webpub-manifest/schema/altIdentifier.schema.json + */ +export class AltIdentifier { + /** The value of the alternate identifier. */ + public readonly value: string; + + /** The scheme of the alternate identifier (URI format). */ + public readonly scheme?: string; + + /** Creates an AltIdentifier object */ + constructor(values: { + value: string; + scheme?: string; + }) { + this.value = values.value; + this.scheme = values.scheme; + } + + /** + * Parses an AltIdentifier from its RWPM JSON representation. + */ + public static deserialize(json: string | any): AltIdentifier | undefined { + if (!json) return; + + if (typeof json === 'string') { + return new AltIdentifier({ value: json }); + } + + if (typeof json === 'object' && json.value) { + return new AltIdentifier({ + value: json.value, + scheme: json.scheme + }); + } + + return undefined; + } + + /** + * Serializes an AltIdentifier to its RWPM JSON representation. + */ + public serialize(): string | any { + if (this.scheme) { + return { + value: this.value, + scheme: this.scheme + }; + } + return this.value; + } +} diff --git a/shared/src/publication/Contributor.ts b/shared/src/publication/Contributor.ts index 9596a8dc..041c131b 100644 --- a/shared/src/publication/Contributor.ts +++ b/shared/src/publication/Contributor.ts @@ -10,6 +10,7 @@ import { setToArray, } from '../util/JSONParse'; import { LocalizedString } from './LocalizedString'; +import { AltIdentifier } from './AltIdentifier'; /** * Contributor Object for the Readium Web Publication Manifest. @@ -25,6 +26,9 @@ export class Contributor { /** An unambiguous reference to this contributor. */ public readonly identifier?: string; + /** Alternate identifiers for this contributor. */ + public readonly altIdentifiers?: Set; + /** The role of the contributor in the publication making. */ public readonly roles?: Set; @@ -41,6 +45,7 @@ export class Contributor { name: LocalizedString; sortAs?: string; identifier?: string; + altIdentifiers?: Set; roles?: Set; links?: Links; position?: number; @@ -48,6 +53,7 @@ export class Contributor { this.name = values.name; this.sortAs = values.sortAs; this.identifier = values.identifier; + this.altIdentifiers = values.altIdentifiers; this.roles = values.roles; this.links = values.links; this.position = values.position; @@ -70,6 +76,14 @@ export class Contributor { name: LocalizedString.deserialize(json.name) as LocalizedString, sortAs: json.sortAs, identifier: json.identifier, + altIdentifiers: json.altIdentifier + ? (json.altIdentifier instanceof Array + ? new Set(json.altIdentifier + .map((x: string | { value: string; scheme?: string }) => AltIdentifier.deserialize(x)) + .filter((x: AltIdentifier | undefined): x is AltIdentifier => x !== undefined)) + : new Set([AltIdentifier.deserialize(json.altIdentifier as string | { value: string; scheme?: string })] + .filter((x: AltIdentifier | undefined): x is AltIdentifier => x !== undefined))) + : undefined, roles: json.role ? new Set(arrayfromJSONorString(json.role)) : undefined, @@ -86,6 +100,7 @@ export class Contributor { const json: any = { name: this.name.serialize() }; if (this.sortAs !== undefined) json.sortAs = this.sortAs; if (this.identifier !== undefined) json.identifier = this.identifier; + if (this.altIdentifiers) json.altIdentifier = setToArray(this.altIdentifiers).map(altId => altId.serialize()); if (this.roles) json.role = setToArray(this.roles); if (this.links) json.links = this.links.serialize(); if (this.position !== undefined) json.position = this.position; diff --git a/shared/src/publication/Link.ts b/shared/src/publication/Link.ts index 22eb4a98..79230e9f 100644 --- a/shared/src/publication/Link.ts +++ b/shared/src/publication/Link.ts @@ -42,6 +42,9 @@ export class Link { /** Width of the linked resource in pixels. */ public readonly width?: number; + /** Size of the linked resource in bytes. */ + public readonly size?: number; + /** Length of the linked resource in seconds. */ public readonly duration?: number; @@ -69,6 +72,7 @@ export class Link { properties?: Properties; height?: number; width?: number; + size?: number; duration?: number; bitrate?: number; languages?: Array; @@ -83,6 +87,7 @@ export class Link { this.properties = values.properties; this.height = values.height; this.width = values.width; + this.size = values.size; this.duration = values.duration; this.bitrate = values.bitrate; this.languages = values.languages; @@ -109,6 +114,7 @@ export class Link { properties: Properties.deserialize(json.properties), height: positiveNumberfromJSON(json.height), width: positiveNumberfromJSON(json.width), + size: positiveNumberfromJSON(json.size), duration: positiveNumberfromJSON(json.duration), bitrate: positiveNumberfromJSON(json.bitrate), languages: arrayfromJSONorString(json.language), @@ -129,6 +135,7 @@ export class Link { if (this.properties) json.properties = this.properties.serialize(); if (this.height !== undefined) json.height = this.height; if (this.width !== undefined) json.width = this.width; + if (this.size !== undefined) json.size = this.size; if (this.duration !== undefined) json.duration = this.duration; if (this.bitrate !== undefined) json.bitrate = this.bitrate; if (this.languages) json.language = this.languages; diff --git a/shared/src/publication/Metadata.ts b/shared/src/publication/Metadata.ts index 5e5c76d1..898c314d 100644 --- a/shared/src/publication/Metadata.ts +++ b/shared/src/publication/Metadata.ts @@ -8,11 +8,13 @@ import { datefromJSON, positiveNumberfromJSON, } from '../util/JSONParse'; +import { AltIdentifier } from './AltIdentifier'; import { BelongsTo } from './BelongsTo'; import { Contributors } from './Contributor'; import { LocalizedString } from './LocalizedString'; import { ReadingProgression } from './ReadingProgression'; import { Subjects } from './Subject'; +import { TDM } from './TDM'; /** * https://readium.org/webpub-manifest/schema/metadata.schema.json @@ -26,6 +28,7 @@ export class Metadata { public title: LocalizedString; public typeUri?: string; public identifier?: string; + public altIdentifier?: AltIdentifier; public subtitle?: LocalizedString; public sortAs?: LocalizedString; public artists?: Contributors; @@ -52,6 +55,7 @@ export class Metadata { public readingProgression?: ReadingProgression; public duration?: number; public numberOfPages?: number; + public tdm?: TDM; public otherMetadata?: { [key: string]: any }; /**All metadata not in otherMetadata */ @@ -59,6 +63,7 @@ export class Metadata { 'title', '@type', 'identifier', + 'altIdentifier', 'subtitle', 'sortAs', 'artist', @@ -83,6 +88,7 @@ export class Metadata { 'readingProgression', 'duration', 'numberOfPages', + 'tdm' ]; /** Creates [Metadata] object */ @@ -90,6 +96,7 @@ export class Metadata { title: LocalizedString; typeUri?: string; identifier?: string; + altIdentifier?: AltIdentifier; subtitle?: LocalizedString; sortAs?: LocalizedString; artists?: Contributors; @@ -116,12 +123,14 @@ export class Metadata { readingProgression?: ReadingProgression; duration?: number; numberOfPages?: number; + tdm?: TDM; otherMetadata?: { [key: string]: any }; }) { //title always required this.title = values.title as LocalizedString; this.typeUri = values.typeUri; this.identifier = values.identifier; + this.altIdentifier = values.altIdentifier; this.subtitle = values.subtitle; this.sortAs = values.sortAs; this.artists = values.artists; @@ -166,6 +175,7 @@ export class Metadata { this.readingProgression = values.readingProgression; this.duration = values.duration; this.numberOfPages = values.numberOfPages; + this.tdm = values.tdm; this.otherMetadata = values.otherMetadata; } @@ -180,6 +190,7 @@ export class Metadata { const title = LocalizedString.deserialize(json.title) as LocalizedString; const typeUri = json['@type']; const identifier = json.identifier; + const altIdentifier = AltIdentifier.deserialize(json.altIdentifier); const subtitle = LocalizedString.deserialize(json.subtitle); const sortAs = LocalizedString.deserialize(json.sortAs); const artists = Contributors.deserialize(json.artist); @@ -204,6 +215,7 @@ export class Metadata { const readingProgression = json.readingProgression; const duration = positiveNumberfromJSON(json.duration); const numberOfPages = positiveNumberfromJSON(json.numberOfPages); + const tdm = TDM.deserialize(json.tdm); let otherMetadata = Object.assign({}, json); Metadata.mappedProperties.forEach(x => delete otherMetadata[x]); @@ -215,6 +227,7 @@ export class Metadata { title, typeUri, identifier, + altIdentifier, subtitle, sortAs, artists, @@ -239,7 +252,8 @@ export class Metadata { readingProgression, duration, numberOfPages, - otherMetadata, + tdm, + otherMetadata }); } @@ -250,6 +264,7 @@ export class Metadata { const json: any = { title: this.title.serialize() }; if (this.typeUri !== undefined) json['@type'] = this.typeUri; if (this.identifier !== undefined) json.identifier = this.identifier; + if (this.altIdentifier) json.altIdentifier = this.altIdentifier.serialize(); if (this.subtitle) json.subtitle = this.subtitle.serialize(); if (this.sortAs) json.sortAs = this.sortAs.serialize(); if (this.editors) json.editor = this.editors.serialize(); @@ -278,6 +293,7 @@ export class Metadata { if (this.duration !== undefined) json.duration = this.duration; if (this.numberOfPages !== undefined) json.numberOfPages = this.numberOfPages; + if (this.tdm) json.tdm = this.tdm.serialize(); if (this.otherMetadata) { const metadata = this.otherMetadata; diff --git a/shared/src/publication/TDM.ts b/shared/src/publication/TDM.ts new file mode 100644 index 00000000..c978ed50 --- /dev/null +++ b/shared/src/publication/TDM.ts @@ -0,0 +1,56 @@ +/* Copyright 2025 Readium Foundation. All rights reserved. + * Use of this source code is governed by a BSD-style license, + * available in the LICENSE file present in the Github repository of the project. + */ + +/** + * https://readium.org/webpub-manifest/contexts/default/#text-and-data-mining + */ + +export enum TDMReservation { + all = 'all', + none = 'none' +} + +export class TDM { + /** + * Indicates whether the publication allows text and data mining. + */ + public readonly reservation?: TDMReservation; + + /** + * Additional policy information about text and data mining usage. + */ + public readonly policy?: string; + + /** Creates a [TDM] object */ + constructor(values: { + reservation?: TDMReservation; + policy?: string; + }) { + this.reservation = values.reservation; + this.policy = values.policy; + } + + /** + * Parses a [TDM] from its RWPM JSON representation. + */ + public static deserialize(json: any): TDM | undefined { + if (!json) return; + + return new TDM({ + reservation: json.reservation as TDMReservation, + policy: json.policy, + }); + } + + /** + * Serializes a [TDM] to its RWPM JSON representation. + */ + public serialize(): any { + const json: any = {}; + if (this.reservation !== undefined) json.reservation = this.reservation; + if (this.policy !== undefined) json.policy = this.policy; + return json; + } +} diff --git a/shared/src/publication/divina/Properties.ts b/shared/src/publication/divina/Properties.ts new file mode 100644 index 00000000..3815425f --- /dev/null +++ b/shared/src/publication/divina/Properties.ts @@ -0,0 +1,18 @@ +import { Properties } from "../Properties"; + +// Divina extensions for link [Properties]. +// https://github.com/readium/webpub-manifest/blob/master/schema/extensions/divina/properties.schema.json + +declare module '../Properties' { + export interface Properties { + /** + * Specifies that an item in the reading order should break the current continuous scroll + * and start a new one. + */ + getBreakScrollBefore(): boolean; + } +} + +Properties.prototype.getBreakScrollBefore = function(): boolean { + return this.otherProperties['break-scroll-before'] ?? false; +}; \ No newline at end of file diff --git a/shared/src/publication/divina/index.ts b/shared/src/publication/divina/index.ts new file mode 100644 index 00000000..ef224bea --- /dev/null +++ b/shared/src/publication/divina/index.ts @@ -0,0 +1 @@ +export * from './Properties'; \ No newline at end of file diff --git a/shared/src/publication/epub/MediaOverlay.ts b/shared/src/publication/epub/MediaOverlay.ts new file mode 100644 index 00000000..6d533151 --- /dev/null +++ b/shared/src/publication/epub/MediaOverlay.ts @@ -0,0 +1,43 @@ +/* Copyright 2025 Readium Foundation. All rights reserved. + * Use of this source code is governed by a BSD-style license, + * available in the LICENSE file present in the Github repository of the project. + */ + +export class MediaOverlay { + /** Author-defined CSS class name to apply to the currently-playing EPUB Content Document element. */ + public activeClass?: string; + + /** Author-defined CSS class name to apply to the EPUB Content Document's document element when playback is active. */ + public playbackActiveClass?: string; + + /** Creates a MediaOverlay object */ + constructor(values: { + activeClass?: string; + playbackActiveClass?: string; + }) { + this.activeClass = values.activeClass; + this.playbackActiveClass = values.playbackActiveClass; + } + + /** + * Parses a MediaOverlay from its RWPM JSON representation. + */ + public static deserialize(json: any): MediaOverlay | undefined { + if (!json) return; + + return new MediaOverlay({ + activeClass: json.activeClass, + playbackActiveClass: json.playbackActiveClass, + }); + } + + /** + * Serializes a MediaOverlay to its RWPM JSON representation. + */ + public serialize(): any { + const json: any = {}; + if (this.activeClass) json.activeClass = this.activeClass; + if (this.playbackActiveClass) json.playbackActiveClass = this.playbackActiveClass; + return json; + } +} diff --git a/shared/src/publication/epub/Metadata.ts b/shared/src/publication/epub/Metadata.ts new file mode 100644 index 00000000..bb86fd23 --- /dev/null +++ b/shared/src/publication/epub/Metadata.ts @@ -0,0 +1,16 @@ +import { Metadata } from "../Metadata"; +import { MediaOverlay } from "./MediaOverlay"; + +declare module '../Metadata' { + export interface Metadata { + getMediaOverlay(): MediaOverlay | undefined; + } +} + +Metadata.prototype.getMediaOverlay = function(): MediaOverlay | undefined { + const mediaOverlay = this.otherMetadata?.['mediaOverlay']; + + if (!mediaOverlay) return; + + return MediaOverlay.deserialize(mediaOverlay); +}; \ No newline at end of file diff --git a/shared/src/publication/epub/index.ts b/shared/src/publication/epub/index.ts index 2251a77e..80754e7f 100644 --- a/shared/src/publication/epub/index.ts +++ b/shared/src/publication/epub/index.ts @@ -1,4 +1,6 @@ export * from './EPUBLayout'; +export * from './MediaOverlay'; +export * from './Metadata'; export * from './Presentation'; export * from './Properties'; export * from './Publication'; diff --git a/shared/src/publication/index.ts b/shared/src/publication/index.ts index f0fdc66d..9bfe0369 100644 --- a/shared/src/publication/index.ts +++ b/shared/src/publication/index.ts @@ -1,9 +1,11 @@ +export * from './divina'; export * from './encryption'; export * from './epub'; export * from './html'; export * from './opds'; export * from './presentation'; export * from './services'; +export * from './AltIdentifier'; export * from './BelongsTo'; export * from './Contributor'; export * from './Link'; @@ -17,4 +19,5 @@ export * from './Publication'; export * from './PublicationCollection'; export * from './ReadingProgression'; export * from './Subject'; +export * from './TDM'; export * from './GuidedNavigation'; diff --git a/shared/src/util/JSONParse.ts b/shared/src/util/JSONParse.ts index 1768ccfb..f9868fa4 100644 --- a/shared/src/util/JSONParse.ts +++ b/shared/src/util/JSONParse.ts @@ -23,9 +23,9 @@ export function positiveNumberfromJSON(json: any): number | undefined { return num !== undefined && Math.sign(json) >= 0 ? json : undefined; } -/** Converts a Set of a string to a string Array object */ -export function setToArray(obj: Set): Array { - const list = new Array(); +/** Converts a Set to an Array object */ +export function setToArray(obj: Set): Array { + const list = new Array(); obj.forEach(x => list.push(x)); return list; } diff --git a/shared/test/AltIdentifier.test.ts b/shared/test/AltIdentifier.test.ts new file mode 100644 index 00000000..2864f658 --- /dev/null +++ b/shared/test/AltIdentifier.test.ts @@ -0,0 +1,61 @@ +import { AltIdentifier } from '../src'; + +describe('AltIdentifier', () => { + it('parse JSON string', () => { + const identifier = AltIdentifier.deserialize('author:67890'); + expect(identifier).toBeDefined(); + expect(identifier?.value).toBe('author:67890'); + expect(identifier?.scheme).toBeUndefined(); + }); + + it('parse JSON object', () => { + const identifier = AltIdentifier.deserialize({ + value: 'author:67890', + scheme: 'http://example.com/schemes/author-id' + }); + + expect(identifier).toBeDefined(); + expect(identifier?.value).toBe('author:67890'); + expect(identifier?.scheme).toBe('http://example.com/schemes/author-id'); + }); + + it('parse JSON object without scheme', () => { + const identifier = AltIdentifier.deserialize({ + value: 'author:67890' + }); + + expect(identifier).toBeDefined(); + expect(identifier?.value).toBe('author:67890'); + expect(identifier?.scheme).toBeUndefined(); + }); + + it('parse JSON invalid', () => { + const identifier = AltIdentifier.deserialize({ + scheme: 'http://example.com/schemes/author-id' + }); + + expect(identifier).toBeUndefined(); + }); + + it('serialize string', () => { + const identifier = new AltIdentifier({ + value: 'author:22222' + }); + + const json = identifier.serialize(); + expect(json).toBe('author:22222'); + }); + + it('serialize object', () => { + const identifier = new AltIdentifier({ + value: 'author:22222', + scheme: 'http://example.com/schemes/author-id' + }); + + const json = identifier.serialize(); + expect(json).toEqual({ + value: 'author:22222', + scheme: 'http://example.com/schemes/author-id' + }); + }); +}); diff --git a/shared/test/Contributor.test.ts b/shared/test/Contributor.test.ts index 960dd8be..00d6b3f8 100644 --- a/shared/test/Contributor.test.ts +++ b/shared/test/Contributor.test.ts @@ -1,4 +1,5 @@ import { + AltIdentifier, Contributor, Contributors, Link, @@ -28,6 +29,7 @@ describe('Contributor Tests', () => { Contributor.deserialize({ name: 'Colin Greenwood', identifier: 'colin', + altIdentifier: { scheme: 'http://example.com/author-id', value: 'author-22222' }, sortAs: 'greenwood', role: 'bassist', position: 4, @@ -38,6 +40,11 @@ describe('Contributor Tests', () => { name: new LocalizedString('Colin Greenwood'), sortAs: 'greenwood', identifier: 'colin', + altIdentifiers: new Set([ + new AltIdentifier({ + scheme: 'http://example.com/author-id', + value: 'author-22222', + })]), roles: new Set(['bassist']), position: 4.0, links: new Links([ @@ -166,6 +173,12 @@ describe('Contributor Tests', () => { name: new LocalizedString('Colin Greenwood'), sortAs: 'greenwood', identifier: 'colin', + altIdentifiers: new Set([ + new AltIdentifier({ + scheme: 'http://example.com/author-id', + value: 'author-22222', + }), + ]), roles: new Set(['bassist']), position: 4.0, links: new Links([ @@ -177,6 +190,7 @@ describe('Contributor Tests', () => { name: { undefined: 'Colin Greenwood' }, sortAs: 'greenwood', identifier: 'colin', + altIdentifier: [{ scheme: 'http://example.com/author-id', value: 'author-22222' }], role: ['bassist'], position: 4.0, links: [{ href: 'http://link1' }, { href: 'http://link2' }], diff --git a/shared/test/Link.test.ts b/shared/test/Link.test.ts index 0a5cff99..e426128b 100644 --- a/shared/test/Link.test.ts +++ b/shared/test/Link.test.ts @@ -49,6 +49,7 @@ describe('Link Tests', () => { }, height: 1024, width: 768, + size: 1024, bitrate: 74.2, duration: 45.6, language: 'fr', @@ -65,6 +66,7 @@ describe('Link Tests', () => { properties: new Properties({ orientation: 'landscape' }), height: 1024, width: 768, + size: 1024, bitrate: 74.2, duration: 45.6, languages: ['fr'], @@ -129,6 +131,12 @@ describe('Link Tests', () => { expect(link?.height).toBeUndefined(); }); + it('parse JSON requires positive size', () => { + const link = Link.deserialize({ href: 'a', size: -20 }); + expect(link).toBeDefined(); + expect(link?.size).toBeUndefined(); + }); + it('parse JSON requires positive bitrate', () => { const link = Link.deserialize({ href: 'a', bitrate: -20 }); expect(link).toBeDefined(); @@ -179,6 +187,7 @@ describe('Link Tests', () => { properties: new Properties({ orientation: 'landscape' }), height: 1024, width: 768, + size: 1024, bitrate: 74.2, duration: 45.6, languages: ['fr'], @@ -202,6 +211,7 @@ describe('Link Tests', () => { }, height: 1024, width: 768, + size: 1024, bitrate: 74.2, duration: 45.6, language: ['fr'], diff --git a/shared/test/Metadata.test.ts b/shared/test/Metadata.test.ts index 6b7b4088..3c3dbd16 100644 --- a/shared/test/Metadata.test.ts +++ b/shared/test/Metadata.test.ts @@ -7,6 +7,9 @@ import { ReadingProgression, Subject, Subjects, + TDM, + TDMReservation, + AltIdentifier, } from '../src'; describe('Metadata Tests', () => { @@ -20,6 +23,7 @@ describe('Metadata Tests', () => { expect( Metadata.deserialize({ identifier: '1234', + altIdentifier: { scheme: 'http://example.com/scheme', value: 'test-1234' }, '@type': 'epub', title: { en: 'Title', fr: 'Titre' }, subtitle: { en: 'Subtitle', fr: 'Sous-titre' }, @@ -45,6 +49,10 @@ describe('Metadata Tests', () => { description: 'Description', duration: 4.24, numberOfPages: 240, + tdm: { + reservation: 'all', + policy: 'Some policy text', + }, belongsTo: { collection: 'Collection', series: 'Series', @@ -57,6 +65,10 @@ describe('Metadata Tests', () => { ).toEqual( new Metadata({ identifier: '1234', + altIdentifier: new AltIdentifier({ + scheme: 'http://example.com/scheme', + value: 'test-1234', + }), typeUri: 'epub', title: new LocalizedString({ en: 'Title', @@ -117,6 +129,10 @@ describe('Metadata Tests', () => { description: 'Description', duration: 4.24, numberOfPages: 240, + tdm: new TDM({ + reservation: TDMReservation.all, + policy: 'Some policy text', + }), belongsTo: new BelongsTo({ items: new Map([ [ @@ -202,10 +218,14 @@ describe('Metadata Tests', () => { }); }); - it('parse full JSON', () => { + it('get full JSON', () => { expect( new Metadata({ identifier: '1234', + altIdentifier: new AltIdentifier({ + scheme: 'http://example.com/scheme', + value: 'test-1234', + }), typeUri: 'epub', title: new LocalizedString({ en: 'Title', @@ -291,6 +311,10 @@ describe('Metadata Tests', () => { ], ]), }), + tdm: new TDM({ + reservation: TDMReservation.all, + policy: 'Some policy text', + }), otherMetadata: { 'other-metadata1': 'value', 'other-metadata2': [42], @@ -298,6 +322,7 @@ describe('Metadata Tests', () => { }).serialize() ).toEqual({ identifier: '1234', + altIdentifier: { scheme: 'http://example.com/scheme', value: 'test-1234' }, '@type': 'epub', title: { en: 'Title', fr: 'Titre' }, subtitle: { en: 'Subtitle', fr: 'Sous-titre' }, @@ -331,6 +356,10 @@ describe('Metadata Tests', () => { series: [{ name: { undefined: 'Series' } }], 'schema:Periodical': [{ name: { undefined: 'Periodical' } }], }, + tdm: { + reservation: 'all', + policy: 'Some policy text', + }, 'other-metadata1': 'value', 'other-metadata2': [42], }); diff --git a/shared/test/TDM.test.ts b/shared/test/TDM.test.ts new file mode 100644 index 00000000..ba351a04 --- /dev/null +++ b/shared/test/TDM.test.ts @@ -0,0 +1,41 @@ +import { TDM, TDMReservation } from '../src'; + +describe('TDM Tests', () => { + it('parse minimal JSON', () => { + expect(TDM.deserialize({})).toEqual(new TDM({})); + }); + + it('parse full JSON', () => { + expect( + TDM.deserialize({ + reservation: 'all', + policy: 'Some policy text', + }) + ).toEqual( + new TDM({ + reservation: TDMReservation.all, + policy: 'Some policy text', + }) + ); + }); + + it('parse undefined JSON', () => { + expect(TDM.deserialize(undefined)).toBeUndefined(); + }); + + it('get minimal JSON', () => { + expect(new TDM({}).serialize()).toEqual({}); + }); + + it('get full JSON', () => { + expect( + new TDM({ + reservation: TDMReservation.all, + policy: 'Some policy text', + }).serialize() + ).toEqual({ + reservation: 'all', + policy: 'Some policy text', + }); + }); +}); diff --git a/shared/test/divina/Properties.test.ts b/shared/test/divina/Properties.test.ts new file mode 100644 index 00000000..36cb1e40 --- /dev/null +++ b/shared/test/divina/Properties.test.ts @@ -0,0 +1,24 @@ +import { Properties } from '../../src'; + +describe('Divina Properties Tests', () => { + describe('getBreakScrollBefore', () => { + it('returns false when break-scroll-before is not set', () => { + const properties = new Properties({}); + expect(properties.getBreakScrollBefore()).toBe(false); + }); + + it('returns true when break-scroll-before is true', () => { + const properties = new Properties({ + 'break-scroll-before': true + }); + expect(properties.getBreakScrollBefore()).toBe(true); + }); + + it('returns false when break-scroll-before is false', () => { + const properties = new Properties({ + 'break-scroll-before': false + }); + expect(properties.getBreakScrollBefore()).toBe(false); + }); + }); +}); diff --git a/shared/test/epub/MediaOverlay.test.ts b/shared/test/epub/MediaOverlay.test.ts new file mode 100644 index 00000000..4cd4b638 --- /dev/null +++ b/shared/test/epub/MediaOverlay.test.ts @@ -0,0 +1,59 @@ +import { MediaOverlay } from '../../src'; + +describe('MediaOverlay', () => { + it('parse JSON', () => { + const mediaOverlay = MediaOverlay.deserialize({ + activeClass: 'active', + playbackActiveClass: 'playing', + }); + + expect(mediaOverlay).toBeDefined(); + expect(mediaOverlay?.activeClass).toBe('active'); + expect(mediaOverlay?.playbackActiveClass).toBe('playing'); + }); + + it('parse JSON with undefined values', () => { + const mediaOverlay = MediaOverlay.deserialize({ + activeClass: undefined, + playbackActiveClass: undefined, + }); + + expect(mediaOverlay).toBeDefined(); + expect(mediaOverlay?.activeClass).toBeUndefined(); + expect(mediaOverlay?.playbackActiveClass).toBeUndefined(); + }); + + it('parse JSON with empty values', () => { + const mediaOverlay = MediaOverlay.deserialize({ + activeClass: '', + playbackActiveClass: '', + }); + + expect(mediaOverlay).toBeDefined(); + expect(mediaOverlay?.activeClass).toBe(''); + expect(mediaOverlay?.playbackActiveClass).toBe(''); + }); + + it('serialize', () => { + const mediaOverlay = new MediaOverlay({ + activeClass: 'active', + playbackActiveClass: 'playing', + }); + + const json = mediaOverlay.serialize(); + expect(json).toEqual({ + activeClass: 'active', + playbackActiveClass: 'playing', + }); + }); + + it('serialize with undefined values', () => { + const mediaOverlay = new MediaOverlay({ + activeClass: undefined, + playbackActiveClass: undefined, + }); + + const json = mediaOverlay.serialize(); + expect(json).toEqual({}); + }); +}); diff --git a/shared/test/epub/Metadata.test.ts b/shared/test/epub/Metadata.test.ts new file mode 100644 index 00000000..8d8d7ec8 --- /dev/null +++ b/shared/test/epub/Metadata.test.ts @@ -0,0 +1,30 @@ +import { LocalizedString, MediaOverlay, Metadata } from '../../src'; + +describe('EPUB Metadata Tests', () => { + it('getMediaOverlay when available', () => { + expect( + new Metadata({ + title: new LocalizedString({ default: 'Test' }), + otherMetadata: { + mediaOverlay: { + activeClass: 'active', + playbackActiveClass: 'playing' + } + } + }).getMediaOverlay() + ).toEqual( + new MediaOverlay({ + activeClass: 'active', + playbackActiveClass: 'playing' + }) + ); + }); + + it('getMediaOverlay when missing', () => { + expect( + new Metadata({ + title: new LocalizedString({ default: 'Test' }) + }).getMediaOverlay() + ).toBeUndefined(); + }); +}); \ No newline at end of file