mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 08:24:46 +00:00
fix(file-observer): track files announced by native change events
This commit is contained in:
@@ -9,66 +9,75 @@ import { createObserverPaths } from "./paths.js";
|
||||
|
||||
// Native watchers may coalesce file removals into change notifications. Keep the
|
||||
// filesystem real while controlling which notifications reach reconciliation.
|
||||
test("shallow parent scans retain nested change scopes", async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), "native-scopes-"));
|
||||
const paths = createObserverPaths(process.platform);
|
||||
const removed = [
|
||||
join(root, "root.txt"),
|
||||
join(root, "child", "child.txt"),
|
||||
join(root, "child", "deep", "deep.txt"),
|
||||
];
|
||||
await mkdir(join(root, "child", "deep"), { recursive: true });
|
||||
await Promise.all(removed.map((path) => writeFile(path, "before")));
|
||||
const events: FileChange[] = [];
|
||||
const notifications = new EventEmitter();
|
||||
let active = true;
|
||||
const observer = createFileObserver();
|
||||
const backend = createNativeRecursiveBackend(
|
||||
{
|
||||
root,
|
||||
metrics: observer.getDiagnostics(),
|
||||
isActive: () => active,
|
||||
isIgnored: () => false,
|
||||
isPathInside: paths.isInside,
|
||||
queueEvent: (type, path) => events.push({ type, path }),
|
||||
fail: (error) => {
|
||||
throw error;
|
||||
},
|
||||
},
|
||||
paths,
|
||||
(_root, listener) => {
|
||||
notifications.on("change", listener);
|
||||
return {
|
||||
close: () => {
|
||||
notifications.removeAllListeners();
|
||||
test.each(["existing", "new"])(
|
||||
"shallow scans recover coalesced deletes of %s files in nested scopes",
|
||||
async (population) => {
|
||||
const root = await mkdtemp(join(tmpdir(), "native-scopes-"));
|
||||
const paths = createObserverPaths(process.platform);
|
||||
const removed = [
|
||||
join(root, "root.txt"),
|
||||
join(root, "child", "child.txt"),
|
||||
join(root, "child", "deep", "deep.txt"),
|
||||
];
|
||||
await mkdir(join(root, "child", "deep"), { recursive: true });
|
||||
if (population === "existing") {
|
||||
await Promise.all(removed.map((path) => writeFile(path, "before")));
|
||||
}
|
||||
const events: FileChange[] = [];
|
||||
const notifications = new EventEmitter();
|
||||
let active = true;
|
||||
const observer = createFileObserver();
|
||||
const backend = createNativeRecursiveBackend(
|
||||
{
|
||||
root,
|
||||
metrics: observer.getDiagnostics(),
|
||||
isActive: () => active,
|
||||
isIgnored: () => false,
|
||||
isPathInside: paths.isInside,
|
||||
queueEvent: (type, path) => events.push({ type, path }),
|
||||
fail: (error) => {
|
||||
throw error;
|
||||
},
|
||||
on: (event, onError) => notifications.on(event, onError),
|
||||
};
|
||||
},
|
||||
);
|
||||
try {
|
||||
await backend.start();
|
||||
await Promise.all(removed.map((path) => rm(path)));
|
||||
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "performance"] });
|
||||
for (const path of removed) notifications.emit("change", "change", path);
|
||||
await vi.advanceTimersByTimeAsync(8_000);
|
||||
vi.useRealTimers();
|
||||
await expect
|
||||
.poll(() =>
|
||||
events
|
||||
.filter((event) => event.type === "delete")
|
||||
.map((event) => event.path)
|
||||
.sort(),
|
||||
)
|
||||
.toEqual([...removed].sort());
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
active = false;
|
||||
await backend.close();
|
||||
await observer.close();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
},
|
||||
paths,
|
||||
(_root, listener) => {
|
||||
notifications.on("change", listener);
|
||||
return {
|
||||
close: () => {
|
||||
notifications.removeAllListeners();
|
||||
},
|
||||
on: (event, onError) => notifications.on(event, onError),
|
||||
};
|
||||
},
|
||||
);
|
||||
try {
|
||||
await backend.start();
|
||||
if (population === "new") {
|
||||
await Promise.all(removed.map((path) => writeFile(path, "before")));
|
||||
for (const path of removed) notifications.emit("change", "change", path);
|
||||
}
|
||||
await Promise.all(removed.map((path) => rm(path)));
|
||||
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "performance"] });
|
||||
for (const path of removed) notifications.emit("change", "change", path);
|
||||
await vi.advanceTimersByTimeAsync(8_000);
|
||||
vi.useRealTimers();
|
||||
await expect
|
||||
.poll(() =>
|
||||
events
|
||||
.filter((event) => event.type === "delete")
|
||||
.map((event) => event.path)
|
||||
.sort(),
|
||||
)
|
||||
.toEqual([...removed].sort());
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
active = false;
|
||||
await backend.close();
|
||||
await observer.close();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test("a file announced only as changed remains visible to coalesced deletion scans", async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), "native-change-only-"));
|
||||
|
||||
Reference in New Issue
Block a user