| '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()'); |