Reads and edits BWF metadata for production sound. One source tree builds a single self-contained page and a native Tauri app with a Rust audio engine and WAV writer. Around 370 checks across seven test suites. First commit of the existing state, so that from here every change can be seen and undone.
247 lines
9.5 KiB
JavaScript
247 lines
9.5 KiB
JavaScript
/**
|
|
* Reopening the last folder on launch.
|
|
*
|
|
* Three cases matter: the folder is remembered when you open one, it comes
|
|
* back by itself next launch, and a folder that has since moved or been
|
|
* ejected leaves the launch screen up rather than an error — with the stale
|
|
* path dropped so it can't fail twice.
|
|
*
|
|
* Run: npm i jsdom && node build/test-restore.js
|
|
*/
|
|
|
|
const fs = require("fs");
|
|
const os = require("os");
|
|
const path = require("path");
|
|
const assert = require("assert");
|
|
const { JSDOM, VirtualConsole } = require("jsdom");
|
|
const { build } = require("./make-sample.js");
|
|
|
|
const INDEX = path.join(__dirname, "..", "mac-app", "dist", "index.html");
|
|
const STORAGE_KEY = "bwfa_last_folder";
|
|
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "bwf-restore-"));
|
|
const card = path.join(root, "Day 14 PR-2");
|
|
fs.mkdirSync(card, { recursive: true });
|
|
["A001.wav", "A002.wav"].forEach((name, i) =>
|
|
fs.writeFileSync(path.join(card, name), build({ scene: "1", take: i + 1 })));
|
|
|
|
const results = [];
|
|
function check(name, fn) {
|
|
try {
|
|
fn();
|
|
results.push(["PASS", name]);
|
|
} catch (e) {
|
|
results.push(["FAIL", name + " — " + e.message]);
|
|
}
|
|
}
|
|
|
|
function waitFor(fn, label, timeout = 10000) {
|
|
return new Promise((resolve, reject) => {
|
|
const started = Date.now();
|
|
(function poll() {
|
|
let value;
|
|
try {
|
|
value = fn();
|
|
} catch (e) {
|
|
return reject(e);
|
|
}
|
|
if (value) return resolve(value);
|
|
if (Date.now() - started > timeout) return reject(new Error("timed out waiting for " + label));
|
|
setTimeout(poll, 30);
|
|
})();
|
|
});
|
|
}
|
|
|
|
/** A window, with the Rust commands mirrored and a note of what was asked. */
|
|
function launch({ remembered, dialogAnswers = [] } = {}) {
|
|
const asked = { dialog: 0 };
|
|
const dialogQueue = dialogAnswers.slice();
|
|
|
|
const dom = new JSDOM(fs.readFileSync(INDEX, "utf8"), {
|
|
runScripts: "dangerously",
|
|
url: "http://tauri.localhost/",
|
|
pretendToBeVisual: true,
|
|
virtualConsole: new VirtualConsole(),
|
|
beforeParse(win) {
|
|
win.HTMLCanvasElement.prototype.getContext = () => new Proxy({}, {
|
|
get: (target, prop) => (prop === "canvas" ? null : () => {}),
|
|
set: () => true,
|
|
});
|
|
win.URL.createObjectURL = () => "blob:stub";
|
|
win.URL.revokeObjectURL = () => {};
|
|
if (remembered !== undefined) {
|
|
win.localStorage.setItem(STORAGE_KEY, remembered);
|
|
}
|
|
|
|
const toRealm = (buffer) => {
|
|
const view = new win.Uint8Array(buffer.length);
|
|
view.set(buffer);
|
|
return view.buffer;
|
|
};
|
|
|
|
win.__TAURI__ = {
|
|
event: { listen: () => Promise.resolve(() => {}) },
|
|
core: {
|
|
invoke(command, payload) {
|
|
if (command === "plugin:dialog|open") {
|
|
asked.dialog++;
|
|
const next = dialogQueue.shift();
|
|
return Promise.resolve(next === undefined ? null : next);
|
|
}
|
|
if (command === "bwf_list_dir") {
|
|
// Rejects for a folder that isn't there, exactly as the
|
|
// Rust command does.
|
|
if (!fs.existsSync(payload.path)) {
|
|
return Promise.reject(new Error(payload.path + ": No such file or directory"));
|
|
}
|
|
return Promise.resolve(fs.readdirSync(payload.path).sort().map((name) => ({
|
|
name,
|
|
path: path.join(payload.path, name),
|
|
kind: fs.statSync(path.join(payload.path, name)).isDirectory() ? "directory" : "file",
|
|
})));
|
|
}
|
|
if (command === "bwf_stat") {
|
|
const stat = fs.statSync(payload.path);
|
|
return Promise.resolve({
|
|
path: payload.path,
|
|
relativePath: path.basename(payload.path),
|
|
name: path.basename(payload.path),
|
|
size: stat.size,
|
|
lastModified: Math.floor(stat.mtimeMs),
|
|
});
|
|
}
|
|
if (command === "bwf_read_range") {
|
|
const size = fs.statSync(payload.path).size;
|
|
if (payload.offset >= size || !payload.length) return Promise.resolve(toRealm(Buffer.alloc(0)));
|
|
const take = Math.min(payload.length, size - payload.offset);
|
|
const fd = fs.openSync(payload.path, "r");
|
|
const buffer = Buffer.alloc(take);
|
|
fs.readSync(fd, buffer, 0, take, payload.offset);
|
|
fs.closeSync(fd);
|
|
return Promise.resolve(toRealm(buffer));
|
|
}
|
|
if (command === "bwf_read_all") {
|
|
return Promise.resolve(toRealm(fs.readFileSync(payload.path)));
|
|
}
|
|
return Promise.reject(new Error("unexpected command " + command));
|
|
},
|
|
},
|
|
};
|
|
},
|
|
});
|
|
|
|
return new Promise((resolve) => {
|
|
dom.window.addEventListener("load", () => resolve({ dom, window: dom.window, asked }));
|
|
});
|
|
}
|
|
|
|
const rowsIn = (doc) => Array.from(doc.querySelectorAll("[data-bwfa-table-body] tr"));
|
|
|
|
(async () => {
|
|
/* --- opening a folder is remembered --- */
|
|
|
|
const first = await launch({ dialogAnswers: [card] });
|
|
first.window.document.querySelector("[data-bwfa-edit-folder]")
|
|
.dispatchEvent(new first.window.MouseEvent("click", { bubbles: true }));
|
|
await waitFor(() => rowsIn(first.window.document).length === 2, "first open");
|
|
|
|
check("opening a folder remembers it", () =>
|
|
assert.strictEqual(first.window.localStorage.getItem(STORAGE_KEY), card,
|
|
"stored: " + first.window.localStorage.getItem(STORAGE_KEY)));
|
|
first.window.close();
|
|
|
|
/* --- next launch reopens it, with no dialog --- */
|
|
|
|
const again = await launch({ remembered: card });
|
|
await waitFor(() => rowsIn(again.window.document).length === 2, "reopen on launch");
|
|
const doc = again.window.document;
|
|
|
|
check("the folder comes back on the next launch", () => {
|
|
assert.strictEqual(rowsIn(doc).length, 2, "got " + rowsIn(doc).length + " rows");
|
|
assert.strictEqual(again.asked.dialog, 0, "it opened a file dialog instead of restoring");
|
|
assert.strictEqual(doc.querySelector("[data-bwfa-current-folder]").textContent, "Day 14 PR-2");
|
|
assert(doc.querySelector("[data-bwfa-app]").classList.contains("has-folder"),
|
|
"still showing the launch screen");
|
|
});
|
|
|
|
check("it comes back read-write, not read-only", () =>
|
|
assert.strictEqual(doc.querySelector("[data-bwfa-editing-note]").hidden, false,
|
|
"restored without edit mode, so saving would be impossible"));
|
|
again.window.close();
|
|
|
|
/* --- a folder that has gone leaves the launch screen up --- */
|
|
|
|
const missing = path.join(root, "Ejected Card");
|
|
const gone = await launch({ remembered: missing });
|
|
// Nothing to wait for, so give the check a beat to have happened.
|
|
await new Promise((r) => setTimeout(r, 300));
|
|
const goneDoc = gone.window.document;
|
|
|
|
check("a folder that isn't there leaves the launch screen alone", () => {
|
|
assert.strictEqual(rowsIn(goneDoc).length, 0, "it loaded something");
|
|
assert.strictEqual(goneDoc.querySelector("[data-bwfa-app]").classList.contains("has-folder"), false,
|
|
"the app left its launch state for a folder that doesn't exist");
|
|
assert.strictEqual(gone.asked.dialog, 0, "it prompted on launch, which is not the ask");
|
|
assert.strictEqual(goneDoc.querySelector("[data-bwfa-status]").textContent.trim(), "",
|
|
"an error was left on screen: " + goneDoc.querySelector("[data-bwfa-status]").textContent);
|
|
});
|
|
|
|
check("the stale path is dropped so it can't fail twice", () =>
|
|
assert.strictEqual(gone.window.localStorage.getItem(STORAGE_KEY), null,
|
|
"still remembered: " + gone.window.localStorage.getItem(STORAGE_KEY)));
|
|
gone.window.close();
|
|
|
|
/* --- eject puts the app back to a first run --- */
|
|
|
|
const open = await launch({ dialogAnswers: [card] });
|
|
const openDoc = open.window.document;
|
|
openDoc.querySelector("[data-bwfa-edit-folder]")
|
|
.dispatchEvent(new open.window.MouseEvent("click", { bubbles: true }));
|
|
await waitFor(() => rowsIn(openDoc).length === 2, "open before reset");
|
|
|
|
check("eject only offers itself once a folder is open", () => {
|
|
const reset = openDoc.querySelector("[data-bwfa-reset]");
|
|
assert(reset, "no reset control in the markup");
|
|
assert.notStrictEqual(open.window.getComputedStyle(reset).display, "none",
|
|
"reset is hidden while a folder is open");
|
|
});
|
|
|
|
openDoc.querySelector("[data-bwfa-reset]")
|
|
.dispatchEvent(new open.window.MouseEvent("click", { bubbles: true }));
|
|
await waitFor(() => rowsIn(openDoc).length === 0, "reset to clear the table", 5000);
|
|
|
|
check("eject returns the app to its empty state", () => {
|
|
const app = openDoc.querySelector("[data-bwfa-app]");
|
|
assert.strictEqual(app.classList.contains("has-folder"), false, "still in folder mode");
|
|
assert.strictEqual(openDoc.querySelector("[data-bwfa-results]").hidden, true,
|
|
"the results panel survived");
|
|
assert.strictEqual(openDoc.querySelector("[data-bwfa-player]").hidden, true,
|
|
"the player survived");
|
|
assert.strictEqual(openDoc.querySelector("[data-bwfa-current-folder]").hidden, true,
|
|
"the folder name is still shown");
|
|
assert(/open folder/i.test(openDoc.querySelector("[data-bwfa-edit-folder]").textContent),
|
|
"the button still says Change Folder: " +
|
|
openDoc.querySelector("[data-bwfa-edit-folder]").textContent);
|
|
assert.strictEqual(openDoc.querySelector("[data-bwfa-status]").textContent.trim(), "",
|
|
"a status message was left behind");
|
|
assert.strictEqual(open.window.getComputedStyle(
|
|
openDoc.querySelector("[data-bwfa-reset]")).display, "none",
|
|
"reset is still offered with nothing open");
|
|
});
|
|
|
|
check("eject also forgets what to reopen", () =>
|
|
assert.strictEqual(open.window.localStorage.getItem(STORAGE_KEY), null,
|
|
"next launch would reopen " + open.window.localStorage.getItem(STORAGE_KEY)));
|
|
open.window.close();
|
|
|
|
console.log("");
|
|
results.forEach(([s, n]) => console.log((s === "PASS" ? " ok " : " FAIL") + " " + n));
|
|
const failed = results.filter(([s]) => s === "FAIL").length;
|
|
console.log("\n" + (results.length - failed) + "/" + results.length + " checks passed");
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
process.exit(failed ? 1 : 0);
|
|
})().catch((e) => {
|
|
console.error("harness error:", e);
|
|
process.exit(1);
|
|
});
|