-
Notifications
You must be signed in to change notification settings - Fork 356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/107 Unsubscribe from emails #115
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,13 @@ | ||
{ | ||
"editor.formatOnSave": true, | ||
"editor.defaultFormatter": "svelte.svelte-vscode", | ||
"eslint.validate": ["javascript", "javascriptreact", "svelte"] | ||
"[svelte]": { | ||
"editor.defaultFormatter": "svelte.svelte-vscode" | ||
}, | ||
"[typescript]": { | ||
"editor.defaultFormatter": "esbenp.prettier-vscode" | ||
}, | ||
"[javascript]": { | ||
"editor.defaultFormatter": "esbenp.prettier-vscode" | ||
}, | ||
"eslint.validate": ["javascript", "javascriptreact", "typescript", "svelte"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
vi.mock("@supabase/supabase-js") | ||
vi.mock("$env/dynamic/private") | ||
vi.mock("resend") | ||
|
||
import { createClient, type User } from "@supabase/supabase-js" | ||
import { Resend } from "resend" | ||
import * as mailer from "./mailer" | ||
|
||
describe("mailer", () => { | ||
const mockSend = vi.fn().mockResolvedValue({ id: "mock-email-id" }) | ||
|
||
const mockSupabaseClient = { | ||
auth: { | ||
admin: { | ||
getUserById: vi.fn(), | ||
}, | ||
}, | ||
from: vi.fn().mockReturnThis(), | ||
select: vi.fn().mockReturnThis(), | ||
eq: vi.fn().mockReturnThis(), | ||
single: vi.fn(), | ||
} | ||
|
||
beforeEach(async () => { | ||
vi.clearAllMocks() | ||
const { env } = await import("$env/dynamic/private") | ||
env.PRIVATE_RESEND_API_KEY = "mock_resend_api_key" | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
;(createClient as any).mockReturnValue(mockSupabaseClient) | ||
|
||
vi.mocked(Resend).mockImplementation( | ||
() => | ||
({ | ||
emails: { | ||
send: mockSend, | ||
}, | ||
}) as unknown as Resend, | ||
) | ||
}) | ||
|
||
describe("sendUserEmail", () => { | ||
const mockUser = { id: "user123", email: "[email protected]" } | ||
|
||
it("sends welcome email", async () => { | ||
mockSupabaseClient.auth.admin.getUserById.mockResolvedValue({ | ||
data: { user: { email_confirmed_at: new Date().toISOString() } }, | ||
error: null, | ||
}) | ||
|
||
mockSupabaseClient.single.mockResolvedValue({ | ||
data: { unsubscribed: false }, | ||
error: null, | ||
}) | ||
|
||
await mailer.sendUserEmail({ | ||
user: mockUser as User, | ||
subject: "Test", | ||
from_email: "[email protected]", | ||
template_name: "welcome_email", | ||
template_properties: {}, | ||
}) | ||
|
||
expect(mockSend).toHaveBeenCalled() | ||
const email = mockSend.mock.calls[0][0] | ||
expect(email.to).toEqual(["[email protected]"]) | ||
}) | ||
|
||
it("should not send email if user is unsubscribed", async () => { | ||
const originalConsoleLog = console.log | ||
console.log = vi.fn() | ||
|
||
mockSupabaseClient.auth.admin.getUserById.mockResolvedValue({ | ||
data: { user: { email_confirmed_at: new Date().toISOString() } }, | ||
error: null, | ||
}) | ||
|
||
mockSupabaseClient.single.mockResolvedValue({ | ||
data: { unsubscribed: true }, | ||
error: null, | ||
}) | ||
|
||
await mailer.sendUserEmail({ | ||
user: mockUser as User, | ||
subject: "Test", | ||
from_email: "[email protected]", | ||
template_name: "welcome_email", | ||
template_properties: {}, | ||
}) | ||
|
||
expect(mockSend).not.toHaveBeenCalled() | ||
|
||
expect(console.log).toHaveBeenCalledWith( | ||
"User unsubscribed. Aborting email. ", | ||
mockUser.id, | ||
mockUser.email, | ||
) | ||
|
||
console.log = originalConsoleLog | ||
}) | ||
}) | ||
|
||
describe("sendTemplatedEmail", () => { | ||
it("sends templated email", async () => { | ||
await mailer.sendTemplatedEmail({ | ||
subject: "Test subject", | ||
from_email: "[email protected]", | ||
to_emails: ["[email protected]"], | ||
template_name: "welcome_email", | ||
template_properties: {}, | ||
}) | ||
|
||
expect(mockSend).toHaveBeenCalled() | ||
const email = mockSend.mock.calls[0][0] | ||
expect(email.from).toEqual("[email protected]") | ||
expect(email.to).toEqual(["[email protected]"]) | ||
expect(email.subject).toEqual("Test subject") | ||
expect(email.text).toContain("This is a quick sample of a welcome email") | ||
expect(email.html).toContain(">This is a quick sample of a welcome email") | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<script lang="ts"> | ||
import SettingsModule from "../settings_module.svelte" | ||
export let data | ||
let { profile } = data | ||
let unsubscribed = profile?.unsubscribed | ||
</script> | ||
|
||
<svelte:head> | ||
<title>Change Email Subscription</title> | ||
</svelte:head> | ||
|
||
<h1 class="text-2xl font-bold mb-6"> | ||
{unsubscribed ? "Re-subscribe to Emails" : "Unsubscribe from Emails"} | ||
</h1> | ||
|
||
<SettingsModule | ||
editable={true} | ||
saveButtonTitle={unsubscribed | ||
? "Re-subscribe" | ||
: "Unsubscribe from all emails"} | ||
successBody={unsubscribed | ||
? "You have been re-subscribed to emails" | ||
: "You have been unsubscribed from all emails"} | ||
formTarget="/account/api?/toggleEmailSubscription" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: I'd make this explicitly pass the true/false value instead of toggle. Technically UI could be stale and button does the opposite of what it says. Not likely in practice/P2. Will merge anyways, but if we're making other changes might be nice to add. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, I would prefer an explicit true/false request as well, but went with path of least resistance here :D |
||
fields={[]} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: I'd remove word "all". Things like "reset password" will still be sent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did this one while making some visual tweaks to settings. Those green buttons were bothering me. Other one still outstanding/optional.