pinokiod 8.0.78 → 8.0.80
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/api/notify/index.js +2 -1
- package/kernel/vault/automatic_scans.js +6 -7
- package/kernel/vault/index.js +59 -16
- package/kernel/vault/registry.js +2 -0
- package/kernel/vault/registry_core.js +39 -0
- package/package.json +1 -1
- package/server/index.js +5 -2
- package/server/public/tab-idle-notifier.js +151 -11
- package/server/public/tab-link-popover.css +128 -0
- package/server/public/tab-link-popover.js +36 -4
- package/server/public/vault.js +69 -14
- package/server/views/app.ejs +240 -40
- package/test/launch-settings-ui.test.js +37 -5
- package/test/tab-notification-settings.test.js +135 -0
- package/test/vault-automatic-scans.test.js +1 -0
- package/test/vault-engine.test.js +151 -12
- package/test/vault-ui.test.js +150 -16
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const assert = require("node:assert/strict")
|
|
2
|
+
const fs = require("node:fs/promises")
|
|
3
|
+
const path = require("node:path")
|
|
4
|
+
const test = require("node:test")
|
|
5
|
+
const { JSDOM, VirtualConsole } = require("jsdom")
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(__dirname, "..")
|
|
8
|
+
const notifierPath = path.resolve(root, "server/public/tab-idle-notifier.js")
|
|
9
|
+
const popoverPath = path.resolve(root, "server/public/tab-link-popover.js")
|
|
10
|
+
const popoverCssPath = path.resolve(root, "server/public/tab-link-popover.css")
|
|
11
|
+
|
|
12
|
+
const settle = () => new Promise((resolve) => setImmediate(resolve))
|
|
13
|
+
|
|
14
|
+
test("desktop notification settings mount inline and persist both scopes", async () => {
|
|
15
|
+
const script = await fs.readFile(notifierPath, "utf8")
|
|
16
|
+
const virtualConsole = new VirtualConsole()
|
|
17
|
+
const dom = new JSDOM(`<!doctype html><html><head></head><body>
|
|
18
|
+
<div class="appcanvas vertical">
|
|
19
|
+
<aside>
|
|
20
|
+
<div class="menu-container">
|
|
21
|
+
<a class="frame-link" href="/run/test" target="test-shell" data-can-notify="true">
|
|
22
|
+
<span class="tab"><span class="tab-main">Test</span></span>
|
|
23
|
+
</a>
|
|
24
|
+
</div>
|
|
25
|
+
</aside>
|
|
26
|
+
</div>
|
|
27
|
+
<div id="settings"></div>
|
|
28
|
+
</body></html>`, {
|
|
29
|
+
url: "http://127.0.0.1:42000/run/test",
|
|
30
|
+
runScripts: "outside-only",
|
|
31
|
+
pretendToBeVisual: true,
|
|
32
|
+
virtualConsole,
|
|
33
|
+
beforeParse(window) {
|
|
34
|
+
window.fetch = async () => ({
|
|
35
|
+
ok: true,
|
|
36
|
+
async json() {
|
|
37
|
+
return {
|
|
38
|
+
sounds: [{
|
|
39
|
+
url: "/sound/bell.mp3",
|
|
40
|
+
label: "Bell",
|
|
41
|
+
filename: "bell.mp3"
|
|
42
|
+
}]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
window.Audio = class {
|
|
47
|
+
play() {
|
|
48
|
+
return Promise.resolve()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
dom.window.eval(script)
|
|
56
|
+
const link = dom.window.document.querySelector(".frame-link")
|
|
57
|
+
const settings = dom.window.document.getElementById("settings")
|
|
58
|
+
let enabledChange = null
|
|
59
|
+
let soundChange = null
|
|
60
|
+
|
|
61
|
+
const mounted = dom.window.PinokioIdleNotifier.mountSettingsForLink(link, settings, {
|
|
62
|
+
onEnabledChange(enabled) {
|
|
63
|
+
enabledChange = enabled
|
|
64
|
+
},
|
|
65
|
+
onSoundChange(choice) {
|
|
66
|
+
soundChange = choice
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
assert.equal(mounted, true)
|
|
71
|
+
assert.equal(settings.textContent.includes("Notifications for this tab"), true)
|
|
72
|
+
assert.equal(settings.textContent.includes("Sound"), true)
|
|
73
|
+
|
|
74
|
+
const toggle = settings.querySelector('[role="switch"]')
|
|
75
|
+
const status = settings.querySelector(".tab-link-notification-toggle-status")
|
|
76
|
+
const select = settings.querySelector("select")
|
|
77
|
+
assert.equal(toggle.getAttribute("aria-checked"), "true")
|
|
78
|
+
assert.equal(status.textContent, "On")
|
|
79
|
+
assert.equal(select.value, "__default__")
|
|
80
|
+
|
|
81
|
+
toggle.click()
|
|
82
|
+
assert.equal(toggle.getAttribute("aria-checked"), "false")
|
|
83
|
+
assert.equal(status.textContent, "Off")
|
|
84
|
+
assert.equal(enabledChange, false)
|
|
85
|
+
assert.deepEqual(JSON.parse(dom.window.localStorage.getItem("pinokio:idle-prefs")), {
|
|
86
|
+
"test-shell": false
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
toggle.click()
|
|
90
|
+
assert.equal(toggle.getAttribute("aria-checked"), "true")
|
|
91
|
+
assert.equal(status.textContent, "On")
|
|
92
|
+
assert.equal(enabledChange, true)
|
|
93
|
+
assert.equal(dom.window.localStorage.getItem("pinokio:idle-prefs"), null)
|
|
94
|
+
|
|
95
|
+
await settle()
|
|
96
|
+
assert.equal(Array.from(select.options).some((option) => option.textContent === "Bell"), true)
|
|
97
|
+
select.value = "/sound/bell.mp3"
|
|
98
|
+
select.dispatchEvent(new dom.window.Event("change", { bubbles: true }))
|
|
99
|
+
assert.equal(soundChange, "/sound/bell.mp3")
|
|
100
|
+
assert.deepEqual(JSON.parse(dom.window.localStorage.getItem("pinokio:idle-sound")), {
|
|
101
|
+
choice: "/sound/bell.mp3"
|
|
102
|
+
})
|
|
103
|
+
} finally {
|
|
104
|
+
dom.window.close()
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test("tab actions expands notification settings instead of reopening at the sidebar", async () => {
|
|
109
|
+
const [popover, css] = await Promise.all([
|
|
110
|
+
fs.readFile(popoverPath, "utf8"),
|
|
111
|
+
fs.readFile(popoverCssPath, "utf8")
|
|
112
|
+
])
|
|
113
|
+
const branchStart = popover.indexOf('if (action === "notifications")')
|
|
114
|
+
const branchEnd = popover.indexOf('const url = item.getAttribute("data-url")', branchStart)
|
|
115
|
+
assert.notEqual(branchStart, -1)
|
|
116
|
+
assert.notEqual(branchEnd, -1)
|
|
117
|
+
const branch = popover.slice(branchStart, branchEnd)
|
|
118
|
+
|
|
119
|
+
assert.match(branch, /mountSettingsForLink\(activeLink, panel/)
|
|
120
|
+
assert.match(branch, /tab-link-notification-settings/)
|
|
121
|
+
assert.match(branch, /panel\.setAttribute\("role", "group"\)/)
|
|
122
|
+
assert.doesNotMatch(branch, /hideTabLinkPopover/)
|
|
123
|
+
assert.doesNotMatch(branch, /openMenuForLink/)
|
|
124
|
+
assert.match(css, /\.tab-link-notification-settings\s*\{/)
|
|
125
|
+
assert.match(css, /\.tab-link-notification-switch:focus-visible/)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test("legacy notification popover stays inside the viewport", async () => {
|
|
129
|
+
const notifier = await fs.readFile(notifierPath, "utf8")
|
|
130
|
+
|
|
131
|
+
assert.match(notifier, /\.pinokio-notify-popover \{[\s\S]*position: fixed;/)
|
|
132
|
+
assert.match(notifier, /\.pinokio-notify-popover \{[\s\S]*max-height: calc\(100dvh - 24px\);/)
|
|
133
|
+
assert.match(notifier, /const topAbove = rect\.top - menuHeight - menuGap;/)
|
|
134
|
+
assert.match(notifier, /top \+ menuHeight > window\.innerHeight - viewportPadding/)
|
|
135
|
+
})
|
|
@@ -1247,6 +1247,7 @@ describe("automatic app checks", () => {
|
|
|
1247
1247
|
await fs.promises.writeFile(second, "same-content")
|
|
1248
1248
|
const vault = await makeVault(home)
|
|
1249
1249
|
vault.lastScanCache.set("", { candidate_min_bytes: 1 })
|
|
1250
|
+
await vault.registry.setScanSetting(`app:${app}`, 0)
|
|
1250
1251
|
const hashedPaths = []
|
|
1251
1252
|
const originalHashFile = vault.hashFile.bind(vault)
|
|
1252
1253
|
vault.hashFile = async (filePath, options) => {
|
|
@@ -29,13 +29,18 @@ const makeOutside = async () => {
|
|
|
29
29
|
return directory
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const makeVault = async () => {
|
|
32
|
+
const makeVault = async ({ candidateSize = CANDIDATE_SIZE_OPTIONS[0] } = {}) => {
|
|
33
33
|
const home = await makeHome()
|
|
34
34
|
const kernel = { homedir: home, platform: process.platform }
|
|
35
35
|
const vault = new Vault(kernel)
|
|
36
36
|
kernel.vault = vault
|
|
37
37
|
await vault.init()
|
|
38
38
|
vault.sizeThreshold = CANDIDATE_SIZE_OPTIONS[0]
|
|
39
|
+
if (candidateSize !== null) {
|
|
40
|
+
await vault.perform("set_candidate_size", {
|
|
41
|
+
candidate_size: candidateSize
|
|
42
|
+
})
|
|
43
|
+
}
|
|
39
44
|
return { home, kernel, vault }
|
|
40
45
|
}
|
|
41
46
|
|
|
@@ -175,15 +180,142 @@ describe("Save Space engine", () => {
|
|
|
175
180
|
await close(vault)
|
|
176
181
|
})
|
|
177
182
|
|
|
178
|
-
test("
|
|
179
|
-
const { vault } = await makeVault()
|
|
183
|
+
test("global and app minimum sizes persist and drive their own scans", async () => {
|
|
184
|
+
const { home, vault } = await makeVault()
|
|
185
|
+
const app = "minimum-size-app"
|
|
186
|
+
const appRoot = path.join(home, "api", app)
|
|
187
|
+
const appScope = `app:${app}`
|
|
188
|
+
const globalMinimum = CANDIDATE_SIZE_OPTIONS[2]
|
|
189
|
+
const appMinimum = CANDIDATE_SIZE_OPTIONS[1]
|
|
190
|
+
await fs.promises.mkdir(appRoot)
|
|
191
|
+
await vault.openWorkspace()
|
|
192
|
+
|
|
193
|
+
assert.equal((await vault.perform("set_candidate_size", {
|
|
194
|
+
candidate_size: globalMinimum
|
|
195
|
+
})).candidate_min_bytes, globalMinimum)
|
|
196
|
+
assert.equal((await vault.perform("set_candidate_size", {
|
|
197
|
+
scope_id: appScope,
|
|
198
|
+
candidate_size: appMinimum
|
|
199
|
+
})).candidate_min_bytes, appMinimum)
|
|
200
|
+
assert.equal((await vault.status()).candidate_min_bytes, globalMinimum)
|
|
201
|
+
assert.equal((await vault.status(appScope)).candidate_min_bytes, appMinimum)
|
|
202
|
+
|
|
203
|
+
const starts = []
|
|
204
|
+
vault.globalScanReady = async () => true
|
|
205
|
+
vault.startScan = (scopeId, threshold) => {
|
|
206
|
+
starts.push({ scope_id: scopeId, threshold })
|
|
207
|
+
return { started: true }
|
|
208
|
+
}
|
|
209
|
+
assert.deepEqual(await vault.perform("scan", {
|
|
210
|
+
candidate_size: CANDIDATE_SIZE_OPTIONS[0]
|
|
211
|
+
}), { started: true })
|
|
212
|
+
assert.deepEqual(await vault.perform("scan", {
|
|
213
|
+
scope_id: appScope,
|
|
214
|
+
candidate_size: CANDIDATE_SIZE_OPTIONS[0]
|
|
215
|
+
}), { started: true })
|
|
216
|
+
assert.deepEqual(starts, [
|
|
217
|
+
{ scope_id: null, threshold: globalMinimum },
|
|
218
|
+
{ scope_id: appScope, threshold: appMinimum }
|
|
219
|
+
])
|
|
220
|
+
let folderThreshold = null
|
|
221
|
+
vault.startFolderDiscovery = (folderPath, threshold) => {
|
|
222
|
+
folderThreshold = threshold
|
|
223
|
+
return { started: true }
|
|
224
|
+
}
|
|
225
|
+
assert.deepEqual(await vault.perform("find_folders", {
|
|
226
|
+
path: home,
|
|
227
|
+
candidate_size: CANDIDATE_SIZE_OPTIONS[0]
|
|
228
|
+
}), { started: true })
|
|
229
|
+
assert.equal(folderThreshold, globalMinimum)
|
|
230
|
+
assert.equal((await vault.status()).candidate_min_bytes, globalMinimum)
|
|
231
|
+
assert.equal((await vault.status(appScope)).candidate_min_bytes, appMinimum)
|
|
232
|
+
assert.equal((await vault.automaticScans.candidatePolicy(app)).threshold,
|
|
233
|
+
appMinimum)
|
|
234
|
+
|
|
235
|
+
await close(vault)
|
|
236
|
+
const reopened = new Vault({ homedir: home, platform: process.platform })
|
|
237
|
+
await reopened.init()
|
|
238
|
+
assert.equal((await reopened.status()).candidate_min_bytes, globalMinimum)
|
|
239
|
+
assert.equal((await reopened.status(appScope)).candidate_min_bytes,
|
|
240
|
+
appMinimum)
|
|
241
|
+
await close(reopened)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test("reading a default minimum size does not persist it", async () => {
|
|
245
|
+
const { vault } = await makeVault({ candidateSize: null })
|
|
246
|
+
|
|
247
|
+
assert.equal(await vault.registry.scanSetting(), null)
|
|
248
|
+
assert.equal((await vault.status()).candidate_min_bytes, SIZE_THRESHOLD)
|
|
249
|
+
assert.equal(await vault.registry.scanSetting(), null)
|
|
250
|
+
|
|
251
|
+
await close(vault)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
test("minimum size settings reject coerced values", async () => {
|
|
255
|
+
const { vault } = await makeVault({ candidateSize: null })
|
|
256
|
+
|
|
257
|
+
for (const candidateSize of [
|
|
258
|
+
null,
|
|
259
|
+
"",
|
|
260
|
+
String(CANDIDATE_SIZE_OPTIONS[1])
|
|
261
|
+
]) {
|
|
262
|
+
assert.match((await vault.perform("set_candidate_size", {
|
|
263
|
+
candidate_size: candidateSize
|
|
264
|
+
})).error, /valid minimum file size/i)
|
|
265
|
+
}
|
|
266
|
+
assert.equal(await vault.registry.scanSetting(), null)
|
|
267
|
+
|
|
268
|
+
await close(vault)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test("minimum size settings reject malformed scopes", async () => {
|
|
272
|
+
const { vault } = await makeVault({ candidateSize: null })
|
|
273
|
+
|
|
274
|
+
const result = await vault.perform("set_candidate_size", {
|
|
275
|
+
scope_id: 123,
|
|
276
|
+
candidate_size: CANDIDATE_SIZE_OPTIONS[1]
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
assert.match(result.error, /valid scan scope/i)
|
|
280
|
+
assert.equal(await vault.registry.scanSetting(), null)
|
|
281
|
+
|
|
282
|
+
await close(vault)
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test("scan history does not become a minimum size setting", async () => {
|
|
286
|
+
const home = await makeHome()
|
|
287
|
+
const vault = new Vault({ homedir: home, platform: process.platform })
|
|
288
|
+
await vault.init()
|
|
289
|
+
await vault.perform("set_candidate_size", {
|
|
290
|
+
candidate_size: CANDIDATE_SIZE_OPTIONS[2]
|
|
291
|
+
})
|
|
292
|
+
assert.equal((await vault.perform("scan")).started, true)
|
|
293
|
+
await waitForEngine(() => !vault.scanPromise &&
|
|
294
|
+
!vault.scanCompletionPromise)
|
|
295
|
+
await close(vault)
|
|
296
|
+
|
|
297
|
+
const database = new Database(path.join(home, "vault", "registry.sqlite3"))
|
|
298
|
+
database.exec("DROP TABLE minimum_size_settings")
|
|
299
|
+
database.close()
|
|
300
|
+
|
|
301
|
+
const reopened = new Vault({ homedir: home, platform: process.platform })
|
|
302
|
+
await reopened.init()
|
|
303
|
+
assert.equal((await reopened.status()).candidate_min_bytes,
|
|
304
|
+
SIZE_THRESHOLD)
|
|
305
|
+
assert.equal(await reopened.registry.scanSetting(), null)
|
|
306
|
+
await close(reopened)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
test("the saved global minimum is reused by unscoped policy reads", async () => {
|
|
310
|
+
const { vault } = await makeVault({ candidateSize: null })
|
|
180
311
|
const selected = CANDIDATE_SIZE_OPTIONS[2]
|
|
181
312
|
|
|
182
313
|
assert.equal((await vault.status()).global_candidate_min_bytes,
|
|
183
314
|
SIZE_THRESHOLD)
|
|
184
|
-
|
|
315
|
+
await vault.perform("set_candidate_size", {
|
|
185
316
|
candidate_size: selected
|
|
186
|
-
})
|
|
317
|
+
})
|
|
318
|
+
assert.deepEqual(await vault.perform("scan"), { started: true })
|
|
187
319
|
await waitForEngine(() => !vault.scanPromise &&
|
|
188
320
|
!vault.scanCompletionPromise)
|
|
189
321
|
|
|
@@ -994,6 +1126,9 @@ describe("Save Space engine", () => {
|
|
|
994
1126
|
crypto.randomBytes(512))
|
|
995
1127
|
vault.sizeThreshold = 0
|
|
996
1128
|
await vault.sweeper.scan()
|
|
1129
|
+
await vault.perform("set_candidate_size", {
|
|
1130
|
+
candidate_size: selectedThreshold
|
|
1131
|
+
})
|
|
997
1132
|
|
|
998
1133
|
const stageDiscoveryFiles = vault.registry.stageFolderDiscoveryFiles
|
|
999
1134
|
.bind(vault.registry)
|
|
@@ -1039,18 +1174,22 @@ describe("Save Space engine", () => {
|
|
|
1039
1174
|
await close(vault)
|
|
1040
1175
|
})
|
|
1041
1176
|
|
|
1042
|
-
test("Find folders
|
|
1177
|
+
test("Find folders ignores request thresholds and uses the saved setting", async () => {
|
|
1043
1178
|
const { vault } = await makeVault()
|
|
1044
1179
|
const outside = await makeOutside()
|
|
1180
|
+
const thresholds = []
|
|
1181
|
+
vault.startFolderDiscovery = (folderPath, threshold) => {
|
|
1182
|
+
thresholds.push(threshold)
|
|
1183
|
+
return { started: true }
|
|
1184
|
+
}
|
|
1045
1185
|
|
|
1046
|
-
assert.
|
|
1047
|
-
path: outside
|
|
1048
|
-
})).error, /valid minimum file size/i)
|
|
1049
|
-
assert.match((await vault.perform("find_folders", {
|
|
1186
|
+
assert.deepEqual(await vault.perform("find_folders", {
|
|
1050
1187
|
path: outside,
|
|
1051
1188
|
candidate_size: 123
|
|
1052
|
-
})
|
|
1053
|
-
assert.
|
|
1189
|
+
}), { started: true })
|
|
1190
|
+
assert.deepEqual(thresholds, [CANDIDATE_SIZE_OPTIONS[0]])
|
|
1191
|
+
assert.equal((await vault.status()).candidate_min_bytes,
|
|
1192
|
+
CANDIDATE_SIZE_OPTIONS[0])
|
|
1054
1193
|
await close(vault)
|
|
1055
1194
|
})
|
|
1056
1195
|
|
package/test/vault-ui.test.js
CHANGED
|
@@ -414,7 +414,7 @@ const makePage = async (status, options = {}) => {
|
|
|
414
414
|
const childManifest = parent
|
|
415
415
|
? folderDiscoveryChildren[parent]
|
|
416
416
|
: null
|
|
417
|
-
const response = parent
|
|
417
|
+
const response = await (parent
|
|
418
418
|
? typeof childManifest === "function"
|
|
419
419
|
? childManifest(childPage)
|
|
420
420
|
: childManifest || {
|
|
@@ -425,7 +425,7 @@ const makePage = async (status, options = {}) => {
|
|
|
425
425
|
page_size: 500,
|
|
426
426
|
pages: 1
|
|
427
427
|
}
|
|
428
|
-
: typeof status === "function" ? status(url) : status
|
|
428
|
+
: typeof status === "function" ? status(url) : status)
|
|
429
429
|
if (options.deferAutomaticStatus &&
|
|
430
430
|
url === "/info/vault/automatic-scans") {
|
|
431
431
|
await deferredAutomaticStatus
|
|
@@ -778,7 +778,7 @@ describe("Save Space interface", () => {
|
|
|
778
778
|
request.action === "scan"))
|
|
779
779
|
const scan = requests.find((request) => request.action === "scan")
|
|
780
780
|
assert.equal(scan.scope_id, null)
|
|
781
|
-
assert.equal(
|
|
781
|
+
assert.equal("candidate_size" in scan, false)
|
|
782
782
|
await settle()
|
|
783
783
|
dom.window.close()
|
|
784
784
|
})
|
|
@@ -866,12 +866,13 @@ describe("Save Space interface", () => {
|
|
|
866
866
|
dom.window.close()
|
|
867
867
|
})
|
|
868
868
|
|
|
869
|
-
test("
|
|
869
|
+
test("a scan relies on the saved scope setting, not browser storage", async () => {
|
|
870
870
|
const candidateBase = process.platform === "win32" ? 1024 : 1000
|
|
871
|
-
const
|
|
871
|
+
const saved = 50 * candidateBase ** 2
|
|
872
872
|
const stored = 10 * candidateBase ** 2
|
|
873
873
|
const { dom, requests } = await makePage(fixture([], {
|
|
874
|
-
|
|
874
|
+
candidate_min_bytes: saved,
|
|
875
|
+
global_candidate_min_bytes: 100 * candidateBase ** 2
|
|
875
876
|
}), {
|
|
876
877
|
storedCandidateSize: stored
|
|
877
878
|
})
|
|
@@ -882,13 +883,39 @@ describe("Save Space interface", () => {
|
|
|
882
883
|
document.getElementById("btn-scan").click()
|
|
883
884
|
await waitFor(() => requests.some((request) =>
|
|
884
885
|
request.action === "scan"))
|
|
885
|
-
assert.equal(requests.find((request) =>
|
|
886
|
-
request.action === "scan")
|
|
886
|
+
assert.equal("candidate_size" in requests.find((request) =>
|
|
887
|
+
request.action === "scan"), false)
|
|
887
888
|
|
|
888
889
|
await settle()
|
|
889
890
|
dom.window.close()
|
|
890
891
|
})
|
|
891
892
|
|
|
893
|
+
test("an app displays and immediately saves its own minimum size", async () => {
|
|
894
|
+
const candidateBase = process.platform === "win32" ? 1024 : 1000
|
|
895
|
+
const appMinimum = candidateBase ** 2
|
|
896
|
+
const globalMinimum = 10 * candidateBase ** 2
|
|
897
|
+
const selected = 50 * candidateBase ** 2
|
|
898
|
+
const { dom, requests } = await makePage(fixture([], {
|
|
899
|
+
candidate_min_bytes: appMinimum,
|
|
900
|
+
global_candidate_min_bytes: globalMinimum
|
|
901
|
+
}), { appMode: true })
|
|
902
|
+
const document = dom.window.document
|
|
903
|
+
const selector = document.getElementById("vault-candidate-size")
|
|
904
|
+
|
|
905
|
+
assert.equal(selector.value, String(appMinimum))
|
|
906
|
+
selector.value = String(selected)
|
|
907
|
+
selector.dispatchEvent(new dom.window.Event("change", { bubbles: true }))
|
|
908
|
+
await waitFor(() => requests.some((request) =>
|
|
909
|
+
request.action === "set_candidate_size"))
|
|
910
|
+
const request = requests.find((entry) =>
|
|
911
|
+
entry.action === "set_candidate_size")
|
|
912
|
+
assert.equal(request.scope_id, "app:app")
|
|
913
|
+
assert.equal(request.candidate_size, selected)
|
|
914
|
+
assert.equal(selector.value, String(selected))
|
|
915
|
+
|
|
916
|
+
dom.window.close()
|
|
917
|
+
})
|
|
918
|
+
|
|
892
919
|
test("a threshold chosen before initial status remains selected", async () => {
|
|
893
920
|
const candidateBase = process.platform === "win32" ? 1024 : 1000
|
|
894
921
|
const published = 50 * candidateBase ** 2
|
|
@@ -914,13 +941,87 @@ describe("Save Space interface", () => {
|
|
|
914
941
|
document.getElementById("btn-scan").click()
|
|
915
942
|
await waitFor(() => requests.some((request) =>
|
|
916
943
|
request.action === "scan"))
|
|
917
|
-
|
|
918
|
-
request.action === "
|
|
944
|
+
const savedIndex = requests.findIndex((request) =>
|
|
945
|
+
request.action === "set_candidate_size")
|
|
946
|
+
const scanIndex = requests.findIndex((request) =>
|
|
947
|
+
request.action === "scan")
|
|
948
|
+
assert.ok(savedIndex >= 0 && savedIndex < scanIndex)
|
|
949
|
+
assert.equal("candidate_size" in requests[scanIndex], false)
|
|
919
950
|
|
|
920
951
|
await settle()
|
|
921
952
|
dom.window.close()
|
|
922
953
|
})
|
|
923
954
|
|
|
955
|
+
test("a rejected minimum size save blocks a pending scan and restores the saved size", async () => {
|
|
956
|
+
const candidateBase = process.platform === "win32" ? 1024 : 1000
|
|
957
|
+
const saved = 50 * candidateBase ** 2
|
|
958
|
+
const selected = 10 * candidateBase ** 2
|
|
959
|
+
const { dom, requests } = await makePage(fixture([], {
|
|
960
|
+
candidate_min_bytes: saved,
|
|
961
|
+
global_candidate_min_bytes: saved
|
|
962
|
+
}), {
|
|
963
|
+
actionResults: {
|
|
964
|
+
set_candidate_size: { error: "Could not save minimum file size." }
|
|
965
|
+
}
|
|
966
|
+
})
|
|
967
|
+
const document = dom.window.document
|
|
968
|
+
|
|
969
|
+
document.querySelector(`[data-candidate-size="${selected}"]`).click()
|
|
970
|
+
document.getElementById("btn-scan").click()
|
|
971
|
+
|
|
972
|
+
await waitFor(() => /50 MB\+/.test(document.getElementById(
|
|
973
|
+
"vault-scan-size-label").textContent) &&
|
|
974
|
+
/could not save minimum file size/i.test(
|
|
975
|
+
document.getElementById("vault-feedback").textContent))
|
|
976
|
+
assert.equal(requests.some((request) => request.action === "scan"), false)
|
|
977
|
+
|
|
978
|
+
dom.window.close()
|
|
979
|
+
})
|
|
980
|
+
|
|
981
|
+
test("a stale minimum size failure does not replace a newer successful selection", async () => {
|
|
982
|
+
const candidateBase = process.platform === "win32" ? 1024 : 1000
|
|
983
|
+
const first = 10 * candidateBase ** 2
|
|
984
|
+
const second = 50 * candidateBase ** 2
|
|
985
|
+
const currentStatus = fixture([], {
|
|
986
|
+
candidate_min_bytes: 100 * candidateBase ** 2,
|
|
987
|
+
global_candidate_min_bytes: 100 * candidateBase ** 2
|
|
988
|
+
})
|
|
989
|
+
let statusCount = 0
|
|
990
|
+
let releaseRecovery
|
|
991
|
+
const recovery = new Promise((resolve) => { releaseRecovery = resolve })
|
|
992
|
+
let saveCount = 0
|
|
993
|
+
const actionResults = {}
|
|
994
|
+
Object.defineProperty(actionResults, "set_candidate_size", {
|
|
995
|
+
get() {
|
|
996
|
+
saveCount += 1
|
|
997
|
+
return saveCount === 1
|
|
998
|
+
? { error: "Could not save minimum file size." }
|
|
999
|
+
: {}
|
|
1000
|
+
}
|
|
1001
|
+
})
|
|
1002
|
+
const { dom, requests } = await makePage(async () => {
|
|
1003
|
+
statusCount += 1
|
|
1004
|
+
if (statusCount === 2) await recovery
|
|
1005
|
+
return currentStatus
|
|
1006
|
+
}, { actionResults })
|
|
1007
|
+
const document = dom.window.document
|
|
1008
|
+
|
|
1009
|
+
document.querySelector(`[data-candidate-size="${first}"]`).click()
|
|
1010
|
+
await waitFor(() => statusCount === 2)
|
|
1011
|
+
document.querySelector(`[data-candidate-size="${second}"]`).click()
|
|
1012
|
+
releaseRecovery()
|
|
1013
|
+
|
|
1014
|
+
await waitFor(() => requests.filter((request) =>
|
|
1015
|
+
request.action === "set_candidate_size").length === 2)
|
|
1016
|
+
await settle()
|
|
1017
|
+
assert.match(document.getElementById("vault-scan-size-label").textContent,
|
|
1018
|
+
/50 MB\+/)
|
|
1019
|
+
assert.doesNotMatch(document.getElementById("vault-feedback").textContent,
|
|
1020
|
+
/could not save minimum file size/i)
|
|
1021
|
+
|
|
1022
|
+
dom.window.close()
|
|
1023
|
+
})
|
|
1024
|
+
|
|
924
1025
|
test("a runtime write denial is reported as cannot deduplicate", async () => {
|
|
925
1026
|
const duplicate = item({
|
|
926
1027
|
status: "duplicate",
|
|
@@ -1090,15 +1191,49 @@ describe("Save Space interface", () => {
|
|
|
1090
1191
|
request.action === "find_folders"))
|
|
1091
1192
|
assert.equal(requests.find((entry) =>
|
|
1092
1193
|
entry.action === "find_folders").path, "/Users/test")
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1194
|
+
const savedIndex = requests.findIndex((request) =>
|
|
1195
|
+
request.action === "set_candidate_size")
|
|
1196
|
+
const findIndex = requests.findIndex((request) =>
|
|
1197
|
+
request.action === "find_folders")
|
|
1198
|
+
assert.ok(savedIndex >= 0 && savedIndex < findIndex)
|
|
1199
|
+
assert.equal("candidate_size" in requests[findIndex], false)
|
|
1096
1200
|
assert.equal(pickerRequests.length, 0)
|
|
1097
1201
|
await waitFor(() => document.getElementById(
|
|
1098
1202
|
"vault-find-overlay").hidden)
|
|
1099
1203
|
dom.window.close()
|
|
1100
1204
|
})
|
|
1101
1205
|
|
|
1206
|
+
test("a rejected minimum size save blocks pending folder discovery", async () => {
|
|
1207
|
+
const candidateBase = process.platform === "win32" ? 1024 : 1000
|
|
1208
|
+
const saved = 100 * candidateBase ** 2
|
|
1209
|
+
const selected = 50 * candidateBase ** 2
|
|
1210
|
+
const { dom, requests } = await makePage(fixture([item()], {
|
|
1211
|
+
candidate_min_bytes: saved,
|
|
1212
|
+
global_candidate_min_bytes: saved
|
|
1213
|
+
}), {
|
|
1214
|
+
actionResults: {
|
|
1215
|
+
set_candidate_size: { error: "Could not save minimum file size." }
|
|
1216
|
+
}
|
|
1217
|
+
})
|
|
1218
|
+
const document = dom.window.document
|
|
1219
|
+
|
|
1220
|
+
document.getElementById("btn-find-folders").click()
|
|
1221
|
+
await waitFor(() => document.querySelector("[data-find-home-folder]"))
|
|
1222
|
+
const selector = document.getElementById("vault-find-candidate-size")
|
|
1223
|
+
selector.value = String(selected)
|
|
1224
|
+
selector.dispatchEvent(new dom.window.Event("change", { bubbles: true }))
|
|
1225
|
+
document.querySelector("[data-find-home-folder]").click()
|
|
1226
|
+
|
|
1227
|
+
await waitFor(() => document.getElementById(
|
|
1228
|
+
"vault-find-candidate-size").value === String(saved) &&
|
|
1229
|
+
/could not save minimum file size/i.test(
|
|
1230
|
+
document.getElementById("vault-feedback").textContent))
|
|
1231
|
+
assert.equal(requests.some((request) =>
|
|
1232
|
+
request.action === "find_folders"), false)
|
|
1233
|
+
|
|
1234
|
+
dom.window.close()
|
|
1235
|
+
})
|
|
1236
|
+
|
|
1102
1237
|
test("Find folders explains when a lower threshold needs a global scan", async () => {
|
|
1103
1238
|
const message = "Run a global scan with this minimum file size before searching folders."
|
|
1104
1239
|
const { dom, requests } = await makePage(fixture([item()]), {
|
|
@@ -1121,9 +1256,8 @@ describe("Save Space interface", () => {
|
|
|
1121
1256
|
assert.equal(document.querySelector(
|
|
1122
1257
|
".vault-find-partial[role='alert']").textContent.trim(), message)
|
|
1123
1258
|
assert.ok(document.querySelector("[data-find-home-folder]"))
|
|
1124
|
-
assert.equal(requests.find((request) =>
|
|
1125
|
-
request.action === "find_folders")
|
|
1126
|
-
50 * candidateBase ** 2)
|
|
1259
|
+
assert.equal("candidate_size" in requests.find((request) =>
|
|
1260
|
+
request.action === "find_folders"), false)
|
|
1127
1261
|
dom.window.close()
|
|
1128
1262
|
})
|
|
1129
1263
|
|