iOS install instructions
Test / test (push) Failing after 42s
Details
Test / test (push) Failing after 42s
Details
This commit is contained in:
parent
16015491e4
commit
fd351360f1
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"tabWidth": 4,
|
"tabWidth": 2,
|
||||||
"semi": false,
|
"semi": false,
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"overrides": [
|
"overrides": [
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@
|
||||||
* For more information, see https://remix.run/file-conventions/entry.client
|
* For more information, see https://remix.run/file-conventions/entry.client
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RemixBrowser } from "@remix-run/react";
|
import { RemixBrowser } from "@remix-run/react"
|
||||||
import { startTransition, StrictMode } from "react";
|
import React from "react"
|
||||||
import { hydrateRoot } from "react-dom/client";
|
import { startTransition, StrictMode } from "react"
|
||||||
|
import { hydrateRoot } from "react-dom/client"
|
||||||
|
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
hydrateRoot(
|
hydrateRoot(
|
||||||
|
|
@ -14,5 +15,5 @@ startTransition(() => {
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<RemixBrowser />
|
<RemixBrowser />
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,16 @@
|
||||||
* For more information, see https://remix.run/file-conventions/entry.server
|
* For more information, see https://remix.run/file-conventions/entry.server
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { PassThrough } from "node:stream";
|
import { PassThrough } from "node:stream"
|
||||||
|
|
||||||
import type { AppLoadContext, EntryContext } from "@remix-run/node";
|
import type { AppLoadContext, EntryContext } from "@remix-run/node"
|
||||||
import { createReadableStreamFromReadable } from "@remix-run/node";
|
import { createReadableStreamFromReadable } from "@remix-run/node"
|
||||||
import { RemixServer } from "@remix-run/react";
|
import { RemixServer } from "@remix-run/react"
|
||||||
import { isbot } from "isbot";
|
import { isbot } from "isbot"
|
||||||
import { renderToPipeableStream } from "react-dom/server";
|
import { renderToPipeableStream } from "react-dom/server"
|
||||||
import React from "react";
|
import React from "react"
|
||||||
|
|
||||||
const ABORT_DELAY = 5_000;
|
const ABORT_DELAY = 5_000
|
||||||
|
|
||||||
export default function handleRequest(
|
export default function handleRequest(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
@ -37,7 +37,7 @@ export default function handleRequest(
|
||||||
responseStatusCode,
|
responseStatusCode,
|
||||||
responseHeaders,
|
responseHeaders,
|
||||||
remixContext
|
remixContext
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBotRequest(
|
function handleBotRequest(
|
||||||
|
|
@ -47,7 +47,7 @@ function handleBotRequest(
|
||||||
remixContext: EntryContext
|
remixContext: EntryContext
|
||||||
) {
|
) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let shellRendered = false;
|
let shellRendered = false
|
||||||
const { pipe, abort } = renderToPipeableStream(
|
const { pipe, abort } = renderToPipeableStream(
|
||||||
<RemixServer
|
<RemixServer
|
||||||
context={remixContext}
|
context={remixContext}
|
||||||
|
|
@ -56,38 +56,38 @@ function handleBotRequest(
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
onAllReady() {
|
onAllReady() {
|
||||||
shellRendered = true;
|
shellRendered = true
|
||||||
const body = new PassThrough();
|
const body = new PassThrough()
|
||||||
const stream = createReadableStreamFromReadable(body);
|
const stream = createReadableStreamFromReadable(body)
|
||||||
|
|
||||||
responseHeaders.set("Content-Type", "text/html");
|
responseHeaders.set("Content-Type", "text/html")
|
||||||
|
|
||||||
resolve(
|
resolve(
|
||||||
new Response(stream, {
|
new Response(stream, {
|
||||||
headers: responseHeaders,
|
headers: responseHeaders,
|
||||||
status: responseStatusCode,
|
status: responseStatusCode,
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
pipe(body);
|
pipe(body)
|
||||||
},
|
},
|
||||||
onShellError(error: unknown) {
|
onShellError(error: unknown) {
|
||||||
reject(error);
|
reject(error)
|
||||||
},
|
},
|
||||||
onError(error: unknown) {
|
onError(error: unknown) {
|
||||||
responseStatusCode = 500;
|
responseStatusCode = 500
|
||||||
// Log streaming rendering errors from inside the shell. Don't log
|
// Log streaming rendering errors from inside the shell. Don't log
|
||||||
// errors encountered during initial shell rendering since they'll
|
// errors encountered during initial shell rendering since they'll
|
||||||
// reject and get logged in handleDocumentRequest.
|
// reject and get logged in handleDocumentRequest.
|
||||||
if (shellRendered) {
|
if (shellRendered) {
|
||||||
console.error(error);
|
console.error(error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
setTimeout(abort, ABORT_DELAY);
|
setTimeout(abort, ABORT_DELAY)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBrowserRequest(
|
function handleBrowserRequest(
|
||||||
|
|
@ -97,7 +97,7 @@ function handleBrowserRequest(
|
||||||
remixContext: EntryContext
|
remixContext: EntryContext
|
||||||
) {
|
) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let shellRendered = false;
|
let shellRendered = false
|
||||||
const { pipe, abort } = renderToPipeableStream(
|
const { pipe, abort } = renderToPipeableStream(
|
||||||
<RemixServer
|
<RemixServer
|
||||||
context={remixContext}
|
context={remixContext}
|
||||||
|
|
@ -106,36 +106,36 @@ function handleBrowserRequest(
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
onShellReady() {
|
onShellReady() {
|
||||||
shellRendered = true;
|
shellRendered = true
|
||||||
const body = new PassThrough();
|
const body = new PassThrough()
|
||||||
const stream = createReadableStreamFromReadable(body);
|
const stream = createReadableStreamFromReadable(body)
|
||||||
|
|
||||||
responseHeaders.set("Content-Type", "text/html");
|
responseHeaders.set("Content-Type", "text/html")
|
||||||
|
|
||||||
resolve(
|
resolve(
|
||||||
new Response(stream, {
|
new Response(stream, {
|
||||||
headers: responseHeaders,
|
headers: responseHeaders,
|
||||||
status: responseStatusCode,
|
status: responseStatusCode,
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
pipe(body);
|
pipe(body)
|
||||||
},
|
},
|
||||||
onShellError(error: unknown) {
|
onShellError(error: unknown) {
|
||||||
reject(error);
|
reject(error)
|
||||||
},
|
},
|
||||||
onError(error: unknown) {
|
onError(error: unknown) {
|
||||||
responseStatusCode = 500;
|
responseStatusCode = 500
|
||||||
// Log streaming rendering errors from inside the shell. Don't log
|
// Log streaming rendering errors from inside the shell. Don't log
|
||||||
// errors encountered during initial shell rendering since they'll
|
// errors encountered during initial shell rendering since they'll
|
||||||
// reject and get logged in handleDocumentRequest.
|
// reject and get logged in handleDocumentRequest.
|
||||||
if (shellRendered) {
|
if (shellRendered) {
|
||||||
console.error(error);
|
console.error(error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
setTimeout(abort, ABORT_DELAY);
|
setTimeout(abort, ABORT_DELAY)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
/// <reference lib="WebWorker" />
|
/// <reference lib="WebWorker" />
|
||||||
|
|
||||||
export {};
|
export {}
|
||||||
|
|
||||||
declare let self: ServiceWorkerGlobalScope;
|
declare let self: ServiceWorkerGlobalScope
|
||||||
|
|
||||||
self.addEventListener('install', event => {
|
self.addEventListener("install", (event) => {
|
||||||
console.log('Service worker installed');
|
console.log("Service worker installed")
|
||||||
|
|
||||||
event.waitUntil(self.skipWaiting());
|
event.waitUntil(self.skipWaiting())
|
||||||
});
|
})
|
||||||
|
|
||||||
self.addEventListener('activate', event => {
|
self.addEventListener("activate", (event) => {
|
||||||
console.log('Service worker activated');
|
console.log("Service worker activated")
|
||||||
|
|
||||||
event.waitUntil(self.clients.claim());
|
event.waitUntil(self.clients.claim())
|
||||||
});
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
import { act, render, screen, waitFor } from "@testing-library/react"
|
||||||
import MatchMediaMock from 'jest-matchmedia-mock';
|
import MatchMediaMock from "jest-matchmedia-mock"
|
||||||
|
|
||||||
import { createRemixStub, RemixStubProps } from "@remix-run/testing"
|
import { createRemixStub, RemixStubProps } from "@remix-run/testing"
|
||||||
import App from './root'
|
import App from "./root"
|
||||||
import React from 'react'
|
import React from "react"
|
||||||
let RemixStub: (props: RemixStubProps) => React.JSX.Element
|
let RemixStub: (props: RemixStubProps) => React.JSX.Element
|
||||||
|
|
||||||
describe("root", () => {
|
describe("root", () => {
|
||||||
|
|
@ -11,22 +11,22 @@ describe("root", () => {
|
||||||
RemixStub = createRemixStub([
|
RemixStub = createRemixStub([
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
meta: () => ([]),
|
meta: () => [],
|
||||||
links: () => ([]),
|
links: () => [],
|
||||||
loader: () => ({ isSupported: true }),
|
loader: () => ({ isSupported: true }),
|
||||||
Component: App
|
Component: App,
|
||||||
}
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
let matchMedia: MatchMediaMock
|
let matchMedia: MatchMediaMock
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
matchMedia = new MatchMediaMock();
|
matchMedia = new MatchMediaMock()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
matchMedia.clear();
|
matchMedia.clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("when user is on iOS", () => {
|
describe("when user is on iOS", () => {
|
||||||
|
|
@ -35,7 +35,9 @@ describe("root", () => {
|
||||||
test("they are instructed to install tack up now", async () => {
|
test("they are instructed to install tack up now", async () => {
|
||||||
render(<RemixStub />)
|
render(<RemixStub />)
|
||||||
|
|
||||||
await waitFor(() => screen.findByText<HTMLElement>(/Install/), { timeout: 2000 })
|
await waitFor(() => screen.findByText<HTMLElement>(/Install/), {
|
||||||
|
timeout: 2000,
|
||||||
|
})
|
||||||
|
|
||||||
expect(true).toBe(true)
|
expect(true).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charSet="utf-8" />
|
<meta charSet="utf-8" />
|
||||||
<meta
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1"
|
|
||||||
/>
|
|
||||||
<Meta />
|
<Meta />
|
||||||
<Links />
|
<Links />
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,21 @@
|
||||||
import { json, type LoaderFunctionArgs, type MetaFunction } from "@remix-run/node";
|
import {
|
||||||
import { useLoaderData } from "@remix-run/react";
|
json,
|
||||||
import React, { Suspense, useEffect } from "react";
|
type LoaderFunctionArgs,
|
||||||
import UAParser from "ua-parser-js"
|
type MetaFunction,
|
||||||
import versionAtLeast from "semver/functions/gte"
|
} from "@remix-run/node"
|
||||||
|
import { useLoaderData } from "@remix-run/react"
|
||||||
|
import React, { useState } from "react"
|
||||||
import coerceSemver from "semver/functions/coerce"
|
import coerceSemver from "semver/functions/coerce"
|
||||||
|
import versionAtLeast from "semver/functions/gte"
|
||||||
|
import UAParser from "ua-parser-js"
|
||||||
|
import { usePush } from "remix-pwa-monorepo/packages/push/client/hook"
|
||||||
|
|
||||||
export const meta: MetaFunction = () => {
|
export const meta: MetaFunction = () => {
|
||||||
return [
|
return [
|
||||||
{ title: "Tack Up Now!" },
|
{ title: "Tack Up Now!" },
|
||||||
{ name: "description", content: "Get equinelive notifications" },
|
{ name: "description", content: "Get equinelive notifications" },
|
||||||
];
|
]
|
||||||
};
|
}
|
||||||
|
|
||||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
const userAgent = request.headers.get("user-agent")
|
const userAgent = request.headers.get("user-agent")
|
||||||
|
|
@ -23,30 +28,55 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
|
|
||||||
const { isSupported } = useLoaderData<typeof loader>()
|
const { isSupported } = useLoaderData<typeof loader>()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
|
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
|
||||||
<Suspense>
|
|
||||||
<LandingMessage isSupported={isSupported} />
|
<LandingMessage isSupported={isSupported} />
|
||||||
</Suspense>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function LandingMessage({ isSupported }: { isSupported: boolean }) {
|
function LandingMessage({ isSupported }: { isSupported: boolean }) {
|
||||||
|
const isClient = typeof window !== "undefined"
|
||||||
|
const notificationsEnabled =
|
||||||
|
isClient &&
|
||||||
|
"Notification" in window &&
|
||||||
|
window.Notification.permission === "granted"
|
||||||
|
|
||||||
if (typeof window === "undefined") return <div>Loading</div>
|
const isRunningPWA =
|
||||||
|
isClient &&
|
||||||
|
(("standalone" in navigator && (navigator.standalone as boolean)) ||
|
||||||
|
matchMedia("(dislay-mode: standalone)").matches)
|
||||||
|
|
||||||
const isRunningPWA = ("standalone" in navigator && navigator.standalone as boolean) || matchMedia("(dislay-mode: standalone)").matches
|
const [isInstalled, setIsInstalled] = useState(notificationsEnabled)
|
||||||
const notificationsEnabled = "Notification" in window && window.Notification.permission === "granted"
|
|
||||||
const message = isRunningPWA && !notificationsEnabled
|
|
||||||
? "Enable notifications"
|
|
||||||
: isSupported
|
|
||||||
? "Install Tack Up Now!"
|
|
||||||
: "Sorry, your device doesn't support Tack Up Now! :("
|
|
||||||
|
|
||||||
return <div><div>{message}</div></div>
|
return !isClient ? (
|
||||||
|
<div>Loading</div>
|
||||||
|
) : isInstalled ? (
|
||||||
|
<div>Your Notifications</div>
|
||||||
|
) : isRunningPWA && !notificationsEnabled ? (
|
||||||
|
<EnableButton onSubscribe={() => setIsInstalled(true)} />
|
||||||
|
) : isSupported ? (
|
||||||
|
"Install Tack Up Now!"
|
||||||
|
) : (
|
||||||
|
"Sorry, your device doesn't support Tack Up Now! :("
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function EnableButton({ onSubscribe }: { onSubscribe: () => void }) {
|
||||||
|
const { subscribeToPush, requestPermission } = usePush()
|
||||||
|
|
||||||
|
return <button onClick={subscribe}>Enable notifications</button>
|
||||||
|
|
||||||
|
async function subscribe() {
|
||||||
|
console.log("Hey the thing was clicked wow")
|
||||||
|
subscribeToPush("Derpderp", (subscription) => {
|
||||||
|
fetch("/subscribe", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(subscription),
|
||||||
|
})
|
||||||
|
onSubscribe()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { test, expect } from "@playwright/test"
|
import { test, expect, Page } from "@playwright/test"
|
||||||
|
import { before } from "node:test"
|
||||||
|
|
||||||
const { describe, beforeEach, skip, use } = test
|
const { describe, beforeEach, skip, use } = test
|
||||||
|
|
||||||
|
|
@ -13,10 +14,13 @@ describe("when user is on iOS", () => {
|
||||||
|
|
||||||
describe("version 16.4 or higher", () => {
|
describe("version 16.4 or higher", () => {
|
||||||
use({
|
use({
|
||||||
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Mobile/15E148 Safari/604.1",
|
userAgent:
|
||||||
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Mobile/15E148 Safari/604.1",
|
||||||
})
|
})
|
||||||
|
|
||||||
test("and tack up now is not running as a PWA, they are instructed to install tack up now", async ({ page }) => {
|
test("and tack up now is not running as a PWA, they are instructed to install tack up now", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
await page.goto("/")
|
await page.goto("/")
|
||||||
|
|
||||||
const installText = await page.getByText(/Install Tack Up Now!/)
|
const installText = await page.getByText(/Install Tack Up Now!/)
|
||||||
|
|
@ -25,27 +29,66 @@ describe("when user is on iOS", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("and tack up now is running as a PWA", () => {
|
describe("and tack up now is running as a PWA", () => {
|
||||||
|
|
||||||
beforeEach(async ({ page }) => {
|
beforeEach(async ({ page }) => {
|
||||||
await page.addInitScript(() => (window.navigator as any)["standalone"] = true)
|
await page.addInitScript(
|
||||||
|
() => ((window.navigator as any)["standalone"] = true)
|
||||||
|
)
|
||||||
|
await stubServiceWorker(page)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("and notifications aren't enabled, they are asked to enable notifications", async ({ page, browser }) => {
|
describe("and notifications aren't enabled", () => {
|
||||||
await page.addInitScript(() => (Object.defineProperty(window.Notification, "permission", {value: "default", writable: true })))
|
test("they are asked to enable notifications", async ({ page }) => {
|
||||||
|
await stubNotifications(page, { permission: "default" })
|
||||||
await page.goto("/")
|
await page.goto("/")
|
||||||
|
|
||||||
const notificationText = await page.getByText(/Enable notifications/)
|
const enableButton = await page.getByText(/Enable notifications/)
|
||||||
|
|
||||||
await expect(notificationText).toBeAttached()
|
await expect(enableButton).toBeAttached()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("and then the user enables notifications", () => {
|
||||||
|
beforeEach(async ({ page }) => {
|
||||||
|
await stubNotifications(page, {
|
||||||
|
permission: "default",
|
||||||
|
requestPermissionResult: "granted",
|
||||||
|
})
|
||||||
|
await page.route("/subscribe", async (route) => {
|
||||||
|
await route.fulfill()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("their push token is submitted after notifications are enabled", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto("/")
|
||||||
|
|
||||||
|
const requestPromise = page.waitForRequest("/subscribe")
|
||||||
|
await page.getByText(/Enable notifications/).click()
|
||||||
|
const request = await requestPromise
|
||||||
|
|
||||||
|
await expect(request.postDataJSON()).toEqual({ hi: "subscription" })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("users see tack up now after enabling notifications", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto("/")
|
||||||
|
|
||||||
|
const requestPromise = page.waitForRequest("/subscribe")
|
||||||
|
await page.getByText(/Enable notifications/).click()
|
||||||
|
await requestPromise
|
||||||
|
|
||||||
|
const yourNotificationsHeading =
|
||||||
|
await page.getByText(/Your Notifications/)
|
||||||
|
|
||||||
|
await expect(yourNotificationsHeading).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("and notifications are enabled", () => {
|
describe("and notifications are enabled", () => {
|
||||||
|
|
||||||
beforeEach(async ({ page }) => {
|
beforeEach(async ({ page }) => {
|
||||||
await page.addInitScript(() => {
|
await stubNotifications(page, { permission: "granted" })
|
||||||
window.Notification = window.Notification ?? {}
|
|
||||||
Object.defineProperty(window.Notification, "permission", {value: "granted", writable: true })
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("they aren't asked to enable notifications", async ({ page }) => {
|
test("they aren't asked to enable notifications", async ({ page }) => {
|
||||||
|
|
@ -56,16 +99,28 @@ describe("when user is on iOS", () => {
|
||||||
|
|
||||||
await expect(notificationText).not.toBeAttached()
|
await expect(notificationText).not.toBeAttached()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("users see tack up now", async ({ page }) => {
|
||||||
|
await page.goto("/")
|
||||||
|
|
||||||
|
const yourNotificationsHeading =
|
||||||
|
await page.getByText(/Your Notifications/)
|
||||||
|
|
||||||
|
await expect(yourNotificationsHeading).toBeVisible()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("version 16.3 and under", () => {
|
describe("version 16.3 and under", () => {
|
||||||
use({
|
use({
|
||||||
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Mobile/15E148 Safari/604.1",
|
userAgent:
|
||||||
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 16_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Mobile/15E148 Safari/604.1",
|
||||||
})
|
})
|
||||||
|
|
||||||
test("version 16.3 and under they are informed that their iOS version isn't supported", async ({ page }) => {
|
test("version 16.3 and under they are informed that their iOS version isn't supported", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
await page.goto("/")
|
await page.goto("/")
|
||||||
|
|
||||||
const sorryText = await page.getByText(/Sorry/)
|
const sorryText = await page.getByText(/Sorry/)
|
||||||
|
|
@ -74,3 +129,58 @@ describe("when user is on iOS", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function stubNotifications(
|
||||||
|
page: Page,
|
||||||
|
args: {
|
||||||
|
permission: NotificationPermission
|
||||||
|
requestPermissionResult?: NotificationPermission
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return page.addInitScript(
|
||||||
|
(args: {
|
||||||
|
permission: NotificationPermission
|
||||||
|
requestPermissionResult?: NotificationPermission
|
||||||
|
}) => {
|
||||||
|
window.Notification = window.Notification ?? {}
|
||||||
|
Object.defineProperty(window.Notification, "permission", {
|
||||||
|
value: args.permission,
|
||||||
|
writable: true,
|
||||||
|
})
|
||||||
|
Object.defineProperty(window.Notification, "requestPermission", {
|
||||||
|
value: () =>
|
||||||
|
Promise.resolve(args.requestPermissionResult ?? args.permission),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
args
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stubServiceWorker(page: Page) {
|
||||||
|
return page.addInitScript(() => {
|
||||||
|
const registration = {
|
||||||
|
pushManager: {
|
||||||
|
getSubscription() {
|
||||||
|
return Promise.resolve({ hi: "subscription" })
|
||||||
|
},
|
||||||
|
subscribe(args: Parameters<PushManager["subscribe"]>[0]) {
|
||||||
|
return Promise.resolve({ hi: "subscription" })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(navigator, "serviceWorker", {
|
||||||
|
value: {
|
||||||
|
register() {
|
||||||
|
return Promise.resolve(registration)
|
||||||
|
},
|
||||||
|
getRegistration() {
|
||||||
|
return Promise.resolve(registration)
|
||||||
|
},
|
||||||
|
addEventListener() {},
|
||||||
|
removeEventListener() {},
|
||||||
|
},
|
||||||
|
writable: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
"isbot": "^4.1.0",
|
"isbot": "^4.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"remix-pwa-monorepo": "github:remix-pwa/monorepo#main",
|
||||||
"remix-utils": "^7.6.0",
|
"remix-utils": "^7.6.0",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"ua-parser-js": "^1.0.39"
|
"ua-parser-js": "^1.0.39"
|
||||||
|
|
@ -14603,6 +14604,25 @@
|
||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/remix-pwa-monorepo": {
|
||||||
|
"resolved": "git+ssh://git@github.com/remix-pwa/monorepo.git#dda9d68b1c69642679d6ff17658f21fe24c668d6",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"packages/cli",
|
||||||
|
"packages/client",
|
||||||
|
"packages/dev",
|
||||||
|
"packages/eslint-config",
|
||||||
|
"packages/lint-staged-config",
|
||||||
|
"packages/push",
|
||||||
|
"packages/sw",
|
||||||
|
"packages/sync",
|
||||||
|
"packages/worker-runtime",
|
||||||
|
"playground"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/remix-utils": {
|
"node_modules/remix-utils": {
|
||||||
"version": "7.6.0",
|
"version": "7.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/remix-utils/-/remix-utils-7.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/remix-utils/-/remix-utils-7.6.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
"isbot": "^4.1.0",
|
"isbot": "^4.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"remix-pwa-monorepo": "github:remix-pwa/monorepo#main",
|
||||||
"remix-utils": "^7.6.0",
|
"remix-utils": "^7.6.0",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"ua-parser-js": "^1.0.39"
|
"ua-parser-js": "^1.0.39"
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,6 @@ app.get("/api", (req, res) => res.send("HI"))
|
||||||
// and your app is "just a request handler"
|
// and your app is "just a request handler"
|
||||||
app.all("*", createRequestHandler({ build }))
|
app.all("*", createRequestHandler({ build }))
|
||||||
|
|
||||||
import { register } from "register-service-worker";
|
|
||||||
|
|
||||||
register(`/service-worker.js`)
|
|
||||||
|
|
||||||
app.listen(3000, () => {
|
app.listen(3000, () => {
|
||||||
console.log("App listening on http://localhost:3000")
|
console.log("App listening on http://localhost:3000")
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,13 @@ import baseConfig from "./thebabel.config.cjs"
|
||||||
let metaPlugin = ({ types: t }) => ({
|
let metaPlugin = ({ types: t }) => ({
|
||||||
visitor: {
|
visitor: {
|
||||||
MetaProperty: (path) => {
|
MetaProperty: (path) => {
|
||||||
path.replaceWith(t.identifier("undefined"));
|
path.replaceWith(t.identifier("undefined"))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
export default babelJest.createTransformer({
|
export default babelJest.createTransformer({
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
plugins: [...baseConfig.plugins, metaPlugin],
|
plugins: [...baseConfig.plugins, metaPlugin],
|
||||||
});
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,10 @@ import { vitePlugin as remix } from "@remix-run/dev"
|
||||||
import { installGlobals } from "@remix-run/node"
|
import { installGlobals } from "@remix-run/node"
|
||||||
import { defineConfig } from "vite"
|
import { defineConfig } from "vite"
|
||||||
import tsconfigPaths from "vite-tsconfig-paths"
|
import tsconfigPaths from "vite-tsconfig-paths"
|
||||||
import { remixPWA } from '@remix-pwa/dev'
|
import { remixPWA } from "@remix-pwa/dev"
|
||||||
|
|
||||||
installGlobals()
|
installGlobals()
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [remix(), tsconfigPaths(), remixPWA()],
|
||||||
remix(),
|
|
||||||
tsconfigPaths(),
|
|
||||||
remixPWA()
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue