Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions app/editor/note-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ const Metadata = () => {
</time>

{save !== "idle" && (
<span className="font-semibold text-gray-400 text-xs transition-colors duration-300 before:content-['_'] sm:text-sm dark:text-gray-600">
<output className="font-semibold text-gray-400 text-xs transition-colors duration-300 before:content-['_'] sm:text-sm dark:text-gray-600">
{save === "saving" && "Saving..."}
{save === "saved" && "Saved."}
</span>
</output>
)}
</section>
);
Expand Down Expand Up @@ -92,19 +92,20 @@ export const NoteEditor = () => {
SystemStore.setState((c) => ({ ...c, save: "saved" }));
}, 100);

/**
* Listen to store changes. If the note changes we want to be able to
* debounce the save on Indexed DB.
*/
const { unsubscribe } = NoteStore.subscribe(() => {
SystemStore.setState((c) => ({ ...c, save: "saving" }));
debouncedSave.debouncedFn();
});

/**
* Mount effect on load.
*/
useEffect(() => {
/**
* Listen to store changes. If the note changes we want to be able to
* debounce the save on Indexed DB. This store is only mounted once during the
* application's lifecycle.
*/
const { unsubscribe } = NoteStore.subscribe(() => {
SystemStore.setState((c) => ({ ...c, save: "saving" }));
debouncedSave.debouncedFn();
});

/**
* If the user switched tabs, minimized the browser, or closed the page,
* immediately save and unmount the effect.
Expand All @@ -115,7 +116,6 @@ export const NoteEditor = () => {
}

await debouncedSave.flush();
unsubscribe();
};

/**
Expand All @@ -124,7 +124,6 @@ export const NoteEditor = () => {
*/
const handlePageHide = async () => {
await debouncedSave.flush();
unsubscribe();
};

/**
Expand All @@ -141,10 +140,11 @@ export const NoteEditor = () => {
* On app unmount, remove all of the event listeners.
*/
return () => {
unsubscribe();
document.removeEventListener("visibilitychange", handleVisibilityChange);
window.removeEventListener("pagehide", handlePageHide);
};
}, [unsubscribe, debouncedSave.flush]);
}, [debouncedSave]);

const handleSave = async () => {
await debouncedSave.flush();
Expand Down
44 changes: 44 additions & 0 deletions e2e/Speednote.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,50 @@ test("able to handle unused query parameters", async ({ page }) => {
await expect(content).toBeEditable();
});

test("able to restore autosave after hiding the app", async ({ page }) => {
// Render the app with our new browser context.
await renderPage(page);

// Write something on the inputs.
const { title, content } = await getAndAssertEditor(page);
await title.fill("Autosave Test Title");
await content.fill("Autosave Test Content");

// Force page visibility.
await page.evaluate(() => {
Object.defineProperty(document, "visibilityState", {
configurable: true,
value: "hidden",
});

document.dispatchEvent(new Event("visibilitychange"));
});

// Verify that the state is still `Saved.`
await expect(page.getByRole("status")).toContainText("Saved.");

// Make the page visible again, and then try again.
await page.evaluate(() => {
Object.defineProperty(document, "visibilityState", {
configurable: true,
value: "visible",
});

document.dispatchEvent(new Event("visibilitychange"));
});

// Fill it out again, and then check if the Autosave still works.
await title.fill("Autosave");
await content.fill("Autosave");
await expect(page.getByRole("status")).toContainText("Saving...");
await expect(page.getByRole("status")).toContainText("Saved.");

// Reload the page.
await page.reload();
await expect(title).toHaveValue("Autosave");
await expect(content).toHaveValue("Autosave");
});

// // 404 pages are only testable in integration environments as it's a server-side page.
// test('able to view and recover from 404 not found', async ({ page }) => {
// await renderPage(page, '/404');
Expand Down