browser-fs 0.1.0

A browser-based filesystem implementation for WebAssembly applications
Documentation
// This only runs in the browser
#![cfg(target_arch = "wasm32")]

use std::assert_eq;
use std::io::ErrorKind;

use futures_lite::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
use wasm_bindgen_test::*;

wasm_bindgen_test_configure!(run_in_dedicated_worker);

use browser_fs::{File, OpenOptions};

async fn read_file(filename: &str) -> String {
    let mut file = OpenOptions::new()
        .create(true)
        .read(true)
        .open(filename)
        .await
        .unwrap();
    let mut buf = String::new();
    file.read_to_string(&mut buf).await.unwrap();
    buf
}

#[wasm_bindgen_test]
async fn should_file_if_file_not_found() {
    let err = OpenOptions::new()
        .open("file-not-found.txt")
        .await
        .unwrap_err();
    assert_eq!(err.kind(), ErrorKind::NotFound, "{err:?}");
}

#[wasm_bindgen_test]
async fn should_create_file_if_not_exists() {
    let fname = "created-file.txt";
    let file = OpenOptions::new().create(true).open(fname).await.unwrap();
    drop(file);
    let _file = OpenOptions::new().create(true).open(fname).await.unwrap();
}

#[wasm_bindgen_test]
async fn should_write_and_read_file() {
    let fname = "written-file.txt";
    let mut file = File::create(fname).await.unwrap();
    file.write_all("Hello".as_bytes()).await.unwrap();
    file.flush().await.unwrap();
    file.close().await.unwrap();
    drop(file);

    let buf = read_file(fname).await;

    assert_eq!(buf, "Hello")
}

#[wasm_bindgen_test]
async fn should_append_to_file() {
    let fname = "append-file.txt";
    let mut file = File::create(fname).await.unwrap();
    file.write_all("Hello ".as_bytes()).await.unwrap();
    file.flush().await.unwrap();
    file.close().await.unwrap();
    drop(file);

    let mut file = OpenOptions::new()
        .create(true)
        .append(true)
        .write(true)
        .open(fname)
        .await
        .unwrap();
    file.write_all("World".as_bytes()).await.unwrap();
    file.flush().await.unwrap();
    file.close().await.unwrap();
    drop(file);

    let buf = read_file(fname).await;

    assert_eq!(buf, "Hello World")
}

#[wasm_bindgen_test]
async fn should_truncate_file() {
    let fname = "truncate-file.txt";
    let mut file = File::create(fname).await.unwrap();
    file.write_all("Hello World".as_bytes()).await.unwrap();
    file.flush().await.unwrap();
    file.close().await.unwrap();
    drop(file);

    let mut file = OpenOptions::new()
        .create(true)
        .truncate(true)
        .write(true)
        .open(fname)
        .await
        .unwrap();
    file.write_all("Nevermind".as_bytes()).await.unwrap();
    file.flush().await.unwrap();
    file.close().await.unwrap();
    drop(file);

    let buf = read_file(fname).await;
    assert_eq!(buf, "Nevermind")
}

#[wasm_bindgen_test]
async fn should_seek() {
    let fname = "seek-file.txt";
    let mut file = File::create(fname).await.unwrap();
    file.write_all("Hello World".as_bytes()).await.unwrap();
    file.seek(std::io::SeekFrom::End(-5)).await.unwrap();
    file.write_all("New World".as_bytes()).await.unwrap();
    file.flush().await.unwrap();
    file.close().await.unwrap();
    drop(file);

    let buf = read_file(fname).await;
    assert_eq!(buf, "Hello New World");
}

#[wasm_bindgen_test]
async fn shouldnt_seek_in_negative() {
    let mut file = File::create("seek-out-file.txt").await.unwrap();
    file.write_all("Hello World".as_bytes()).await.unwrap();
    let err = file.seek(std::io::SeekFrom::End(-100)).await.unwrap_err();
    assert_eq!(err.kind(), ErrorKind::InvalidInput);
}

#[wasm_bindgen_test]
async fn should_seek_ahead() {
    let fname = "seek-ahead-file.txt";
    let mut file = File::create(fname).await.unwrap();
    file.write_all("Hello World".as_bytes()).await.unwrap();
    file.seek(std::io::SeekFrom::Start(100)).await.unwrap();
    file.write_all("Yolo".as_bytes()).await.unwrap();
    file.flush().await.unwrap();
    file.close().await.unwrap();
    drop(file);

    let buf = read_file(fname).await;
    assert_eq!(buf, "Hello World\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0Yolo");
}

#[wasm_bindgen_test]
async fn shouldnt_create_file_in_missing_directory() {
    let fname = "/not-found/file.txt";
    let err = File::create(fname).await.unwrap_err();
    assert_eq!(err.kind(), ErrorKind::NotFound);
}

#[wasm_bindgen_test]
async fn should_remove_file() {
    let fname = "dropping-file.txt";
    let mut file = File::create(fname).await.unwrap();
    file.write_all("Hello World".as_bytes()).await.unwrap();
    file.seek(std::io::SeekFrom::Start(100)).await.unwrap();
    file.write_all("Yolo".as_bytes()).await.unwrap();
    file.flush().await.unwrap();
    file.close().await.unwrap();
    drop(file);

    browser_fs::remove_file(fname).await.unwrap();

    let err = OpenOptions::new().open(fname).await.unwrap_err();
    assert_eq!(err.kind(), ErrorKind::NotFound, "{err:?}");
}

#[wasm_bindgen_test]
async fn shouldnt_remove_file_if_missing() {
    let fname = "missing-file.txt";
    let err = browser_fs::remove_file(fname).await.unwrap_err();
    assert_eq!(err.kind(), ErrorKind::NotFound, "{err:?}");
}

#[wasm_bindgen_test]
async fn should_read_file_metadata() {
    let fname = "metadata-file.txt";
    let mut file = File::create(fname).await.unwrap();
    file.write_all("Hello World".as_bytes()).await.unwrap();
    file.flush().await.unwrap();
    file.close().await.unwrap();
    drop(file);

    let meta = browser_fs::metadata(fname).await.unwrap();
    assert!(meta.is_file());
    assert_eq!(meta.len(), 11);
    assert!(meta.modified().is_ok());
}