Add tests and enhance local draft storage handling

This commit is contained in:
2026-03-31 01:50:29 +02:00
parent e23bcac025
commit 4df1773c95
7 changed files with 296 additions and 43 deletions

View File

@@ -8,38 +8,139 @@ export interface KeyValueStorage {
removeItem(key: string): void;
}
export interface BrowserStorageAccessResult {
storage: KeyValueStorage | null;
diagnostic: string | null;
}
export type SaveSceneDocumentDraftResult =
| { status: "saved"; message: string }
| { status: "error"; message: string };
export type LoadSceneDocumentDraftResult =
| { status: "loaded"; document: SceneDocument; message: string }
| { status: "missing"; message: string }
| { status: "error"; message: string };
export interface LoadOrCreateSceneDocumentResult {
document: SceneDocument;
diagnostic: string | null;
}
export const DEFAULT_SCENE_DRAFT_STORAGE_KEY = "webeditor3d.scene-document-draft";
export function getBrowserStorage(): KeyValueStorage | null {
if (typeof window === "undefined") {
return null;
function getErrorDetail(error: unknown): string {
if (error instanceof Error && error.message.trim().length > 0) {
return error.message.trim();
}
return window.localStorage;
return "Unknown error.";
}
function formatStorageDiagnostic(prefix: string, error: unknown): string {
return `${prefix} ${getErrorDetail(error)}`;
}
export function getBrowserStorageAccess(): BrowserStorageAccessResult {
if (typeof window === "undefined") {
return {
storage: null,
diagnostic: null
};
}
try {
return {
storage: window.localStorage,
diagnostic: null
};
} catch (error) {
return {
storage: null,
diagnostic: formatStorageDiagnostic("Browser local storage is unavailable.", error)
};
}
}
export function getBrowserStorage(): KeyValueStorage | null {
return getBrowserStorageAccess().storage;
}
export function saveSceneDocumentDraft(
storage: KeyValueStorage,
document: SceneDocument,
key = DEFAULT_SCENE_DRAFT_STORAGE_KEY
) {
storage.setItem(key, serializeSceneDocument(document));
}
): SaveSceneDocumentDraftResult {
try {
storage.setItem(key, serializeSceneDocument(document));
export function loadSceneDocumentDraft(storage: KeyValueStorage, key = DEFAULT_SCENE_DRAFT_STORAGE_KEY): SceneDocument | null {
const rawDocument = storage.getItem(key);
if (rawDocument === null) {
return null;
return {
status: "saved",
message: "Local draft saved."
};
} catch (error) {
return {
status: "error",
message: formatStorageDiagnostic("Local draft could not be saved.", error)
};
}
return parseSceneDocumentJson(rawDocument);
}
export function loadOrCreateSceneDocument(storage: KeyValueStorage | null, key = DEFAULT_SCENE_DRAFT_STORAGE_KEY): SceneDocument {
export function loadSceneDocumentDraft(
storage: KeyValueStorage,
key = DEFAULT_SCENE_DRAFT_STORAGE_KEY
): LoadSceneDocumentDraftResult {
try {
const rawDocument = storage.getItem(key);
if (rawDocument === null) {
return {
status: "missing",
message: "No local draft was found."
};
}
return {
status: "loaded",
document: parseSceneDocumentJson(rawDocument),
message: "Local draft loaded."
};
} catch (error) {
return {
status: "error",
message: formatStorageDiagnostic("Stored local draft could not be loaded.", error)
};
}
}
export function loadOrCreateSceneDocument(
storage: KeyValueStorage | null,
key = DEFAULT_SCENE_DRAFT_STORAGE_KEY
): LoadOrCreateSceneDocumentResult {
if (storage === null) {
return createEmptySceneDocument();
return {
document: createEmptySceneDocument(),
diagnostic: null
};
}
return loadSceneDocumentDraft(storage, key) ?? createEmptySceneDocument();
const draftResult = loadSceneDocumentDraft(storage, key);
switch (draftResult.status) {
case "loaded":
return {
document: draftResult.document,
diagnostic: null
};
case "missing":
return {
document: createEmptySceneDocument(),
diagnostic: null
};
case "error":
return {
document: createEmptySceneDocument(),
diagnostic: `${draftResult.message} Starting with a fresh empty document.`
};
}
}