373 lines
13 KiB
JavaScript
373 lines
13 KiB
JavaScript
'use strict';
|
|
|
|
// This script depends on the following scripts:
|
|
// resources/test-helpers.js
|
|
// resources/collecting-file-system-observer.js
|
|
// resources/change-observer-scope-test.js
|
|
// script-tests/FileSystemObserver-writable-file-stream.js
|
|
|
|
promise_test(async t => {
|
|
try {
|
|
const observer = new FileSystemObserver(() => {});
|
|
} catch {
|
|
assert_unreached();
|
|
}
|
|
}, 'Creating a FileSystemObserver from a supported global succeeds');
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const observer = new FileSystemObserver(() => {});
|
|
try {
|
|
observer.unobserve(root_dir);
|
|
} catch {
|
|
assert_unreached();
|
|
}
|
|
}, 'Calling unobserve() without a corresponding observe() shouldn\'t throw');
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const observer = new FileSystemObserver(() => {});
|
|
try {
|
|
observer.unobserve(root_dir);
|
|
observer.unobserve(root_dir);
|
|
} catch {
|
|
assert_unreached();
|
|
}
|
|
}, 'unobserve() is idempotent');
|
|
|
|
promise_test(async t => {
|
|
const observer = new FileSystemObserver(() => {});
|
|
try {
|
|
observer.disconnect();
|
|
} catch {
|
|
assert_unreached();
|
|
}
|
|
}, 'Calling disconnect() without observing shouldn\'t throw');
|
|
|
|
promise_test(async t => {
|
|
const observer = new FileSystemObserver(() => {});
|
|
try {
|
|
observer.disconnect();
|
|
observer.disconnect();
|
|
} catch {
|
|
assert_unreached();
|
|
}
|
|
}, 'disconnect() is idempotent');
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const observer = new FileSystemObserver(() => {});
|
|
|
|
// Create a `FileSystemFileHandle` and delete its underlying file entry.
|
|
const file = await root_dir.getFileHandle(getUniqueName(), {create: true});
|
|
await file.remove();
|
|
|
|
await promise_rejects_dom(t, 'NotFoundError', observer.observe(file));
|
|
}, 'observe() fails when file does not exist');
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const observer = new FileSystemObserver(() => {});
|
|
|
|
// Create a `FileSystemDirectoryHandle` and delete its underlying file entry.
|
|
const dir =
|
|
await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
|
|
await dir.remove();
|
|
|
|
await promise_rejects_dom(t, 'NotFoundError', observer.observe(dir));
|
|
}, 'observe() fails when directory does not exist');
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const dir =
|
|
await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
|
|
|
|
const scope_test = new ScopeTest(t, dir);
|
|
const watched_handle = await scope_test.watched_handle();
|
|
|
|
for (const recursive of [false, true]) {
|
|
for await (const path of scope_test.in_scope_paths(recursive)) {
|
|
const observer = new CollectingFileSystemObserver(t, root_dir);
|
|
await observer.observe([watched_handle], {recursive});
|
|
|
|
// Create `file`.
|
|
const file = await path.createHandle();
|
|
|
|
// Expect one "appeared" event to happen on `file`.
|
|
const records = await observer.getRecords();
|
|
await assert_records_equal(
|
|
watched_handle, records,
|
|
[appearedEvent(file, path.relativePathComponents())]);
|
|
|
|
observer.disconnect();
|
|
}
|
|
}
|
|
}, 'Creating a file through FileSystemDirectoryHandle.getFileHandle is reported as an "appeared" event if in scope');
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const dir =
|
|
await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
|
|
|
|
const scope_test = new ScopeTest(t, dir);
|
|
const watched_handle = await scope_test.watched_handle();
|
|
|
|
for (const recursive of [false, true]) {
|
|
for await (const path of scope_test.in_scope_paths(recursive)) {
|
|
const file = await path.createHandle();
|
|
|
|
const observer = new CollectingFileSystemObserver(t, root_dir);
|
|
await observer.observe([watched_handle], {recursive});
|
|
|
|
// Remove `file`.
|
|
await file.remove();
|
|
|
|
// Expect one "disappeared" event to happen on `file`.
|
|
const records = await observer.getRecords();
|
|
await assert_records_equal(
|
|
watched_handle, records,
|
|
[disappearedEvent(path.relativePathComponents())]);
|
|
|
|
observer.disconnect();
|
|
}
|
|
}
|
|
}, 'Removing a file through FileSystemFileHandle.remove is reported as an "disappeared" event if in scope');
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const dir =
|
|
await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
|
|
|
|
const scope_test = new ScopeTest(t, dir);
|
|
const watched_handle = await scope_test.watched_handle();
|
|
|
|
for (const recursive of [false, true]) {
|
|
for await (const path of scope_test.out_of_scope_paths(recursive)) {
|
|
const observer = new CollectingFileSystemObserver(t, root_dir);
|
|
await observer.observe([watched_handle], {recursive});
|
|
|
|
// Create and remove `file`.
|
|
const file = await path.createHandle();
|
|
await file.remove();
|
|
|
|
// Expect the observer to receive no events.
|
|
const records = await observer.getRecords();
|
|
await assert_records_equal(watched_handle, records, []);
|
|
|
|
observer.disconnect();
|
|
}
|
|
}
|
|
}, 'Events outside the watch scope are not sent to the observer\'s callback');
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const dir =
|
|
await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
|
|
|
|
const scope_test = new ScopeTest(t, dir);
|
|
const watched_handle = await scope_test.watched_handle();
|
|
|
|
for (const recursive of [false, true]) {
|
|
for await (const src of scope_test.in_scope_paths(recursive)) {
|
|
for await (const dest of scope_test.in_scope_paths(recursive)) {
|
|
const file = await src.createHandle();
|
|
|
|
const observer = new CollectingFileSystemObserver(t, root_dir);
|
|
await observer.observe([watched_handle], {recursive});
|
|
|
|
// Move `file`.
|
|
await file.move(dest.parentHandle(), dest.fileName());
|
|
|
|
// Expect one "moved" event to happen on `file`.
|
|
const records = await observer.getRecords();
|
|
await assert_records_equal(
|
|
watched_handle, records, [movedEvent(
|
|
file, dest.relativePathComponents(),
|
|
src.relativePathComponents())]);
|
|
|
|
observer.disconnect();
|
|
}
|
|
}
|
|
}
|
|
}, 'Moving a file through FileSystemFileHandle.move is reported as a "moved" event if destination and source are in scope');
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const dir =
|
|
await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
|
|
|
|
const scope_test = new ScopeTest(t, dir);
|
|
const watched_handle = await scope_test.watched_handle();
|
|
|
|
for (const recursive of [false, true]) {
|
|
for await (const src of scope_test.out_of_scope_paths(recursive)) {
|
|
for await (const dest of scope_test.out_of_scope_paths(recursive)) {
|
|
const file = await src.createHandle();
|
|
|
|
const observer = new CollectingFileSystemObserver(t, root_dir);
|
|
await observer.observe([watched_handle], {recursive});
|
|
|
|
// Move `file`.
|
|
await file.move(dest.parentHandle(), dest.fileName());
|
|
|
|
// Expect the observer to not receive any events.
|
|
const records = await observer.getRecords();
|
|
await assert_records_equal(watched_handle, records, []);
|
|
}
|
|
}
|
|
}
|
|
}, 'Moving a file through FileSystemFileHandle.move is not reported if destination and source are not in scope');
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const dir =
|
|
await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
|
|
|
|
const scope_test = new ScopeTest(t, dir);
|
|
const watched_handle = await scope_test.watched_handle();
|
|
|
|
for (const recursive of [false, true]) {
|
|
for await (const src of scope_test.out_of_scope_paths(recursive)) {
|
|
for await (const dest of scope_test.in_scope_paths(recursive)) {
|
|
const file = await src.createHandle();
|
|
|
|
const observer = new CollectingFileSystemObserver(t, root_dir);
|
|
await observer.observe([watched_handle], {recursive});
|
|
|
|
// Move `file`.
|
|
await file.move(dest.parentHandle(), dest.fileName());
|
|
|
|
// Expect one "appeared" event to happen on `file`.
|
|
const records = await observer.getRecords();
|
|
await assert_records_equal(
|
|
watched_handle, records,
|
|
[appearedEvent(file, dest.relativePathComponents())]);
|
|
}
|
|
}
|
|
}
|
|
}, 'Moving a file through FileSystemFileHandle.move is reported as a "appeared" event if only destination is in scope');
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const dir =
|
|
await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
|
|
|
|
const scope_test = new ScopeTest(t, dir);
|
|
const watched_handle = await scope_test.watched_handle();
|
|
|
|
for (const recursive of [false, true]) {
|
|
for await (const src of scope_test.in_scope_paths(recursive)) {
|
|
for await (const dest of scope_test.out_of_scope_paths(recursive)) {
|
|
// These both point to the same underlying file entry initially until
|
|
// move is called on `fileToMove`. `file` is kept so that we have a
|
|
// handle that still points at the source file entry.
|
|
const file = await src.createHandle();
|
|
const fileToMove = await src.createHandle();
|
|
|
|
const observer = new CollectingFileSystemObserver(t, root_dir);
|
|
await observer.observe([watched_handle], {recursive});
|
|
|
|
// Move `fileToMove`.
|
|
await fileToMove.move(dest.parentHandle(), dest.fileName());
|
|
|
|
// Expect one "disappeared" event to happen on `file`.
|
|
const records = await observer.getRecords();
|
|
await assert_records_equal(
|
|
watched_handle, records,
|
|
[disappearedEvent(src.relativePathComponents())]);
|
|
}
|
|
}
|
|
}
|
|
}, 'Moving a file through FileSystemFileHandle.move is reported as a "disappeared" event if only source is in scope');
|
|
|
|
// Wraps a `CollectingFileSystemObserver` and disconnects the observer after it's
|
|
// received `num_of_records_to_observe`.
|
|
class DisconnectingFileSystemObserver {
|
|
#collectingObserver;
|
|
|
|
#num_of_records_to_observe;
|
|
|
|
#called_disconnect = false;
|
|
#records_observed_count = 0;
|
|
|
|
constructor(test, root_dir, num_of_records_to_observe) {
|
|
this.#collectingObserver = new CollectingFileSystemObserver(
|
|
test, root_dir, this.#callback.bind(this));
|
|
this.#num_of_records_to_observe = num_of_records_to_observe;
|
|
}
|
|
|
|
#callback(records, observer) {
|
|
this.#records_observed_count += records.length;
|
|
|
|
const called_disconnect = this.#called_disconnect;
|
|
|
|
// Call `disconnect` once after we've received `num_of_records_to_observe`.
|
|
if (!called_disconnect &&
|
|
this.#records_observed_count >= this.#num_of_records_to_observe) {
|
|
observer.disconnect();
|
|
this.#called_disconnect = true;
|
|
}
|
|
|
|
return {called_disconnect};
|
|
}
|
|
|
|
getRecordsWithCallbackInfo() {
|
|
return this.#collectingObserver.getRecordsWithCallbackInfo();
|
|
}
|
|
|
|
observe(handles) {
|
|
return this.#collectingObserver.observe(handles);
|
|
}
|
|
}
|
|
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const total_files_to_create = 100;
|
|
|
|
const child_dir =
|
|
await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
|
|
|
|
// Create a `FileSystemObserver` that will disconnect after its
|
|
// received half of the total files we're going to create.
|
|
const observer = new DisconnectingFileSystemObserver(
|
|
t, root_dir, total_files_to_create / 2);
|
|
|
|
// Observe the child directory and create files in it.
|
|
await observer.observe([child_dir]);
|
|
for (let i = 0; i < total_files_to_create; i++) {
|
|
child_dir.getFileHandle(`file${i}`, {create: true});
|
|
}
|
|
|
|
// Wait for `disconnect` to be called.
|
|
const records_with_disconnect_state =
|
|
await observer.getRecordsWithCallbackInfo();
|
|
|
|
// No observations should have been received after disconnected has been
|
|
// called.
|
|
assert_false(
|
|
records_with_disconnect_state.some(
|
|
({called_disconnect}) => called_disconnect),
|
|
'Received records after disconnect.');
|
|
}, 'Observations stop after disconnect()');
|
|
|
|
directory_test(async (t, root_dir) => {
|
|
const num_of_child_dirs = 5;
|
|
const num_files_to_create_per_directory = 100;
|
|
const total_files_to_create =
|
|
num_files_to_create_per_directory * num_of_child_dirs;
|
|
|
|
const child_dirs = await createDirectoryHandles(
|
|
root_dir, getUniqueName(), getUniqueName(), getUniqueName());
|
|
|
|
// Create a `FileSystemObserver` that will disconnect after its received half
|
|
// of the total files we're going to create.
|
|
const observer = new DisconnectingFileSystemObserver(
|
|
t, root_dir, total_files_to_create / 2);
|
|
|
|
// Observe the child directories and create files in them.
|
|
await observer.observe(child_dirs);
|
|
for (let i = 0; i < num_files_to_create_per_directory; i++) {
|
|
child_dirs.forEach(
|
|
child_dir => child_dir.getFileHandle(`file${i}`, {create: true}));
|
|
}
|
|
|
|
// Wait for `disconnect` to be called.
|
|
const records_with_disconnect_state =
|
|
await observer.getRecordsWithCallbackInfo();
|
|
|
|
// No observations should have been received after disconnected has been
|
|
// called.
|
|
assert_false(
|
|
records_with_disconnect_state.some(
|
|
({called_disconnect}) => called_disconnect),
|
|
'Received records after disconnect.');
|
|
}, 'Observations stop for all observed handles after disconnect()');
|