@internetarchive/bookreader 5.0.0-70-a3 → 5.0.0-70
Sign up to get free protection for your applications and to get access to all the features.
- package/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/ia-bookreader-bundle.js +293 -586
- package/BookReader/ia-bookreader-bundle.js.map +1 -1
- package/BookReader/plugins/plugin.chapters.js +2 -2
- package/BookReader/plugins/plugin.chapters.js.map +1 -1
- package/BookReader/plugins/plugin.tts.js +1 -1
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/BookReader/plugins/plugin.url.js +1 -1
- package/BookReader/plugins/plugin.url.js.map +1 -1
- package/BookReaderDemo/demo-internetarchive.html +1 -1
- package/CHANGELOG.md +4 -0
- package/package.json +4 -2
- package/src/BookNavigator/assets/icon_sort_asc.js +5 -0
- package/src/BookNavigator/assets/icon_sort_desc.js +5 -0
- package/src/BookNavigator/assets/icon_sort_neutral.js +5 -0
- package/src/BookNavigator/assets/icon_volumes.js +11 -0
- package/src/BookNavigator/book-navigator.js +2 -2
- package/src/BookNavigator/sharing.js +5 -5
- package/src/BookNavigator/volumes/volumes-provider.js +111 -0
- package/src/BookNavigator/volumes/volumes.js +188 -0
- package/src/BookReader.js +4 -5
- package/src/ia-bookreader/ia-bookreader.js +6 -6
- package/src/plugins/tts/AbstractTTSEngine.js +22 -3
- package/src/plugins/url/UrlPlugin.js +5 -7
- package/tests/e2e/models/Navigation.js +1 -1
- package/tests/jest/BookNavigator/book-navigator.test.js +2 -2
- package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +1 -1
- package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +121 -17
- package/tests/jest/BookNavigator/volumes/volumes.test.js +97 -0
- package/tests/jest/plugins/tts/AbstractTTSEngine.test.js +20 -0
- package/tests/jest/plugins/url/UrlPlugin.test.js +8 -0
- package/src/BookNavigator/viewable-files.js +0 -95
@@ -1,10 +1,9 @@
|
|
1
|
-
import { fixtureCleanup, fixtureSync } from '@open-wc/testing-helpers';
|
1
|
+
import { fixture, fixtureCleanup, fixtureSync } from '@open-wc/testing-helpers';
|
2
2
|
import sinon from 'sinon';
|
3
|
-
import
|
3
|
+
import volumesProvider from '@/src/BookNavigator/volumes/volumes-provider';
|
4
4
|
|
5
5
|
const brOptions = {
|
6
6
|
"options": {
|
7
|
-
"subPrefix": 'special-subprefix',
|
8
7
|
"enableMultipleBooks": true,
|
9
8
|
"multipleBooksList": {
|
10
9
|
"by_subprefix": {
|
@@ -40,11 +39,10 @@ afterEach(() => {
|
|
40
39
|
});
|
41
40
|
|
42
41
|
describe('Volumes Provider', () => {
|
43
|
-
test('
|
42
|
+
test('constructor', () => {
|
44
43
|
const onProviderChange = sinon.fake();
|
45
|
-
|
46
44
|
const baseHost = "https://archive.org";
|
47
|
-
const provider = new
|
45
|
+
const provider = new volumesProvider({
|
48
46
|
baseHost,
|
49
47
|
bookreader: brOptions,
|
50
48
|
onProviderChange
|
@@ -57,24 +55,130 @@ describe('Volumes Provider', () => {
|
|
57
55
|
expect(provider.id).toEqual('volumes');
|
58
56
|
expect(provider.icon).toBeDefined();
|
59
57
|
expect(fixtureSync(provider.icon).tagName).toEqual('svg');
|
60
|
-
expect(provider.sortOrderBy).toEqual('default');
|
61
|
-
|
62
58
|
expect(provider.label).toEqual(`Viewable files (${volumeCount})`);
|
63
59
|
expect(provider.viewableFiles).toBeDefined();
|
64
60
|
expect(provider.viewableFiles.length).toEqual(3);
|
65
|
-
expect(provider.volumeCount).toEqual(3);
|
66
61
|
|
67
|
-
expect(provider.component.
|
68
|
-
expect(provider.component.
|
69
|
-
expect(provider.component
|
62
|
+
expect(provider.component.hostUrl).toBeDefined();
|
63
|
+
expect(provider.component.hostUrl).toEqual(baseHost);
|
64
|
+
expect(provider.component).toBeDefined();
|
65
|
+
});
|
66
|
+
|
67
|
+
test('sorting cycles - render sort actionButton', async () => {
|
68
|
+
const onProviderChange = sinon.fake();
|
69
|
+
const baseHost = "https://archive.org";
|
70
|
+
const provider = new volumesProvider({
|
71
|
+
baseHost,
|
72
|
+
bookreader: brOptions,
|
73
|
+
onProviderChange
|
74
|
+
});
|
75
|
+
|
76
|
+
expect(provider.sortOrderBy).toEqual("default");
|
77
|
+
|
78
|
+
provider.sortVolumes("title_asc");
|
79
|
+
expect(provider.sortOrderBy).toEqual("title_asc");
|
80
|
+
expect(fixtureSync(provider.sortButton).outerHTML).toContain("sort-by asc-icon");
|
81
|
+
|
82
|
+
provider.sortVolumes("title_desc");
|
83
|
+
expect(provider.sortOrderBy).toEqual("title_desc");
|
84
|
+
expect(fixtureSync(provider.sortButton).outerHTML).toContain("sort-by desc-icon");
|
85
|
+
|
86
|
+
provider.sortVolumes("default");
|
87
|
+
expect(provider.sortOrderBy).toEqual("default");
|
88
|
+
expect(fixtureSync(provider.sortButton).outerHTML).toContain("sort-by neutral-icon");
|
89
|
+
});
|
90
|
+
|
91
|
+
test('sort volumes in initial order', async () => {
|
92
|
+
const onProviderChange = sinon.fake();
|
93
|
+
const baseHost = "https://archive.org";
|
94
|
+
const provider = new volumesProvider({
|
95
|
+
baseHost,
|
96
|
+
bookreader: brOptions,
|
97
|
+
onProviderChange
|
98
|
+
});
|
99
|
+
|
100
|
+
const parsedFiles = brOptions.options.multipleBooksList.by_subprefix;
|
101
|
+
const files = Object.keys(parsedFiles).map(item => parsedFiles[item]).sort((a, b) => a.orig_sort - b.orig_sort);
|
102
|
+
const origSortTitles = files.map(item => item.title);
|
70
103
|
|
104
|
+
provider.sortVolumes("default");
|
105
|
+
|
106
|
+
expect(provider.sortOrderBy).toEqual("default");
|
71
107
|
expect(provider.actionButton).toBeDefined();
|
72
|
-
expect(provider.actionButton).toEqual(provider.sortFilesComponent);
|
73
|
-
expect(provider.actionButton.fileListRaw).toEqual(provider.viewableFiles);
|
74
108
|
|
75
|
-
const
|
76
|
-
|
109
|
+
const providerFileTitles = provider.viewableFiles.map(item => item.title);
|
110
|
+
// use `.eql` for "lose equality" in order to deeply compare values.
|
111
|
+
expect(providerFileTitles).toEqual([...origSortTitles]);
|
112
|
+
});
|
77
113
|
|
78
|
-
|
114
|
+
test('sort volumes in ascending title order', async () => {
|
115
|
+
const onProviderChange = sinon.fake();
|
116
|
+
const baseHost = "https://archive.org";
|
117
|
+
const provider = new volumesProvider({
|
118
|
+
baseHost,
|
119
|
+
bookreader: brOptions,
|
120
|
+
onProviderChange
|
121
|
+
});
|
122
|
+
|
123
|
+
const parsedFiles = brOptions.options.multipleBooksList.by_subprefix;
|
124
|
+
const files = Object.keys(parsedFiles).map(item => parsedFiles[item]);
|
125
|
+
const ascendingTitles = files.map(item => item.title).sort((a, b) => a.localeCompare(b));
|
126
|
+
|
127
|
+
provider.sortVolumes("title_asc");
|
128
|
+
|
129
|
+
expect(provider.sortOrderBy).toEqual("title_asc");
|
130
|
+
expect(provider.actionButton).toBeDefined();
|
131
|
+
|
132
|
+
const providerFileTitles = provider.viewableFiles.map(item => item.title);
|
133
|
+
// use `.eql` for "lose equality" in order to deeply compare values.
|
134
|
+
expect(providerFileTitles).toEqual([...ascendingTitles]);
|
135
|
+
});
|
136
|
+
|
137
|
+
test('sort volumes in descending title order', async () => {
|
138
|
+
const onProviderChange = sinon.fake();
|
139
|
+
const baseHost = "https://archive.org";
|
140
|
+
const provider = new volumesProvider({
|
141
|
+
baseHost,
|
142
|
+
bookreader: brOptions,
|
143
|
+
onProviderChange
|
144
|
+
});
|
145
|
+
provider.isSortAscending = false;
|
146
|
+
|
147
|
+
const parsedFiles = brOptions.options.multipleBooksList.by_subprefix;
|
148
|
+
const files = Object.keys(parsedFiles).map(item => parsedFiles[item]);
|
149
|
+
const descendingTitles = files.map(item => item.title).sort((a, b) => b.localeCompare(a));
|
150
|
+
|
151
|
+
provider.sortVolumes("title_desc");
|
152
|
+
|
153
|
+
expect(provider.sortOrderBy).toEqual("title_desc");
|
154
|
+
expect(provider.actionButton).toBeDefined();
|
155
|
+
|
156
|
+
const providerFileTitles = provider.viewableFiles.map(item => item.title);
|
157
|
+
// use `.eql` for "lose equality" in order to deeply compare values.
|
158
|
+
expect(providerFileTitles).toEqual([...descendingTitles]);
|
159
|
+
});
|
160
|
+
|
161
|
+
describe('Sorting icons', () => {
|
162
|
+
test('has 3 icons', async () => {
|
163
|
+
const onProviderChange = sinon.fake();
|
164
|
+
const baseHost = "https://archive.org";
|
165
|
+
const provider = new volumesProvider({
|
166
|
+
baseHost,
|
167
|
+
bookreader: brOptions,
|
168
|
+
onProviderChange
|
169
|
+
});
|
170
|
+
provider.sortOrderBy = 'default';
|
171
|
+
|
172
|
+
const origSortButton = await fixture(provider.sortButton);
|
173
|
+
expect(origSortButton.classList.contains('neutral-icon')).toBeTruthy();
|
174
|
+
|
175
|
+
provider.sortOrderBy = 'title_asc';
|
176
|
+
const ascButton = await fixture(provider.sortButton);
|
177
|
+
expect(ascButton.classList.contains('asc-icon')).toBeTruthy();
|
178
|
+
|
179
|
+
provider.sortOrderBy = 'title_desc';
|
180
|
+
const descButton = await fixture(provider.sortButton);
|
181
|
+
expect(descButton.classList.contains('desc-icon')).toBeTruthy();
|
182
|
+
});
|
79
183
|
});
|
80
184
|
});
|
@@ -0,0 +1,97 @@
|
|
1
|
+
import {
|
2
|
+
html,
|
3
|
+
fixture,
|
4
|
+
fixtureCleanup,
|
5
|
+
} from '@open-wc/testing-helpers';
|
6
|
+
import sinon from 'sinon';
|
7
|
+
import '@/src/BookNavigator/volumes/volumes.js';
|
8
|
+
|
9
|
+
|
10
|
+
const brOptions = {
|
11
|
+
"options": {
|
12
|
+
"enableMultipleBooks": true,
|
13
|
+
"multipleBooksList": {
|
14
|
+
"by_subprefix": {
|
15
|
+
"/details/SubBookTest": {
|
16
|
+
"url_path": "/details/SubBookTest",
|
17
|
+
"file_subprefix": "book1/GPORFP",
|
18
|
+
"orig_sort": 0,
|
19
|
+
"title": "book1/GPORFP.pdf",
|
20
|
+
"file_source": "/book1/GPORFP_jp2.zip"
|
21
|
+
},
|
22
|
+
"/details/SubBookTest/subdir/book2/brewster_kahle_internet_archive": {
|
23
|
+
"url_path": "/details/SubBookTest/subdir/book2/brewster_kahle_internet_archive",
|
24
|
+
"file_subprefix": "subdir/book2/brewster_kahle_internet_archive",
|
25
|
+
"orig_sort": 1,
|
26
|
+
"title": "subdir/book2/brewster_kahle_internet_archive.pdf",
|
27
|
+
"file_source": "/subdir/book2/brewster_kahle_internet_archive_jp2.zip"
|
28
|
+
},
|
29
|
+
"/details/SubBookTest/subdir/subsubdir/book3/Rfp008011ResponseInternetArchive-without-resume": {
|
30
|
+
"url_path": "/details/SubBookTest/subdir/subsubdir/book3/Rfp008011ResponseInternetArchive-without-resume",
|
31
|
+
"file_subprefix": "subdir/subsubdir/book3/Rfp008011ResponseInternetArchive-without-resume",
|
32
|
+
"orig_sort": 2,
|
33
|
+
"title": "subdir/subsubdir/book3/Rfp008011ResponseInternetArchive-without-resume.pdf",
|
34
|
+
"file_source": "/subdir/subsubdir/book3/Rfp008011ResponseInternetArchive-without-resume_jp2.zip"
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
};
|
40
|
+
|
41
|
+
const container = (brOptions, prefix) => (
|
42
|
+
html`
|
43
|
+
<viewable-files .viewableFiles=${brOptions} .hostUrl="https://archive.org" .subPrefix=${prefix}></viewable-files>
|
44
|
+
`
|
45
|
+
);
|
46
|
+
|
47
|
+
beforeEach(() => {
|
48
|
+
const body = document.querySelector('body');
|
49
|
+
const brHook = document.createElement('div');
|
50
|
+
brHook.setAttribute('id', 'BookReader');
|
51
|
+
body.appendChild(brHook);
|
52
|
+
});
|
53
|
+
|
54
|
+
afterEach(() => {
|
55
|
+
sinon.restore();
|
56
|
+
fixtureCleanup();
|
57
|
+
});
|
58
|
+
|
59
|
+
describe('<viewable-files>', () => {
|
60
|
+
test('sets default properties', async () => {
|
61
|
+
const files = brOptions.options.multipleBooksList?.by_subprefix;
|
62
|
+
const viewableFiles = Object.keys(files).map(item => files[item]);
|
63
|
+
const el = await fixture(container(viewableFiles));
|
64
|
+
await el.updateComplete;
|
65
|
+
|
66
|
+
expect(el.viewableFiles).toEqual(viewableFiles);
|
67
|
+
expect(el.viewableFiles.length).toEqual(3);
|
68
|
+
expect(el.shadowRoot.querySelectorAll("ul li").length).toEqual(3);
|
69
|
+
|
70
|
+
expect(el.shadowRoot.querySelector(".item-title").textContent).toContain(`${viewableFiles[0].title}`);
|
71
|
+
});
|
72
|
+
|
73
|
+
test('render empty volumes', async () => {
|
74
|
+
const viewableFiles = [];
|
75
|
+
const el = await fixture(container(viewableFiles));
|
76
|
+
await el.updateComplete;
|
77
|
+
|
78
|
+
expect(el.viewableFiles).toEqual(viewableFiles);
|
79
|
+
expect(el.viewableFiles.length).toEqual(0);
|
80
|
+
expect(el.shadowRoot.childElementCount).not.toEqual(0);
|
81
|
+
});
|
82
|
+
|
83
|
+
test('render active volume item set as first viewable item ', async () => {
|
84
|
+
const files = brOptions.options.multipleBooksList?.by_subprefix;
|
85
|
+
const viewableFiles = Object.keys(files).map(item => files[item]);
|
86
|
+
const prefix = viewableFiles[0].file_subprefix;
|
87
|
+
|
88
|
+
const el = await fixture(container(viewableFiles, prefix));
|
89
|
+
await el.updateComplete;
|
90
|
+
|
91
|
+
expect(el.viewableFiles).toEqual(viewableFiles);
|
92
|
+
expect(el.viewableFiles.length).toEqual(3);
|
93
|
+
|
94
|
+
expect(el.shadowRoot.querySelectorAll("ul li div")[1].className).toEqual("content active");
|
95
|
+
});
|
96
|
+
|
97
|
+
});
|
@@ -111,6 +111,26 @@ for (const dummyVoice of [dummyVoiceHyphens, dummyVoiceUnderscores]) {
|
|
111
111
|
|
112
112
|
expect(getBestBookVoice(voices, 'en', ['en-CA', 'en'])).toBe(voices[0]);
|
113
113
|
});
|
114
|
+
|
115
|
+
test('choose stored language from localStorage', () => {
|
116
|
+
const voices = [
|
117
|
+
dummyVoice({lang: "en-US", voiceURI: "English US", default: true}),
|
118
|
+
dummyVoice({lang: "en-GB", voiceURI: "English GB"}),
|
119
|
+
dummyVoice({lang: "en-CA", voiceURI: "English CA"}),
|
120
|
+
];
|
121
|
+
class DummyEngine extends AbstractTTSEngine {
|
122
|
+
getVoices() { return voices; }
|
123
|
+
}
|
124
|
+
const ttsEngine = new DummyEngine({...DUMMY_TTS_ENGINE_OPTS, bookLanguage: 'en'});
|
125
|
+
// simulates setting default voice on tts startup
|
126
|
+
ttsEngine.updateBestVoice();
|
127
|
+
// simulates user choosing a voice that matches the bookLanguage
|
128
|
+
// voice will be stored in localStorage
|
129
|
+
ttsEngine.setVoice(voices[2].voiceURI);
|
130
|
+
|
131
|
+
// expecting the voice to be selected by getMatchingStoredVoice and returned as best voice
|
132
|
+
expect(getBestBookVoice(voices, 'en', [])).toBe(voices[2]);
|
133
|
+
});
|
114
134
|
});
|
115
135
|
}
|
116
136
|
|
@@ -19,6 +19,10 @@ describe('UrlPlugin tests', () => {
|
|
19
19
|
expect(urlPlugin.urlStateToUrlString(urlStateWithQueries)).toBe(expectedUrlFromStateWithQueries);
|
20
20
|
});
|
21
21
|
|
22
|
+
test('encodes page number', () => {
|
23
|
+
expect(urlPlugin.urlStateToUrlString({ page: '12/46' })).toBe(`page/12%2F46`);
|
24
|
+
});
|
25
|
+
|
22
26
|
test('urlStateToUrlString with unknown states in schema', () => {
|
23
27
|
const urlState = { page: 'n7', mode: '1up' };
|
24
28
|
const urlStateWithQueries = { page: 'n7', mode: '1up', q: 'hello', viewer: 'theater', sortBy: 'title_asc' };
|
@@ -47,6 +51,10 @@ describe('UrlPlugin tests', () => {
|
|
47
51
|
expect(urlPlugin.urlStringToUrlState(url1)).toEqual({page: 'n7', mode: '1up'});
|
48
52
|
});
|
49
53
|
|
54
|
+
test('decodes page number', () => {
|
55
|
+
expect(urlPlugin.urlStringToUrlState('/page/12%2F46')).toStrictEqual({ page: '12/46' });
|
56
|
+
});
|
57
|
+
|
50
58
|
test('urlStringToUrlState with deprecated_for', () => {
|
51
59
|
const url = '/page/n7/mode/2up/search/hello';
|
52
60
|
|
@@ -1,95 +0,0 @@
|
|
1
|
-
import { html } from 'lit';
|
2
|
-
|
3
|
-
import { viewableFilesIcon } from '@internetarchive/ia-item-navigator/dist/src/menus/viewable-files';
|
4
|
-
import '@internetarchive/ia-item-navigator/dist/src/menus/viewable-files';
|
5
|
-
|
6
|
-
/**
|
7
|
-
* * @typedef { 'title_asc' | 'title_desc' | 'default'} SortTypesT
|
8
|
-
*/
|
9
|
-
const sortTypes = {
|
10
|
-
title_asc: 'title_asc',
|
11
|
-
title_desc: 'title_desc',
|
12
|
-
default: 'default'
|
13
|
-
};
|
14
|
-
export default class ViewableFilesProvider {
|
15
|
-
/**
|
16
|
-
* @param {import('../BookReader').default} bookreader
|
17
|
-
*/
|
18
|
-
constructor({ baseHost, bookreader, onProviderChange }) {
|
19
|
-
/** @type {import('../BookReader').default} */
|
20
|
-
this.bookreader = bookreader;
|
21
|
-
this.onProviderChange = onProviderChange;
|
22
|
-
this.baseHost = baseHost;
|
23
|
-
|
24
|
-
const files = bookreader.options.multipleBooksList.by_subprefix;
|
25
|
-
this.viewableFiles = Object.keys(files).map(item => files[item]);
|
26
|
-
this.volumeCount = Object.keys(files).length;
|
27
|
-
|
28
|
-
this.id = "volumes";
|
29
|
-
this.label = `Viewable files (${this.volumeCount})`;
|
30
|
-
this.icon = html`${viewableFilesIcon}`;
|
31
|
-
this.sortOrderBy = sortTypes.default;
|
32
|
-
|
33
|
-
this.component = document.createElement("iaux-in-viewable-files-panel");
|
34
|
-
this.component.addSortToUrl = true;
|
35
|
-
this.component.subPrefix = bookreader.options.subPrefix || "";
|
36
|
-
this.component.baseHost = baseHost;
|
37
|
-
this.component.fileList = [...this.viewableFiles];
|
38
|
-
|
39
|
-
this.sortFilesComponent = document.createElement("iaux-in-sort-files-button");
|
40
|
-
this.sortFilesComponent.fileListRaw = this.viewableFiles;
|
41
|
-
this.sortFilesComponent.addEventListener('fileListSorted', (e) => this.handleFileListSorted(e));
|
42
|
-
this.actionButton = this.sortFilesComponent;
|
43
|
-
|
44
|
-
// get sort state from query param
|
45
|
-
if (this.bookreader.urlPlugin) {
|
46
|
-
this.bookreader.urlPlugin.pullFromAddressBar();
|
47
|
-
|
48
|
-
const urlSortValue = this.bookreader.urlPlugin.getUrlParam('sort');
|
49
|
-
if (urlSortValue === sortTypes.title_asc || urlSortValue === sortTypes.title_desc) {
|
50
|
-
this.sortOrderBy = urlSortValue;
|
51
|
-
}
|
52
|
-
}
|
53
|
-
|
54
|
-
this.sortFilesComponent.sortVolumes(this.sortOrderBy);
|
55
|
-
|
56
|
-
this.onProviderChange(this.bookreader);
|
57
|
-
}
|
58
|
-
|
59
|
-
/** @param { SortTypesT } sortType */
|
60
|
-
async handleFileListSorted(event) {
|
61
|
-
const { sortType, sortedFiles } = event.detail;
|
62
|
-
|
63
|
-
this.viewableFiles = sortedFiles;
|
64
|
-
this.sortOrderBy = sortType;
|
65
|
-
|
66
|
-
// update the component
|
67
|
-
this.component.fileList = [...this.viewableFiles];
|
68
|
-
await this.component.updateComplete;
|
69
|
-
|
70
|
-
if (this.bookreader.urlPlugin) {
|
71
|
-
this.bookreader.urlPlugin.pullFromAddressBar();
|
72
|
-
if (this.sortOrderBy !== sortTypes.default) {
|
73
|
-
this.bookreader.urlPlugin.setUrlParam('sort', this.sortOrderBy);
|
74
|
-
} else {
|
75
|
-
this.bookreader.urlPlugin.removeUrlParam('sort');
|
76
|
-
}
|
77
|
-
}
|
78
|
-
|
79
|
-
this.onProviderChange(this.bookreader);
|
80
|
-
|
81
|
-
this.multipleFilesClicked(this.sortOrderBy);
|
82
|
-
}
|
83
|
-
|
84
|
-
/**
|
85
|
-
* @param { SortTypesT } orderBy
|
86
|
-
*/
|
87
|
-
multipleFilesClicked(orderBy) {
|
88
|
-
window.archive_analytics?.send_event(
|
89
|
-
'BookReader',
|
90
|
-
`VolumesSort|${orderBy}`,
|
91
|
-
window.location.path,
|
92
|
-
);
|
93
|
-
}
|
94
|
-
|
95
|
-
}
|