Skip to content

Commit

Permalink
Refactor AliasedEmails component and enhance test coverage
Browse files Browse the repository at this point in the history
This commit includes a significant refactor of the AliasedEmails component to improve code readability and maintainability. The changes include the introduction of useCallback for the validateEmail function, reordering of functions for better logical flow, and the encapsulation of timestamp-related logic into a custom hook, useRealtimeTimestamp.

In addition, the test coverage for the aliased emails feature has been significantly enhanced. New tests have been added to verify the correct storage of copy history, the correct copying to clipboard functionality, and the correct timestamp saved in the aliased emails with timestamp. The playwright configuration has also been updated to support these new tests and the extension page specifically.
  • Loading branch information
mikikiv committed Feb 14, 2024
1 parent b3e87ed commit 549fd98
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 46 deletions.
83 changes: 44 additions & 39 deletions components/AliasedEmails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
} from "@tabler/icons-react"
import aliasedEmail from "aliased-email"
import { useAtom } from "jotai"
import { useEffect, useState } from "react"
import { useCallback, useEffect, useState } from "react"
import { localCopyHistoryAtom } from "./global/CopyHistory"

type Props = { extension?: boolean }
Expand All @@ -43,57 +43,23 @@ export default function InputCreator({ extension }: Props) {
defaultValue: "",
})
const [finalEmail, setFinalEmail] = useState("")
const [realtimeTimestamp, setRealtimeTimestamp] = useState("")
const [copiedEmail, setCopiedEmail] = useState("")
const [timestampEnabled, setTimestampEnabled] = useLocalStorage({
key: "timestampEnabled",
defaultValue: true,
})

const timestamp = new Date().getTime()

const updateTimestamp = () => {
const updatingTimestamp = new Date(Date.now())
.toLocaleString("en-US", { hourCycle: "h24" })
.replace(/[:\/]+/g, ".")
.replace(/,/g, "-")
.replace(/\s+/g, "")
setRealtimeTimestamp(updatingTimestamp)
}

useEffect(() => {
updateTimestamp()
const interval = setInterval(updateTimestamp, 1000)
return () => clearInterval(interval)
}, [timestampEnabled])

useEffect(() => {
//every time the selected alias changes, update the aliased email
setFinalEmail(
timestampEnabled
? aliasedEmail(
email,
selectedAlias
? `${selectedAlias}-${realtimeTimestamp}`
: realtimeTimestamp
)
: selectedAlias
? aliasedEmail(email, selectedAlias)
: email
)
}, [email, selectedAlias, realtimeTimestamp, timestampEnabled])

const validateEmail = (email: string) => {
const validateEmail = useCallback((email: string) => {
const re = /\S+@\S+\.\S+/
return re.test(email)
}
}, [])

const handleChangeEmail = (e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value)
setFinalEmail(
aliasedEmail(
e.target.value,
selectedAlias || new Date(timestamp).getTime().toString()
selectedAlias || new Date().getTime().toString()
)
)
}
Expand All @@ -119,11 +85,12 @@ export default function InputCreator({ extension }: Props) {
}

const [copyHistory, setCopyHistory] = useAtom(localCopyHistoryAtom)
const uniqueCopyHistory = new Set(copyHistory.map((item) => item.value))

const handleCopyEmail = () => {
setCopiedEmail(finalEmail)

if (copyHistory.find((item) => item.value === finalEmail)) return
if (uniqueCopyHistory.has(finalEmail)) return
setCopyHistory((history) => [
...history,
{
Expand All @@ -135,6 +102,44 @@ export default function InputCreator({ extension }: Props) {
])
}

const useRealtimeTimestamp = () => {
const [realtimeTimestamp, setRealtimeTimestamp] = useState("")

useEffect(() => {
const updateTimestamp = () => {
const updatingTimestamp = new Date()
.toLocaleString("en-US", { hourCycle: "h24" })
.replace(/[:\/]+/g, ".")
.replace(/,/g, "-")
.replace(/\s+/g, "")
setRealtimeTimestamp(updatingTimestamp)
}

updateTimestamp()
const interval = setInterval(updateTimestamp, 1000)
return () => clearInterval(interval)
}, [])

return realtimeTimestamp
}

const realtimeTimestamp = useRealtimeTimestamp()

useEffect(() => {
setFinalEmail(
timestampEnabled
? aliasedEmail(
email,
selectedAlias
? `${selectedAlias}-${realtimeTimestamp}`
: realtimeTimestamp
)
: selectedAlias
? aliasedEmail(email, selectedAlias)
: email
)
}, [email, selectedAlias, realtimeTimestamp, timestampEnabled])

const generateRandomAlias = () => {
const generatedWord = faker.word.sample({
length: { min: 7, max: 13 },
Expand Down
46 changes: 43 additions & 3 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,59 @@ export default defineConfig({
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
use: {
...devices["Desktop Chrome"],
baseURL: "http://localhost:3000",
contextOptions: { permissions: ["clipboard-read", "clipboard-write"] },
},
},

{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
use: {
...devices["Desktop Firefox"],
baseURL: "http://localhost:3000",
launchOptions: {
firefoxUserPrefs: {
"dom.events.asyncClipboard.readText": true,
"dom.events.testing.asyncClipboard": true,
},
},
},
},

{
name: "webkit",
use: { ...devices["Desktop Safari"] },
use: {
...devices["Desktop Safari"],
baseURL: "http://localhost:3000",
launchOptions: {
// https://webkit.org/blog/11353/async-clipboard-api/
args: ["--enable-features=AsyncClipboard"],
},
contextOptions: { permissions: ["clipboard-read"] },
},
},

{
name: "extension chromium",
use: {
...devices["Desktop Chrome"],
baseURL: "http://localhost:3000/extension",
contextOptions: { permissions: ["clipboard-read", "clipboard-write"] },
},
},

// {
// name: "extension firefox",
// use: { ...devices["Desktop Firefox"], baseURL: "http://localhost:3000/extension" },
// },

// {
// name: "extension webkit",
// use: { ...devices["Desktop Safari"], baseURL: "http://localhost:3000/extension"},
// },

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
Expand Down
113 changes: 109 additions & 4 deletions tests/aliasedEmail.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { AliasType, CopyHistoryType, TotalLocalStorage } from "@/components/types"
import {
AliasType,
CopyHistoryType,
TotalLocalStorage,
} from "@/components/types"
import test, { expect } from "@playwright/test"
import { aliasedEmailObject } from "aliased-email"

const email = "test@example.com"
const aliasedEmail = "test+SugarFire@example.com"
test.describe("aliased emails", () => {
const email = "idxqamv@gmail.com"
const aliasedEmail = "idxqamv+SugarFire@gmail.com"
test.beforeEach(async ({ page }) => {
await page.goto("")
})

test("stores expected data", async ({ page }) => {
await page.goto("/")
await page.fill('input[type="email"]', email)
await page.fill('input[type="alias"]', "Sugar Fire")
await page.press('input[type="alias"]', "Enter")
Expand Down Expand Up @@ -68,4 +75,102 @@ test.describe("aliased emails", () => {
}
await expect(page.getByTestId(aliasedEmail)).toBeVisible()
})

// Test if the copy history is stored correctly in local storage

test("stores copy history", async ({ page }) => {
await page.fill('input[type="email"]', email)
await page.fill('input[type="alias"]', "Sugar Fire")
await page.press('input[type="alias"]', "Enter")
await page.getByText("Time").click()
await expect(page.getByTestId("timestampEnabled")).not.toBeChecked()
expect(await page.textContent("#copyEmail")).toEqual(aliasedEmail)
await page.click("#copyEmail")
const savedLocalStorage = (await page.context().storageState()).origins[0]
const copyHistory = savedLocalStorage.localStorage.find(
(item) => item.name === "copyHistory"
)
const parsedCopyHistory = copyHistory ? JSON.parse(copyHistory.value) : []

// Check if timestamp exists and is a number
expect(parsedCopyHistory[0]).toHaveProperty("timestamp")
expect(typeof parsedCopyHistory[0].timestamp).toBe("number")

// Remove the timestamp property for comparison
delete parsedCopyHistory[0].timestamp

// Now compare the rest of the object
expect(parsedCopyHistory).toEqual([
{ id: 0, type: "email", value: aliasedEmail },
])
})

test("copies to clipboard", async ({ page }) => {
await page.fill('input[type="email"]', email)
await page.fill('input[type="alias"]', "Sugar Fire")
await page.press('input[type="alias"]', "Enter")
// disable the timestamp
await page.getByText("Time").click()
await expect(page.getByTestId("timestampEnabled")).not.toBeChecked()
expect(await page.textContent("#copyEmail")).toEqual(aliasedEmail)
await page.click("#copyEmail")
const handle = await page.evaluateHandle(() =>
navigator.clipboard.readText()
)
const clipboardText = await handle.jsonValue()
expect(clipboardText).toBe(aliasedEmail)
})
})

test.describe("aliased emails with timestamp", () => {
test.describe.configure({
retries: 2,
})

test.beforeEach(async ({ page }) => {
await page.goto("")
})

test("the timestamp saved is correct", async ({ page }) => {
await page.fill('input[type="email"]', email)
await page.click("#copyEmail")
const now = new Date()
// compare the timestamp in the email object in localstorage with the current time
const savedLocalStorage = (await page.context().storageState()).origins[0]
const copyHistory = savedLocalStorage.localStorage.find(
(item) => item.name === "copyHistory"
)
const parsedCopyHistory = copyHistory ? JSON.parse(copyHistory.value) : []
const timestamp = parsedCopyHistory[0].timestamp
const date = new Date(parseInt(timestamp))

expect(`${date.getUTCMinutes()} ${date.getUTCSeconds()}`).toBe(
`${now.getUTCMinutes()} ${now.getUTCSeconds()}`
)
})
})

test.skip("displayed timestamp displays the expected time", async () => {
const formattedTimestamp = new Date()
.toLocaleString("en-US", { hourCycle: "h24" })
.replace(/[:\/]+/g, ".")
.replace(/,/g, "-")
.replace(/\s+/g, "")

test.beforeEach(async ({ page }) => {
await page.goto("")
})

test.describe.configure({
retries: 2,
})

test("UI displays the correct email", async ({ page }) => {
await page.fill('input[type="email"]', email)
for (let i = 0; i < 10; i++) {
expect(await page.locator("#copyEmail").textContent()).toBe(
aliasedEmailObject(email, formattedTimestamp).aliasedEmail
)
}
})
})

1 comment on commit 549fd98

@vercel
Copy link

@vercel vercel bot commented on 549fd98 Feb 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.