diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml
index f9fd104..acc0383 100644
--- a/.gitea/workflows/test.yaml
+++ b/.gitea/workflows/test.yaml
@@ -7,4 +7,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - run: npm install && npm run test && npx playwright test
+ - run: npm install && npm run test && npx playwright install && npx playwright test
diff --git a/.prettierrc b/.prettierrc
index f010f27..a9d9104 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -13,7 +13,7 @@
"*.spec.js"
],
"options": {
- "maxLineLength": 9999999
+ "maxLineLength": 9999
}
}
]
diff --git a/app/install/EnableNotifications.tsx b/app/install/EnableNotifications.tsx
index aa5a09a..3527d3f 100644
--- a/app/install/EnableNotifications.tsx
+++ b/app/install/EnableNotifications.tsx
@@ -1,6 +1,7 @@
import React from "react"
import { useEffect } from "react"
import { usePush } from "../usePush"
+import { request } from "http"
export default function EnableNotifications({
onSubscribe,
diff --git a/app/install/InstallPrompts.tsx b/app/install/InstallPrompts.tsx
index de93024..6077ca0 100644
--- a/app/install/InstallPrompts.tsx
+++ b/app/install/InstallPrompts.tsx
@@ -9,6 +9,7 @@ import PermissionDenied from "./PermissionDenied"
interface InstallPromptsProps {
isMobileSafari: boolean
isSupported: boolean
+ isIOS: boolean
notificationsEnabled: boolean
onInstallComplete: () => void
}
@@ -16,9 +17,10 @@ interface InstallPromptsProps {
export default function InstallPrompts({
isSupported,
isMobileSafari,
+ isIOS,
onInstallComplete,
}: InstallPromptsProps) {
- const { step } = useInstallState({ isSupported, isMobileSafari })
+ const { step } = useInstallState({ isSupported, isMobileSafari, isIOS })
const steps = {
"open safari": ,
diff --git a/app/install/PermissionDenied.tsx b/app/install/PermissionDenied.tsx
index 0ced448..8eda585 100644
--- a/app/install/PermissionDenied.tsx
+++ b/app/install/PermissionDenied.tsx
@@ -3,11 +3,16 @@ import React from "react"
export default function PermissionDenied() {
return (
+
Enable Notifications
- Tack Up Now requires notifications permissions to work
+ Tack Up Now requires notifications
+ permissions to work
-
Grant notifications permissions to use Tack Up Now
+
+ You have denied permission to send notifications, please grant
+ notifications permissions to use Tack Up Now
+
)
}
diff --git a/app/routes/_index.test.tsx b/app/routes/_index.test.tsx
index edb7944..a9ed4f7 100644
--- a/app/routes/_index.test.tsx
+++ b/app/routes/_index.test.tsx
@@ -14,7 +14,11 @@ describe("root", () => {
path: "/",
meta: () => [],
links: () => [],
- loader: () => ({ isSupported: true, isMobileSafari: true }),
+ loader: () => ({
+ isSupported: true,
+ isMobileSafari: true,
+ isIOS: true,
+ }),
Component: Index,
},
])
diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx
index 439eb00..39f7b45 100644
--- a/app/routes/_index.tsx
+++ b/app/routes/_index.tsx
@@ -28,28 +28,25 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
os.name !== "iOS" ||
versionAtLeast(coerceSemver(os.version) ?? "0.0.0", "16.4.0")
+ const isIOS = os.name === "iOS"
+
return json({
isSupported,
isMobileSafari,
- name: os.name,
- version: os.version,
+ isIOS,
})
}
export default function Index() {
- const { isSupported, isMobileSafari, name, version } =
- useLoaderData()
-
- console.log(name, version)
+ const { isSupported, isMobileSafari, isIOS } = useLoaderData()
return (
-
-
-
+
)
}
@@ -57,11 +54,13 @@ export default function Index() {
function LandingMessage({
isSupported,
isMobileSafari,
+ isIOS,
}: {
isSupported: boolean
isMobileSafari: boolean
+ isIOS: boolean
}) {
- const { installed } = useInstallState({ isSupported, isMobileSafari })
+ const { installed } = useInstallState({ isSupported, isMobileSafari, isIOS })
const [isInstalled, setIsInstalled] = useState(installed)
@@ -74,6 +73,7 @@ function LandingMessage({
setIsInstalled(true)}
/>
diff --git a/app/useInstallState.ts b/app/useInstallState.ts
index 5440f05..af88aaa 100644
--- a/app/useInstallState.ts
+++ b/app/useInstallState.ts
@@ -11,9 +11,11 @@ type IOSInstallStep =
export default function useInstallState({
isSupported,
isMobileSafari,
+ isIOS,
}: {
isSupported: boolean
isMobileSafari: boolean
+ isIOS: boolean
}) {
const isClient = typeof window !== "undefined"
@@ -37,18 +39,25 @@ export default function useInstallState({
("standalone" in navigator && (navigator.standalone as boolean)) ||
matchMedia("(dislay-mode: standalone)").matches
+ const iOSStates = [
+ state(!isMobileSafari, "open safari"),
+ state(!isRunningPWA, "install"),
+ ]
+
+ const states = [
+ state(!isSupported, "unsupported"),
+ ...(isIOS ? iOSStates : []),
+ state(permissionDenied, "permission denied"),
+ state(!notificationsEnabled, "enable notifications"),
+ ]
+
return {
- step: !isSupported
- ? "unsupported"
- : !isMobileSafari
- ? "open safari"
- : !isRunningPWA && isMobileSafari
- ? "install"
- : permissionDenied
- ? "permission denied"
- : !notificationsEnabled
- ? "enable notifications"
- : (null as IOSInstallStep | null),
+ step: states.find(({ active }) => active)?.result ?? null,
installed: notificationsEnabled,
}
}
+
+const state = (active: boolean, result: IOSInstallStep) => ({
+ active,
+ result,
+})
diff --git a/e2e/install.spec.ts b/e2e/install.spec.ts
index 81a7b93..ba65380 100644
--- a/e2e/install.spec.ts
+++ b/e2e/install.spec.ts
@@ -8,6 +8,12 @@ function webkitOnly() {
})
}
+function nonWebkitOnly() {
+ beforeEach(async ({ browserName }) => {
+ if (browserName === "webkit") skip()
+ })
+}
+
describe("when user is on iOS", () => {
webkitOnly()
@@ -108,7 +114,6 @@ describe("when user is on iOS", () => {
await page.goto("/")
const deniedText = page.getByText(/requires notifications permissions/)
-
await expect(deniedText).toBeAttached()
})
@@ -156,10 +161,59 @@ describe("when user is on iOS", () => {
})
describe("other browsers", () => {
- describe("when notifications permissions are unknown", () => {
- test("prompt the user to allow notifications", async ({ page }) => {
- // await page.
+ nonWebkitOnly()
+
+ beforeEach(async ({ page }) => {
+ await page.route("/api/subscribe", async (route) => {
+ await route.fulfill()
})
+ await stubServiceWorker(page)
+ })
+
+ test("prompt the user to allow notifications", async ({ page }) => {
+ await stubNotifications(page, { permission: "default" })
+ await page.goto("/")
+
+ const notificationText = page.getByText(/Enable Notifications/)
+
+ await expect(notificationText).toBeAttached()
+ })
+
+ test("show tack up now when permission is granted", async ({ page }) => {
+ await stubNotifications(page, { permission: "granted" })
+
+ await page.goto("/")
+
+ const yourNotificationsHeading = page.getByText(/Your Notifications/)
+ await expect(yourNotificationsHeading).toBeVisible()
+ })
+
+ test("submit the push subscription on permission granted", async ({
+ page,
+ }) => {
+ await stubNotifications(page, {
+ permission: "default",
+ requestPermissionResult: "granted",
+ })
+
+ await page.goto("/")
+
+ const requestPromise = page.waitForRequest("/api/subscribe")
+ await page.getByText(/Enable Notifications/).click()
+ const request = await requestPromise
+
+ await expect(request.postDataJSON()).toEqual({ hi: "subscription" })
+ })
+
+ test("prompt to allow notifications if permission was denied", async ({
+ page,
+ }) => {
+ await stubNotifications(page, { permission: "denied" })
+
+ await page.goto("/")
+
+ const deniedText = page.getByText(/requires notifications permissions/)
+ await expect(deniedText).toBeAttached()
})
})