pinokiod 8.0.47 → 8.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/kernel/vault/constants.js +2 -2
- package/package.json +1 -1
- package/server/public/vault.css +8 -0
- package/server/public/vault.js +93 -27
- package/test/vault-ui.test.js +91 -3
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const CANDIDATE_SIZE_BASE = process.platform === "win32" ? 1024 : 1000
|
|
2
|
-
const CANDIDATE_SIZE_OPTIONS = [10, 50, 100, 500]
|
|
2
|
+
const CANDIDATE_SIZE_OPTIONS = [1, 10, 50, 100, 500]
|
|
3
3
|
.map((value) => value * CANDIDATE_SIZE_BASE ** 2)
|
|
4
4
|
.concat(CANDIDATE_SIZE_BASE ** 3)
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
|
-
SIZE_THRESHOLD: CANDIDATE_SIZE_OPTIONS[
|
|
7
|
+
SIZE_THRESHOLD: CANDIDATE_SIZE_OPTIONS[3],
|
|
8
8
|
CANDIDATE_SIZE_OPTIONS,
|
|
9
9
|
TMP_SUFFIX: ".pinokio-dedup-tmp",
|
|
10
10
|
SHA256_RE: /^[0-9a-f]{64}$/,
|
package/package.json
CHANGED
package/server/public/vault.css
CHANGED
|
@@ -912,10 +912,17 @@ body.dark .vault-row-action .vault-text-button:hover:not(:disabled) {
|
|
|
912
912
|
.vault-empty p { margin: 0; color: var(--task-muted); font-size: 12px; line-height: 1.55; }
|
|
913
913
|
.vault-empty .vault-button { margin-top: 10px; }
|
|
914
914
|
.vault-pane-footer {
|
|
915
|
+
display: flex;
|
|
916
|
+
align-items: center;
|
|
917
|
+
justify-content: space-between;
|
|
918
|
+
gap: 12px;
|
|
915
919
|
padding: 9px var(--vault-inline) 11px;
|
|
916
920
|
color: var(--task-muted);
|
|
917
921
|
font-size: 10.5px;
|
|
918
922
|
}
|
|
923
|
+
.vault-pagination { display: inline-flex; align-items: center; gap: 10px; white-space: nowrap; }
|
|
924
|
+
.vault-pagination .vault-text-button:disabled { cursor: default; }
|
|
925
|
+
.vault-page-range { font-variant-numeric: tabular-nums; }
|
|
919
926
|
.vault-event-kind { font-weight: 570; }
|
|
920
927
|
.vault-event-time { color: var(--task-muted); font-size: 10.5px; }
|
|
921
928
|
.vault-unavailable { color: var(--task-muted); font-size: 11px; }
|
|
@@ -943,6 +950,7 @@ body.dark .vault-row-action .vault-text-button:hover:not(:disabled) {
|
|
|
943
950
|
.vault-result-message { grid-template-columns: 16px minmax(0, 1fr); padding: 9px 0; }
|
|
944
951
|
.vault-result-heading { align-items: flex-start; flex-direction: column; gap: 2px; }
|
|
945
952
|
.vault-result-actions { grid-column: 2; }
|
|
953
|
+
.vault-pane-footer { align-items: flex-start; flex-direction: column; }
|
|
946
954
|
.vault-result.incomplete .vault-button { margin: 0; }
|
|
947
955
|
}
|
|
948
956
|
@media (pointer: coarse) {
|
package/server/public/vault.js
CHANGED
|
@@ -103,6 +103,9 @@ const COPY = {
|
|
|
103
103
|
display_mode: "Display mode",
|
|
104
104
|
sorted_largest: "sorted largest first",
|
|
105
105
|
sorted_smallest: "sorted smallest first",
|
|
106
|
+
previous: "Previous",
|
|
107
|
+
next: "Next",
|
|
108
|
+
file_pages: "File pages",
|
|
106
109
|
status: "Deduplication status",
|
|
107
110
|
matches: "Matches",
|
|
108
111
|
can_save: "Can save",
|
|
@@ -200,9 +203,10 @@ const statusUrl = (progress = false) => {
|
|
|
200
203
|
const reviewedScanKey = `pinokio:vault:reviewed-scan:${SCOPE_ID || "global"}`
|
|
201
204
|
const candidateSizeKey = "pinokio:vault:candidate-size"
|
|
202
205
|
const candidateSizeBase = document.body.dataset.platform === "win32" ? 1024 : 1000
|
|
203
|
-
const candidateSizeOptions = [10, 50, 100, 500]
|
|
206
|
+
const candidateSizeOptions = [1, 10, 50, 100, 500]
|
|
204
207
|
.map((value) => value * candidateSizeBase ** 2)
|
|
205
208
|
.concat(candidateSizeBase ** 3)
|
|
209
|
+
const PAGE_SIZE = 500
|
|
206
210
|
|
|
207
211
|
const state = {
|
|
208
212
|
data: null,
|
|
@@ -221,7 +225,9 @@ const state = {
|
|
|
221
225
|
scanProblemsOpen: false,
|
|
222
226
|
feedback: null,
|
|
223
227
|
actionProgress: null,
|
|
224
|
-
actionRequest: false
|
|
228
|
+
actionRequest: false,
|
|
229
|
+
page: 0,
|
|
230
|
+
pageContext: null
|
|
225
231
|
}
|
|
226
232
|
|
|
227
233
|
const el = (id) => document.getElementById(id)
|
|
@@ -232,7 +238,7 @@ const attr = esc
|
|
|
232
238
|
const fmt = window.PinokioFormatStorageSize
|
|
233
239
|
const candidateSize = () => {
|
|
234
240
|
const value = Number(el("vault-candidate-size").value)
|
|
235
|
-
return candidateSizeOptions.includes(value) ? value : candidateSizeOptions[
|
|
241
|
+
return candidateSizeOptions.includes(value) ? value : candidateSizeOptions[3]
|
|
236
242
|
}
|
|
237
243
|
const countLabel = (count, singular = COPY.file, plural = COPY.files) => `${count} ${count === 1 ? singular : plural}`
|
|
238
244
|
const basename = (value) => String(value || "").split(/[\\/]/).filter(Boolean).pop() || ""
|
|
@@ -706,8 +712,9 @@ const renderDuplicateGroups = (items) => {
|
|
|
706
712
|
return [...groups.entries()].sort(sourceSort).map(([sourceId, group]) => {
|
|
707
713
|
const source = sourceById(sourceId)
|
|
708
714
|
const bytes = group.filter((item) => item.shareable).reduce((sum, item) => sum + item.size, 0)
|
|
709
|
-
const
|
|
710
|
-
|
|
715
|
+
const allShareable = scopeDuplicates(sourceId).filter((item) => item.shareable !== false)
|
|
716
|
+
const action = source && source.shareable && allShareable.length
|
|
717
|
+
? `<button class="vault-button" type="button" data-deduplicate-scope="${attr(sourceId)}">${esc(COPY.deduplicate)} ${countLabel(allShareable.length)}</button>`
|
|
711
718
|
: `<span class="vault-unavailable">${esc(COPY.sharing_unavailable)}</span>`
|
|
712
719
|
return `<div class="vault-group-row"><div class="vault-group-main"><div class="vault-group-title"><i class="fa-regular fa-folder"></i><span>${esc(groupTitle(source))}</span></div><div class="vault-group-meta">${countLabel(group.length, COPY.duplicate.toLowerCase(), COPY.duplicates.toLowerCase())} · ${bytes ? fmt(bytes) : COPY.unavailable}</div></div><div class="vault-group-action">${action}</div></div>${[...group].sort((a, b) => compareRows(a, b, (item) => item.relative_path)).map((item) => renderFileRow(item, 0, true)).join("")}`
|
|
713
720
|
}).join("")
|
|
@@ -751,10 +758,9 @@ const emptyState = (view) => {
|
|
|
751
758
|
return `<div class="vault-empty"><div class="vault-empty-inner"><i class="${content[2]}"></i><h3>${esc(content[0])}</h3><p>${esc(content[1])}</p>${view === "duplicates" ? `<button class="vault-button" type="button" data-view="all">${esc(COPY.view_all)}</button>` : view === "all" && !activeScan ? `<button class="vault-button" type="button" id="btn-empty-scan">${esc(scanLabel)}</button>` : ""}</div></div>`
|
|
752
759
|
}
|
|
753
760
|
|
|
754
|
-
const renderReclaimable = () => {
|
|
755
|
-
const blobs = state.data.blobs.filter((blob) => blob.orphan)
|
|
761
|
+
const renderReclaimable = (blobs) => {
|
|
756
762
|
if (!blobs.length) return emptyState("reclaimable")
|
|
757
|
-
return
|
|
763
|
+
return blobs.map((blob) => `<div class="vault-file-row"><div class="vault-name-cell"><span class="vault-disclosure"></span><i class="fa-regular fa-file vault-name-icon"></i><span class="vault-name-copy"><span class="vault-file-name">${esc(blob.names[0] ? basename(blob.names[0].path) : `${blob.hash.slice(0, 12)}…`)}</span></span></div><span class="vault-size">${fmt(blob.size)}</span><span class="vault-space">${fmt(blob.size)}</span><span class="vault-row-action"><button class="vault-text-button" type="button" data-reclaim="${attr(blob.hash)}">${esc(COPY.reclaim)}</button></span></div>`).join("")
|
|
758
764
|
}
|
|
759
765
|
|
|
760
766
|
const renderActivity = () => {
|
|
@@ -794,7 +800,7 @@ const renderTable = (items) => {
|
|
|
794
800
|
} else if (state.view === "reclaimable") {
|
|
795
801
|
tableClass = "reclaimable"
|
|
796
802
|
headers = [COPY.name, COPY.size, COPY.can_free, ""]
|
|
797
|
-
body = renderReclaimable()
|
|
803
|
+
body = renderReclaimable(items)
|
|
798
804
|
} else if (state.view === "activity") {
|
|
799
805
|
tableClass = "activity"
|
|
800
806
|
headers = [COPY.name, COPY.size, COPY.last_scanned, ""]
|
|
@@ -815,6 +821,73 @@ const renderTable = (items) => {
|
|
|
815
821
|
el("vault-table-wrap").innerHTML = `<div class="vault-table ${tableClass}"><div class="vault-columns">${headerMarkup}</div>${body}</div>`
|
|
816
822
|
}
|
|
817
823
|
|
|
824
|
+
const orderedItems = (items) => {
|
|
825
|
+
if (state.view === "activity") return items
|
|
826
|
+
if (state.view === "reclaimable") {
|
|
827
|
+
return [...items].sort((a, b) => compareRows(a, b,
|
|
828
|
+
(blob) => blob.names[0] ? blob.names[0].path : blob.hash))
|
|
829
|
+
}
|
|
830
|
+
if (state.displayMode === "files" && supportsDisplayMode()) {
|
|
831
|
+
return [...items].sort((a, b) => compareRows(a, b, flatLocation))
|
|
832
|
+
}
|
|
833
|
+
return [...items].sort((a, b) => {
|
|
834
|
+
const sourceOrder = state.sourceId ? 0 : sourceSort([a.source_id], [b.source_id])
|
|
835
|
+
return sourceOrder || compareRows(a, b, (item) => item.relative_path || item.path)
|
|
836
|
+
})
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const pagedItems = (items) => {
|
|
840
|
+
if (state.view === "activity") {
|
|
841
|
+
return { items, start: 0, end: items.length, total: items.length, pages: 1 }
|
|
842
|
+
}
|
|
843
|
+
const context = JSON.stringify([
|
|
844
|
+
state.view, state.sourceId, state.query, state.statusFilter,
|
|
845
|
+
state.displayMode, state.sizeSort
|
|
846
|
+
])
|
|
847
|
+
if (context !== state.pageContext) {
|
|
848
|
+
state.page = 0
|
|
849
|
+
state.pageContext = context
|
|
850
|
+
}
|
|
851
|
+
const ordered = orderedItems(items)
|
|
852
|
+
const pages = Math.max(1, Math.ceil(ordered.length / PAGE_SIZE))
|
|
853
|
+
state.page = Math.max(0, Math.min(state.page, pages - 1))
|
|
854
|
+
const start = state.page * PAGE_SIZE
|
|
855
|
+
const end = Math.min(start + PAGE_SIZE, ordered.length)
|
|
856
|
+
return { items: ordered.slice(start, end), start, end, total: ordered.length, pages }
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const paneFooterText = (items) => {
|
|
860
|
+
if (state.displayMode === "files" && supportsDisplayMode()) {
|
|
861
|
+
const count = state.view === "shared"
|
|
862
|
+
? countLabel(items.length, "deduplicated file", "deduplicated files")
|
|
863
|
+
: state.view === "independent"
|
|
864
|
+
? countLabel(items.length, "file kept separate", "files kept separate")
|
|
865
|
+
: countLabel(items.length)
|
|
866
|
+
const order = state.sizeSort === "desc"
|
|
867
|
+
? COPY.sorted_largest
|
|
868
|
+
: state.sizeSort === "asc" ? COPY.sorted_smallest : ""
|
|
869
|
+
return `${count}${order ? ` · ${order}` : ""}`
|
|
870
|
+
}
|
|
871
|
+
if (state.view === "duplicates") return COPY.duplicate_note
|
|
872
|
+
if (state.view === "reclaimable") return COPY.reclaimable_note
|
|
873
|
+
return COPY.tracked_note.replace("{size}", fmt(candidateSize()))
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const renderPaneFooter = (items, page) => {
|
|
877
|
+
const footer = el("vault-pane-footer")
|
|
878
|
+
const message = paneFooterText(items)
|
|
879
|
+
if (page.pages === 1) {
|
|
880
|
+
footer.textContent = message
|
|
881
|
+
return
|
|
882
|
+
}
|
|
883
|
+
footer.innerHTML = `<span>${esc(message)}</span>
|
|
884
|
+
<span class="vault-pagination" role="navigation" aria-label="${attr(COPY.file_pages)}">
|
|
885
|
+
<button class="vault-text-button" type="button" data-page="previous" ${state.page === 0 ? "disabled" : ""}>${esc(COPY.previous)}</button>
|
|
886
|
+
<span class="vault-page-range">${page.start + 1}–${page.end} of ${page.total}</span>
|
|
887
|
+
<button class="vault-text-button" type="button" data-page="next" ${state.page === page.pages - 1 ? "disabled" : ""}>${esc(COPY.next)}</button>
|
|
888
|
+
</span>`
|
|
889
|
+
}
|
|
890
|
+
|
|
818
891
|
const renderOverview = () => {
|
|
819
892
|
const data = state.data
|
|
820
893
|
const last = data.last_scan
|
|
@@ -1131,23 +1204,10 @@ const render = () => {
|
|
|
1131
1204
|
renderLocations(items)
|
|
1132
1205
|
const visible = activeItems(items)
|
|
1133
1206
|
renderToolbar(visible, items)
|
|
1134
|
-
|
|
1207
|
+
const page = pagedItems(visible)
|
|
1208
|
+
renderTable(page.items)
|
|
1135
1209
|
renderActionProgress()
|
|
1136
|
-
|
|
1137
|
-
const count = state.view === "shared"
|
|
1138
|
-
? countLabel(visible.length, "deduplicated file", "deduplicated files")
|
|
1139
|
-
: state.view === "independent"
|
|
1140
|
-
? countLabel(visible.length, "file kept separate", "files kept separate")
|
|
1141
|
-
: countLabel(visible.length)
|
|
1142
|
-
const order = state.sizeSort === "desc" ? COPY.sorted_largest : state.sizeSort === "asc" ? COPY.sorted_smallest : ""
|
|
1143
|
-
el("vault-pane-footer").textContent = `${count}${order ? ` · ${order}` : ""}`
|
|
1144
|
-
} else {
|
|
1145
|
-
el("vault-pane-footer").textContent = state.view === "duplicates"
|
|
1146
|
-
? COPY.duplicate_note
|
|
1147
|
-
: state.view === "reclaimable"
|
|
1148
|
-
? COPY.reclaimable_note
|
|
1149
|
-
: COPY.tracked_note.replace("{size}", fmt(candidateSize()))
|
|
1150
|
-
}
|
|
1210
|
+
renderPaneFooter(visible, page)
|
|
1151
1211
|
}
|
|
1152
1212
|
|
|
1153
1213
|
const scanActive = (scan) => !!(scan && (scan.pending || scan.active || scan.queued > 0))
|
|
@@ -1405,6 +1465,10 @@ document.addEventListener("click", async (event) => {
|
|
|
1405
1465
|
if (target.id === "btn-dismiss-cloud") {
|
|
1406
1466
|
try { localStorage.setItem(target.dataset.warningKey, "1") } catch (error) {}
|
|
1407
1467
|
renderCloudWarning()
|
|
1468
|
+
} else if (target.dataset.page) {
|
|
1469
|
+
state.page += target.dataset.page === "next" ? 1 : -1
|
|
1470
|
+
render()
|
|
1471
|
+
el("vault-table-wrap").scrollTop = 0
|
|
1408
1472
|
} else if (target.hasAttribute("data-sort-size")) {
|
|
1409
1473
|
state.sizeSort = state.sizeSort === "desc" ? "asc" : "desc"
|
|
1410
1474
|
render()
|
|
@@ -1597,7 +1661,9 @@ document.addEventListener("input", (event) => {
|
|
|
1597
1661
|
state.query = event.target.value
|
|
1598
1662
|
const items = activeItems(buildItems())
|
|
1599
1663
|
updateToolbarSummary(items)
|
|
1600
|
-
|
|
1664
|
+
const page = pagedItems(items)
|
|
1665
|
+
renderTable(page.items)
|
|
1666
|
+
renderPaneFooter(items, page)
|
|
1601
1667
|
renderActionProgress()
|
|
1602
1668
|
})
|
|
1603
1669
|
document.addEventListener("change", (event) => {
|
|
@@ -1617,7 +1683,7 @@ candidateSizeSelect.setAttribute("aria-label", COPY.minimum_file_size)
|
|
|
1617
1683
|
candidateSizeSelect.innerHTML = candidateSizeOptions
|
|
1618
1684
|
.map((size) => `<option value="${size}">${fmt(size)}+</option>`)
|
|
1619
1685
|
.join("")
|
|
1620
|
-
candidateSizeSelect.value = String(candidateSizeOptions[
|
|
1686
|
+
candidateSizeSelect.value = String(candidateSizeOptions[3])
|
|
1621
1687
|
try {
|
|
1622
1688
|
const storedCandidateSize = Number(localStorage.getItem(candidateSizeKey))
|
|
1623
1689
|
if (candidateSizeOptions.includes(storedCandidateSize)) {
|
package/test/vault-ui.test.js
CHANGED
|
@@ -76,12 +76,12 @@ describe('vault dashboard backend (phase 4)', () => {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
const accepted = await vault.perform('scan', {
|
|
79
|
-
candidate_size: CANDIDATE_SIZE_OPTIONS[
|
|
79
|
+
candidate_size: CANDIDATE_SIZE_OPTIONS[0]
|
|
80
80
|
})
|
|
81
81
|
assert.deepStrictEqual(accepted, { started: true })
|
|
82
82
|
assert.deepStrictEqual(startedWith, {
|
|
83
83
|
scopeId: null,
|
|
84
|
-
sizeThreshold: CANDIDATE_SIZE_OPTIONS[
|
|
84
|
+
sizeThreshold: CANDIDATE_SIZE_OPTIONS[0]
|
|
85
85
|
})
|
|
86
86
|
|
|
87
87
|
startedWith = null
|
|
@@ -1323,7 +1323,7 @@ describe('vault dashboard backend (phase 4)', () => {
|
|
|
1323
1323
|
assert.match(dom.window.document.getElementById('btn-scan').textContent, /Scan this app/)
|
|
1324
1324
|
const candidateSize = dom.window.document.getElementById('vault-candidate-size')
|
|
1325
1325
|
assert.deepStrictEqual([...candidateSize.options].map((option) => option.textContent),
|
|
1326
|
-
['10 MB+', '50 MB+', '100 MB+', '500 MB+', '1 GB+'])
|
|
1326
|
+
['1 MB+', '10 MB+', '50 MB+', '100 MB+', '500 MB+', '1 GB+'])
|
|
1327
1327
|
assert.strictEqual(candidateSize.value, '100000000')
|
|
1328
1328
|
candidateSize.value = '50000000'
|
|
1329
1329
|
candidateSize.dispatchEvent(new dom.window.Event('change', { bubbles: true }))
|
|
@@ -1853,6 +1853,94 @@ describe('vault dashboard backend (phase 4)', () => {
|
|
|
1853
1853
|
}
|
|
1854
1854
|
})
|
|
1855
1855
|
|
|
1856
|
+
test('large file lists render bounded pages without narrowing Deduplicate all', async () => {
|
|
1857
|
+
const views = path.resolve(__dirname, '..', 'server', 'views')
|
|
1858
|
+
const html = await ejs.renderFile(path.resolve(views, 'vault_app.ejs'), {
|
|
1859
|
+
theme: 'light', platform: 'darwin', agent: 'electron', scope_id: 'app:appB'
|
|
1860
|
+
})
|
|
1861
|
+
const dom = new JSDOM(html, {
|
|
1862
|
+
url: 'http://localhost/vault/app/appB',
|
|
1863
|
+
runScripts: 'dangerously'
|
|
1864
|
+
})
|
|
1865
|
+
const actions = []
|
|
1866
|
+
const duplicates = Array.from({ length: 501 }, (_, index) => {
|
|
1867
|
+
const name = `file-${String(index).padStart(4, '0')}.bin`
|
|
1868
|
+
return {
|
|
1869
|
+
path: `/pinokio/api/appB/${name}`,
|
|
1870
|
+
relative_path: name,
|
|
1871
|
+
source_id: 'app:appB',
|
|
1872
|
+
source_label: 'appB',
|
|
1873
|
+
size: 1024 + index,
|
|
1874
|
+
shareable: true,
|
|
1875
|
+
match: { path: `/pinokio/api/appA/${name}` }
|
|
1876
|
+
}
|
|
1877
|
+
})
|
|
1878
|
+
const status = {
|
|
1879
|
+
enabled: true,
|
|
1880
|
+
mode: 'link',
|
|
1881
|
+
scan: { active: false, pending: false, queued: 0, phase: 'idle', scope_id: null },
|
|
1882
|
+
last_scan: { ts: Date.now(), bytes_total: 1024 * 501 },
|
|
1883
|
+
tracked_bytes: 1024 * 501,
|
|
1884
|
+
effective_bytes: 1024 * 501,
|
|
1885
|
+
shared_bytes: 0,
|
|
1886
|
+
pending_bytes: duplicates.reduce((sum, item) => sum + item.size, 0),
|
|
1887
|
+
activity_error: null,
|
|
1888
|
+
cloud_sync_warning: null,
|
|
1889
|
+
sources: [{
|
|
1890
|
+
id: 'app:appB', kind: 'app', label: 'appB', root: '/pinokio/api/appB',
|
|
1891
|
+
display_path: '/pinokio/api/appB', parent_id: null, available: true, shareable: true
|
|
1892
|
+
}],
|
|
1893
|
+
blobs: [],
|
|
1894
|
+
duplicates,
|
|
1895
|
+
excluded: [],
|
|
1896
|
+
events: [],
|
|
1897
|
+
undo_batches: []
|
|
1898
|
+
}
|
|
1899
|
+
dom.window.fetch = async (url, options = {}) => {
|
|
1900
|
+
if (options.method === 'POST') {
|
|
1901
|
+
actions.push(JSON.parse(options.body))
|
|
1902
|
+
return { ok: true, json: async () => ({ converted: 501, bytes_saved: status.pending_bytes }) }
|
|
1903
|
+
}
|
|
1904
|
+
return { ok: true, json: async () => status }
|
|
1905
|
+
}
|
|
1906
|
+
await runVaultScript(dom)
|
|
1907
|
+
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
1908
|
+
|
|
1909
|
+
dom.window.document.querySelector('[data-view="duplicates"]').click()
|
|
1910
|
+
const rows = () => [...dom.window.document.querySelectorAll('.vault-table.matches .vault-file-row')]
|
|
1911
|
+
let bulk = dom.window.document.querySelector('[data-deduplicate-all="duplicates"]')
|
|
1912
|
+
assert.strictEqual(rows().length, 500)
|
|
1913
|
+
assert.strictEqual(bulk.textContent, 'Deduplicate 501 files')
|
|
1914
|
+
assert.strictEqual(dom.window.document.querySelector('.vault-page-range').textContent, '1–500 of 501')
|
|
1915
|
+
assert.strictEqual(dom.window.document.querySelector('[data-page="previous"]').disabled, true)
|
|
1916
|
+
assert.strictEqual(dom.window.document.querySelector('[data-page="next"]').disabled, false)
|
|
1917
|
+
|
|
1918
|
+
dom.window.document.querySelector('[data-page="next"]').click()
|
|
1919
|
+
assert.strictEqual(rows().length, 1)
|
|
1920
|
+
assert.strictEqual(rows()[0].querySelector('.vault-file-name').textContent, 'file-0500.bin')
|
|
1921
|
+
assert.strictEqual(dom.window.document.querySelector('.vault-page-range').textContent, '501–501 of 501')
|
|
1922
|
+
assert.strictEqual(dom.window.document.querySelector('[data-page="previous"]').disabled, false)
|
|
1923
|
+
assert.strictEqual(dom.window.document.querySelector('[data-page="next"]').disabled, true)
|
|
1924
|
+
|
|
1925
|
+
bulk = dom.window.document.querySelector('[data-deduplicate-all="duplicates"]')
|
|
1926
|
+
assert.strictEqual(bulk.textContent, 'Deduplicate 501 files')
|
|
1927
|
+
bulk.click()
|
|
1928
|
+
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
1929
|
+
assert.deepStrictEqual(actions[0], {
|
|
1930
|
+
action: 'deduplicate',
|
|
1931
|
+
selection: 'duplicates',
|
|
1932
|
+
scope_id: 'app:appB'
|
|
1933
|
+
})
|
|
1934
|
+
|
|
1935
|
+
const search = dom.window.document.getElementById('vault-search')
|
|
1936
|
+
search.value = 'file-0001.bin'
|
|
1937
|
+
search.dispatchEvent(new dom.window.Event('input', { bubbles: true }))
|
|
1938
|
+
assert.strictEqual(rows().length, 1)
|
|
1939
|
+
assert.strictEqual(rows()[0].querySelector('.vault-file-name').textContent, 'file-0001.bin')
|
|
1940
|
+
assert.strictEqual(dom.window.document.querySelector('.vault-pagination'), null)
|
|
1941
|
+
dom.window.close()
|
|
1942
|
+
})
|
|
1943
|
+
|
|
1856
1944
|
test('app inventory uses explicit sharing actions without ambiguous switches', async () => {
|
|
1857
1945
|
const views = path.resolve(__dirname, '..', 'server', 'views')
|
|
1858
1946
|
const html = await ejs.renderFile(path.resolve(views, 'vault_app.ejs'), {
|