mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-03 08:02:04 +00:00
feat: mount the global modals inside UpgradeDialogProvider so the mailbox-allowance dialog can offer the plan that lifts the cap instead of throwing UpgradeDialogProvider not found, and assert the provider boundary so a sibling cannot drift back outside it
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
// The global modals are siblings of AppLayout, not children of it, and they sit
|
||||
// lower in the file than the provider that has to reach them. That makes it
|
||||
// easy to close a provider above them and not notice: nothing fails to compile,
|
||||
// nothing fails to render, and the throw only arrives when someone opens the
|
||||
// one modal that uses the context.
|
||||
//
|
||||
// MailboxAllowanceDialog, reached through AddEmailModal, calls useUpgradeDialog
|
||||
// to offer the plan that lifts the mailbox cap. With it outside the provider
|
||||
// that threw "UpgradeDialogProvider not found" at the exact moment a customer
|
||||
// hit their allowance.
|
||||
const source = readFileSync(join(__dirname, "layout.tsx"), "utf8");
|
||||
|
||||
const GLOBAL_MODALS = [
|
||||
"<TagsModal />",
|
||||
"<FoldersModal />",
|
||||
"<AddEmailModal />",
|
||||
"<ComposeWindow />",
|
||||
"<PasskeyEnrollPrompt />",
|
||||
"<PermissionDeniedModal />",
|
||||
"<ReauthModal />",
|
||||
];
|
||||
|
||||
function between(open: string, close: string): string {
|
||||
const start = source.indexOf(open);
|
||||
const end = source.indexOf(close);
|
||||
expect(start, `${open} missing from layout.tsx`).toBeGreaterThan(-1);
|
||||
expect(end, `${close} missing from layout.tsx`).toBeGreaterThan(start);
|
||||
return source.slice(start, end);
|
||||
}
|
||||
|
||||
describe("the /app provider tree", () => {
|
||||
it("keeps every global modal inside UpgradeDialogProvider", () => {
|
||||
const inside = between("<UpgradeDialogProvider>", "</UpgradeDialogProvider>");
|
||||
for (const modal of GLOBAL_MODALS) {
|
||||
expect(inside, `${modal} is mounted outside UpgradeDialogProvider`).toContain(modal);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps every global modal inside ConfirmProvider", () => {
|
||||
// Their action handlers call useConfirm(); this is the same trap.
|
||||
const inside = between("<ConfirmProvider>", "</ConfirmProvider>");
|
||||
for (const modal of GLOBAL_MODALS) {
|
||||
expect(inside, `${modal} is mounted outside ConfirmProvider`).toContain(modal);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -29,6 +29,14 @@ export default function RootAppLayout() {
|
||||
// inside their action handlers (delete confirmations etc.). They
|
||||
// need UserProvider too (state lives there: tagsEdit, foldersEdit,
|
||||
// addEmail), so they sit between the two providers.
|
||||
//
|
||||
// They are inside UpgradeDialogProvider for the same class of reason, and
|
||||
// it is easy to get wrong: they render below AppLayout in the file but are
|
||||
// siblings of it, so a provider that wraps only AppLayout does not reach
|
||||
// them. AddEmailModal renders MailboxAllowanceDialog, which calls
|
||||
// useUpgradeDialog to offer the plan that lifts the cap, so leaving it
|
||||
// outside threw the moment someone hit their mailbox allowance -- exactly
|
||||
// when the upgrade path is what they needed.
|
||||
return <UserProvider>
|
||||
<DataSyncProvider>
|
||||
<ConfirmProvider>
|
||||
@@ -51,14 +59,14 @@ export default function RootAppLayout() {
|
||||
</RealtimeManager>
|
||||
</SocketProvider>
|
||||
</LinkProvider>
|
||||
<TagsModal />
|
||||
<FoldersModal />
|
||||
<AddEmailModal />
|
||||
<ComposeWindow />
|
||||
<PasskeyEnrollPrompt />
|
||||
<PermissionDeniedModal />
|
||||
<ReauthModal />
|
||||
</UpgradeDialogProvider>
|
||||
<TagsModal />
|
||||
<FoldersModal />
|
||||
<AddEmailModal />
|
||||
<ComposeWindow />
|
||||
<PasskeyEnrollPrompt />
|
||||
<PermissionDeniedModal />
|
||||
<ReauthModal />
|
||||
</ConfirmProvider>
|
||||
</DataSyncProvider>
|
||||
</UserProvider>
|
||||
|
||||
Reference in New Issue
Block a user