Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions __TEST__/hyperaudio-lite.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,22 @@ test("createWordArray", () => {
expect(ht.createWordArray(words)).toStrictEqual(expectedResult);
});

test("createWordArray ignores malformed data-m values", () => {
const container = document.createElement("p");
container.innerHTML =
'<span data-m="100">valid</span>' +
'<span class="read active" data-m="">invalid</span>' +
'<span data-m="300">valid</span>';

const result = ht.createWordArray(container.querySelectorAll("[data-m]"));
const invalidWord = container.children[1];

expect(result.map(({ m }) => m)).toEqual([100, 300]);
expect(invalidWord.classList.contains("unread")).toBe(true);
expect(invalidWord.classList.contains("read")).toBe(false);
expect(invalidWord.classList.contains("active")).toBe(false);
});

test("getSelectionMediaFragment", () => {
document
.getSelection()
Expand Down Expand Up @@ -414,6 +430,25 @@ test("setPlayHead updates currentTime and plays if playOnClick is true", () => {
expect(ht.myPlayer.play).toHaveBeenCalled();
});

test("setPlayHead ignores targets without a numeric data-m", () => {
ht.playOnClick = true;
ht.highlightedText = true;
ht.myPlayer = { setTime: jest.fn(), play: jest.fn(), paused: true };
const updateSpy = jest.spyOn(ht, "updateTranscriptVisualState");
const activeWord = document.querySelector('span[data-m="3950"]');
activeWord.classList.add("active");

ht.setPlayHead({ target: activeWord.parentNode });

expect(updateSpy).not.toHaveBeenCalled();
expect(ht.myPlayer.setTime).not.toHaveBeenCalled();
expect(ht.myPlayer.play).not.toHaveBeenCalled();
expect(ht.highlightedText).toBe(true);
expect(activeWord.classList.contains("active")).toBe(true);

updateSpy.mockRestore();
});

test("preparePlayHead sets paused to false and calls checkPlayHead", () => {
ht.checkPlayHead = jest.fn();
ht.preparePlayHead();
Expand Down
48 changes: 34 additions & 14 deletions js/hyperaudio-lite.js
Original file line number Diff line number Diff line change
Expand Up @@ -631,22 +631,35 @@ class HyperaudioLite {

// Create an array of words with metadata from the transcript
createWordArray(words) {
return Array.from(words).map(word => {
const wordArr = [];

Array.from(words).forEach(word => {
const m = parseInt(word.getAttribute('data-m'));

// Reset to a clean baseline — stale read/active classes from a previous
// instance on the same DOM would survive the delta updates in
// updateTranscriptVisualState (#251).
word.classList.remove('read', 'active');
word.classList.add('unread');

// A malformed timestamp breaks every binary-search comparison that
// probes it. Keep the DOM node inert instead of poisoning playback.
if (Number.isNaN(m)) {
return;
}

let p = word.parentNode;
while (p !== document) {
if (['p', 'figure', 'ul'].includes(p.tagName.toLowerCase())) {
break;
}
p = p.parentNode;
}
// Reset to a clean baseline — stale read/active classes from a previous
// instance on the same DOM would survive the delta updates in
// updateTranscriptVisualState (#251).
word.classList.remove('read', 'active');
word.classList.add('unread');
return { n: word, m, p };

wordArr.push({ n: word, m, p });
});

return wordArr;
}

getSelectionRange = () => {
Expand Down Expand Up @@ -721,10 +734,19 @@ class HyperaudioLite {
}

const target = e.target || e.srcElement;
const timeSecs = target && target.dataset
? parseInt(target.dataset.m) / 1000
: NaN;

// Clicks on transcript whitespace or malformed timed nodes must not update
// the visual-state binary search with NaN.
if (Number.isNaN(timeSecs)) {
return;
}

this.highlightedText = false;
this.clearActiveClasses();

const timeSecs = parseInt(target.dataset.m) / 1000;
this.updateTranscriptVisualState(timeSecs);

// Mark the clicked word as active AFTER updateTranscriptVisualState (which
Expand All @@ -736,12 +758,10 @@ class HyperaudioLite {
target.parentNode.classList.add('active');
}

if (!isNaN(timeSecs)) {
this.end = null;
this.myPlayer.setTime(timeSecs);
if (this.playOnClick) {
this.myPlayer.play();
}
this.end = null;
this.myPlayer.setTime(timeSecs);
if (this.playOnClick) {
this.myPlayer.play();
}
}

Expand Down
48 changes: 34 additions & 14 deletions js/hyperaudio-lite.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -631,22 +631,35 @@ class HyperaudioLite {

// Create an array of words with metadata from the transcript
createWordArray(words) {
return Array.from(words).map(word => {
const wordArr = [];

Array.from(words).forEach(word => {
const m = parseInt(word.getAttribute('data-m'));

// Reset to a clean baseline — stale read/active classes from a previous
// instance on the same DOM would survive the delta updates in
// updateTranscriptVisualState (#251).
word.classList.remove('read', 'active');
word.classList.add('unread');

// A malformed timestamp breaks every binary-search comparison that
// probes it. Keep the DOM node inert instead of poisoning playback.
if (Number.isNaN(m)) {
return;
}

let p = word.parentNode;
while (p !== document) {
if (['p', 'figure', 'ul'].includes(p.tagName.toLowerCase())) {
break;
}
p = p.parentNode;
}
// Reset to a clean baseline — stale read/active classes from a previous
// instance on the same DOM would survive the delta updates in
// updateTranscriptVisualState (#251).
word.classList.remove('read', 'active');
word.classList.add('unread');
return { n: word, m, p };

wordArr.push({ n: word, m, p });
});

return wordArr;
}

getSelectionRange = () => {
Expand Down Expand Up @@ -721,10 +734,19 @@ class HyperaudioLite {
}

const target = e.target || e.srcElement;
const timeSecs = target && target.dataset
? parseInt(target.dataset.m) / 1000
: NaN;

// Clicks on transcript whitespace or malformed timed nodes must not update
// the visual-state binary search with NaN.
if (Number.isNaN(timeSecs)) {
return;
}

this.highlightedText = false;
this.clearActiveClasses();

const timeSecs = parseInt(target.dataset.m) / 1000;
this.updateTranscriptVisualState(timeSecs);

// Mark the clicked word as active AFTER updateTranscriptVisualState (which
Expand All @@ -736,12 +758,10 @@ class HyperaudioLite {
target.parentNode.classList.add('active');
}

if (!isNaN(timeSecs)) {
this.end = null;
this.myPlayer.setTime(timeSecs);
if (this.playOnClick) {
this.myPlayer.play();
}
this.end = null;
this.myPlayer.setTime(timeSecs);
if (this.playOnClick) {
this.myPlayer.play();
}
}

Expand Down
Loading