Add source code.
This commit is contained in:
parent
ca592b6b26
commit
408291e229
|
@ -0,0 +1 @@
|
|||
export * from "./value.js";
|
|
@ -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),
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
|
@ -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();
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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>
|
|
@ -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"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue