1
Fork 0

Add source code.

This commit is contained in:
Bauke 2022-12-26 12:57:57 +01:00
parent ca592b6b26
commit 408291e229
Signed by: Bauke
GPG Key ID: C1C0F29952BCF558
7 changed files with 234 additions and 0 deletions

1
source/index.ts Normal file
View File

@ -0,0 +1 @@
export * from "./value.js";

73
source/value.ts Normal file
View File

@ -0,0 +1,73 @@
import browser from "webextension-polyfill";
export type StorageArea = browser.Storage.StorageArea;
type ValueOptions<T> = {
/** A function to convert a string to the type `T`. */
deserialize: (input: string) => T;
/** The key to use for storage. */
key: string;
/** A function convert the type `T` to a string, defaults to `JSON.stringify`. */
serialize?: (input: T) => string;
/** The storage area to use, defaults to local. */
storage?: StorageArea;
/** The default value to use if none exists in storage. */
value: T;
};
export async function createValue<T>(
options: ValueOptions<T>,
): Promise<Value<T>> {
const storage = options.storage ?? browser.storage.local;
const value = await storage.get(options.key);
const stored = value[options.key] as string | undefined;
return new Value({
key: options.key,
deserialize: options.deserialize,
serialize: options.serialize ?? JSON.stringify,
storage,
value: stored === undefined ? options.value : options.deserialize(stored),
});
}
type Props<T> = Required<ValueOptions<T>>;
export class Value<T> implements Props<T> {
public readonly deserialize: Props<T>["deserialize"];
public readonly key: Props<T>["key"];
public readonly serialize: Props<T>["serialize"];
public readonly storage: Props<T>["storage"];
private inner: Props<T>["value"];
constructor(options: Required<ValueOptions<T>>) {
this.deserialize = options.deserialize;
this.key = options.key;
this.serialize = options.serialize;
this.storage = options.storage;
this.inner = options.value;
}
get value(): T {
return this.inner;
}
set value(value: T) {
this.inner = value;
}
/** Remove the value from storage. */
public async remove(): Promise<void> {
await this.storage.remove(this.key);
}
/** Save the value to storage. */
public async save(): Promise<void> {
await this.storage.set({
[this.key]: this.serialize(this.inner),
});
}
}

9
tests/background.ts Normal file
View File

@ -0,0 +1,9 @@
import browser from "webextension-polyfill";
browser.browserAction.onClicked.addListener(async () => {
await browser.runtime.openOptionsPage();
});
browser.runtime.onInstalled.addListener(async () => {
await browser.runtime.openOptionsPage();
});

33
tests/example.ts Normal file
View File

@ -0,0 +1,33 @@
import browser from "webextension-polyfill";
import {createValue} from "../build/index.js";
const updatedDate = await createValue<Date>({
// A function that deserializes a string from storage to convert to the wanted
// type.
deserialize: (value) => new Date(value),
// A function that serializes the type to a string to be set in storage.
serialize: (date) => date.toISOString(),
// The key to get from storage.
key: "updatedDate",
// The StorageArea to use, defaults to local.
storage: browser.storage.sync,
// The value to use if there is none in storage.
value: new Date(),
});
// Get the inner value.
console.log(updatedDate.value);
// Set the inner value.
updatedDate.value = new Date();
// Save the value to storage.
await updatedDate.save();
// Remove the value from storage.
await updatedDate.remove();

78
tests/tests.ts Normal file
View File

@ -0,0 +1,78 @@
import {setup, type TestContext} from "@holllo/test";
import browser from "webextension-polyfill";
import {createValue, type Value} from "../source/index.js";
const create = async <T>(
key: string,
expected: T,
): Promise<[string, T, Value<T>]> => {
return [
key,
expected,
await createValue<T>({
deserialize: JSON.parse,
key,
serialize: JSON.stringify,
value: expected,
}),
];
};
const isStored = async (test: TestContext, key: string, exist: boolean) => {
const stored = await browser.storage.local.get(key);
test.equals(typeof stored[key], exist ? "string" : "undefined");
};
type SampleObject = {
name: string;
status: "failed" | "passed";
};
const sampleObject: SampleObject = {
name: "Sample Object",
status: "passed",
};
const group = await setup("Value<T>", async (group) => {
const samples = [
["number", "testNumber", Math.PI],
["string", "testString", "A string to test with!" as string],
["SampleObject", "testSampleObject", sampleObject],
] as const;
for (const sample of samples) {
group.test(`T = ${sample[0]}`, async (test) => {
const [key, expected, value] = await create(sample[1], sample[2]);
if (sample[0] === "SampleObject") {
const _expected = expected as SampleObject;
const _value = value.value as SampleObject;
test.equals(_value.name, _expected.name);
test.equals(_value.status, _expected.status);
} else {
test.equals(value.value, expected);
}
await isStored(test, key, false);
await value.save();
await isStored(test, key, true);
await value.remove();
await isStored(test, key, false);
});
}
group.test(`T = Date`, async (test) => {
const expectedString = "2022-12-31T12:34:56.789Z";
const expected = new Date(expectedString);
const value = await createValue<Date>({
deserialize: (input) => new Date(input),
key: "testDate",
serialize: (input) => input.toISOString(),
value: expected,
});
test.equals(value.value instanceof Date, true);
await value.save();
const stored = await browser.storage.local.get(value.key);
test.equals(stored[value.key], expectedString);
});
});

16
tests/web-ext/index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebExtension Storage Tests</title>
</head>
<body>
<script src="tests.js" type="module"></script>
<script src="example.js" type="module"></script>
</body>
</html>

View File

@ -0,0 +1,24 @@
{
"$schema": "http://json.schemastore.org/webextension",
"manifest_version": 2,
"name": "WebExtension Storage Tests",
"version": "0.1.0",
"applications": {
"gecko": {
"id": "webextension-storage-tests@holllo.org"
}
},
"background": {
"scripts": [
"background.js"
]
},
"browser_action": {},
"options_ui": {
"page": "index.html",
"open_in_tab": true
},
"permissions": [
"storage"
]
}