iOS install instructions (barely), stupid, stupid workaround for babel config inexplicably messing with vite bundling, try not to kill anyone out of frustration
Test / test (push) Successful in 30s Details

This commit is contained in:
Jeff 2024-09-18 17:28:51 -04:00
parent c2af5c40a9
commit fc6a5c4528
12 changed files with 331 additions and 149 deletions

19
.prettierrc Normal file
View File

@ -0,0 +1,19 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": false,
"overrides": [
{
"files": [
"*.test.ts",
"*.test.js",
"*.spec.ts",
"*.spec.js"
],
"options": {
"maxLineLength": 9999999
}
}
]
}

View File

@ -11,6 +11,7 @@ import { createReadableStreamFromReadable } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { isbot } from "isbot";
import { renderToPipeableStream } from "react-dom/server";
import React from "react";
const ABORT_DELAY = 5_000;

View File

@ -1,4 +1,5 @@
import { act, render, screen, waitFor } from '@testing-library/react'
import MatchMediaMock from 'jest-matchmedia-mock';
import { createRemixStub, RemixStubProps } from "@remix-run/testing"
import App from './root'
@ -12,18 +13,29 @@ describe("root", () => {
path: "/",
meta: () => ([]),
links: () => ([]),
loader: () => ({ isSupported: true}),
Component: App
}
])
})
let matchMedia: MatchMediaMock
beforeAll(() => {
matchMedia = new MatchMediaMock();
})
afterEach(() => {
matchMedia.clear();
})
describe("when user is on iOS", () => {
describe("version 16.4 or higher", () => {
describe("and tack up now is not already installed on their device", () => {
test("they are instructed to install tack up now", async () => {
render(<RemixStub/>)
await waitFor(() => screen.findByText<HTMLElement>("Whoah"), { timeout: 2000 })
await waitFor(() => screen.findByText<HTMLElement>(/Install/), { timeout: 2000 })
expect(true).toBe(true)
})

View File

@ -1,62 +1,71 @@
import { json, LoaderFunctionArgs } from "@remix-run/node"
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import React from "react";
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
} from "@remix-run/react"
import React, { useEffect } from "react"
import UAParser from "ua-parser-js"
import versionAtLeast from "semver/functions/gte"
import coerceSemver from "semver/functions/coerce"
// import { LandingMessage } from "./root.client"
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<div>Whoah</div>
<ScrollRestoration />
<Scripts />
</body>
</html>
);
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}
export const loader = async ({ request }: LoaderFunctionArgs) => {
const userAgent = request.headers.get("user-agent")
const os = new UAParser(userAgent ?? "").getOS()
const isSupported =
os.name !== "iOS" ||
versionAtLeast(coerceSemver(os.version) ?? "0.0.0", "16.4.0")
return json({ isSupported })
}
export default function App() {
const { isSupported } = useLoaderData<typeof loader>()
const message = isIOS() ? "Hey it's IOS!" : "Oh no"
return <><Outlet/><LandingMessage isSupported={isSupported}/></>
return <div>{message}</div>
}
function isIOS() {
function LandingMessage({ isSupported }: { isSupported: boolean }) {
if (typeof window === "undefined") return false
useEffect(() => {
console.log("WTF")
}, [])
console.log("User Agent", window.navigator.userAgent)
if (typeof window === "undefined") return <div>WHY</div>
return [
'iPad Simulator',
'iPhone Simulator',
'iPod Simulator',
'iPad',
'iPhone',
'iPod'
].includes(window.navigator.platform)
// iPad on iOS 13 detection
|| (navigator.userAgent.includes("Mac") && "ontouchend" in document)
const isRunningPWA = "standalone" in navigator && navigator.standalone || matchMedia("(dislay-mode: standalone)").matches
const message = isRunningPWA
? "Enable notifications"
: isSupported
? "Install Tack Up Now!"
: "Sorry, your device doesn't support Tack Up Now! :("
return <div>{message}</div>
}
function iOSversion() {
if (window.indexedDB) { return 'iOS 8 and up'; }
if (window.SpeechSynthesisUtterance) { return 'iOS 7'; }
if ((window as any).webkitAudioContext) { return 'iOS 6'; }
if ((window as any).matchMedia) { return 'iOS 5'; }
if (window.history && 'pushState' in window.history) { return 'iOS 4'; }
return 'iOS 3 or earlier';
}

View File

@ -1,42 +0,0 @@
import { test, expect } from '@playwright/test';
const { describe, beforeEach, skip } = test
function webkitOnly() {
beforeEach(async ({ browserName }) => {
if (browserName !== 'webkit') skip()
})
}
describe("when user is on iOS", () => {
webkitOnly()
describe("version 16.4 or higher", () => {
describe("and tack up now is not already installed on their device", () => {
test("they are instructed to install tack up now", async ({ page }) => {
await page.goto("/")
// await page.waitForSelector("text=Hey it's IOS!",{timeout: 500})
console.log(await page.innerHTML("body"))
await expect(page.getByText(/Hey it's IOS!/)).toBeAttached()
})
})
})
test("version 16.3 and under they are informed that their iOS version isn't supported", async ({ page }) => {
await page.goto("/")
// await page.waitForSelector("text=Oh no",{timeout: 500})
console.log(await page.innerHTML("body"))
const donut = await page.getByText("Oh no")
console.log("is visible", await donut.isVisible())
await expect(donut).toBeVisible()
})
})

77
e2e/install.spec.ts Normal file
View File

@ -0,0 +1,77 @@
import { test, expect } from "@playwright/test"
const { describe, beforeEach, skip, use } = test
function webkitOnly() {
beforeEach(async ({ browserName }) => {
if (browserName !== "webkit") skip()
})
}
describe("when user is on iOS", () => {
webkitOnly()
describe("version 16.4 or higher", () => {
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",
})
test("and tack up now is not running as a PWA, they are instructed to install tack up now", async ({ page }) => {
await page.goto("/")
const installText = await page.getByText(/Install Tack Up Now!/)
await expect(installText).toBeAttached()
})
describe("and tack up now is running as a PWA", () => {
beforeEach(async ({ page }) => {
await page.addInitScript(() => (window.navigator as any)["standalone"] = true)
})
test("and notifications aren't enabled, they are asked to enable notifications", async ({ page, browser }) => {
await page.goto("/")
const notificationText = await page.getByText(/Enable notifications/)
await expect(notificationText).toBeAttached()
})
// describe("and notifications are enabled", () => {
// beforeEach(async ({ page }) => {
// await page.addInitScript(() => (window.persmis))
// })
// test("they aren't asked to enable notifications", async ({ browser }) => {
// const context = await browser.newContext({
// permissions: ['notification'],
// });
// const page = await context.newPage()
// await page.goto("/")
// const notificationText = await page.getByText(/Enable notifications/)
// await expect(notificationText).not.toBeAttached()
// })
// })
})
})
describe("version 16.3 and under", () => {
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",
})
test("version 16.3 and under they are informed that their iOS version isn't supported", async ({ page }) => {
await page.goto("/")
const sorryText = await page.getByText("Sorry, your device doesn't support Tack Up Now! :(")
await expect(sorryText).toBeVisible()
})
})
})

View File

@ -4,7 +4,8 @@ const ignorePatterns = [
"\\/\\.vscode\\/",
"\\/\\.tmp\\/",
"\\/\\.cache\\/",
"data"
"data",
"e2e"
];
module.exports = {

171
package-lock.json generated
View File

@ -14,7 +14,10 @@
"express": "^4.19.2",
"isbot": "^4.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"remix-utils": "^7.6.0",
"semver": "^7.6.3",
"ua-parser-js": "^1.0.39"
},
"devDependencies": {
"@babel/core": "^7.24.5",
@ -35,7 +38,9 @@
"@types/node": "^20.14.5",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@types/semver": "^7.5.8",
"@types/supertest": "^6.0.2",
"@types/ua-parser-js": "^0.7.39",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"babel-jest": "^29.7.0",
@ -48,7 +53,9 @@
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-matchmedia-mock": "^1.1.0",
"jest-watch-typeahead": "^2.2.2",
"prettier": "^3.3.3",
"supertest": "^7.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.1.6",
@ -3467,6 +3474,21 @@
}
}
},
"node_modules/@remix-run/dev/node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/@remix-run/express": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/@remix-run/express/-/express-2.9.1.tgz",
@ -4795,6 +4817,12 @@
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
"dev": true
},
"node_modules/@types/ua-parser-js": {
"version": "0.7.39",
"resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz",
"integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==",
"dev": true
},
"node_modules/@types/unist": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
@ -10218,6 +10246,18 @@
"integrity": "sha512-wRiUsea88TjKDc4FBEn+sLvIDesp6brMbGWnJGjew2waAc9evdhja/2LvePc898HJbHw0L+MTWy7NhpnELAvLQ==",
"dev": true
},
"node_modules/jest-matchmedia-mock": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jest-matchmedia-mock/-/jest-matchmedia-mock-1.1.0.tgz",
"integrity": "sha512-REnJRsOSCMpGAlkxmvVTqEBpregyFVi9MPEH3N83W1yLKzDdNehtCkcdDZduXq74PLtfI+11NyM4zKCK5ynV9g==",
"dev": true,
"engines": {
"node": ">=10"
},
"peerDependencies": {
"jest": ">=13"
}
},
"node_modules/jest-message-util": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
@ -13284,15 +13324,15 @@
}
},
"node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=10.13.0"
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
@ -13858,6 +13898,72 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/remix-utils": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/remix-utils/-/remix-utils-7.6.0.tgz",
"integrity": "sha512-BPhCUEy+nwrhDDDg2v3+LFSszV6tluMbeSkbffj2o4tqZxt5Kn69Y9sNpGxYLAj8gjqeYDuxjv55of+gYnnykA==",
"dependencies": {
"type-fest": "^4.3.3"
},
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@remix-run/cloudflare": "^2.0.0",
"@remix-run/deno": "^2.0.0",
"@remix-run/node": "^2.0.0",
"@remix-run/react": "^2.0.0",
"@remix-run/router": "^1.7.2",
"crypto-js": "^4.1.1",
"intl-parse-accept-language": "^1.0.0",
"is-ip": "^5.0.1",
"react": "^18.0.0",
"zod": "^3.22.4"
},
"peerDependenciesMeta": {
"@remix-run/cloudflare": {
"optional": true
},
"@remix-run/deno": {
"optional": true
},
"@remix-run/node": {
"optional": true
},
"@remix-run/react": {
"optional": true
},
"@remix-run/router": {
"optional": true
},
"crypto-js": {
"optional": true
},
"intl-parse-accept-language": {
"optional": true
},
"is-ip": {
"optional": true
},
"react": {
"optional": true
},
"zod": {
"optional": true
}
}
},
"node_modules/remix-utils/node_modules/type-fest": {
"version": "4.26.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz",
"integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -14172,13 +14278,9 @@
}
},
"node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"bin": {
"semver": "bin/semver.js"
},
@ -14186,24 +14288,6 @@
"node": ">=10"
}
},
"node_modules/semver/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/semver/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
@ -15392,6 +15476,31 @@
"node": ">=14.17"
}
},
"node_modules/ua-parser-js": {
"version": "1.0.39",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.39.tgz",
"integrity": "sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
},
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
}
],
"bin": {
"ua-parser-js": "script/cli.js"
},
"engines": {
"node": "*"
}
},
"node_modules/ufo": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz",

View File

@ -21,7 +21,10 @@
"express": "^4.19.2",
"isbot": "^4.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"remix-utils": "^7.6.0",
"semver": "^7.6.3",
"ua-parser-js": "^1.0.39"
},
"devDependencies": {
"@babel/core": "^7.24.5",
@ -42,7 +45,9 @@
"@types/node": "^20.14.5",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@types/semver": "^7.5.8",
"@types/supertest": "^6.0.2",
"@types/ua-parser-js": "^0.7.39",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"babel-jest": "^29.7.0",
@ -55,7 +60,9 @@
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-matchmedia-mock": "^1.1.0",
"jest-watch-typeahead": "^2.2.2",
"prettier": "^3.3.3",
"supertest": "^7.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.1.6",

View File

@ -11,6 +11,7 @@ import { defineConfig, devices } from '@playwright/test';
*/
export default defineConfig({
testDir: './e2e',
testMatch: "*.spec.ts",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
@ -24,8 +25,8 @@ export default defineConfig({
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://127.0.0.1:9000',
baseURL: 'http://127.0.0.1:5173',
ignoreHTTPSErrors: true,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
@ -36,42 +37,30 @@ export default defineConfig({
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 12'] },
},
],
/* Run your local dev server before starting the tests */
webServer: {
command: 'docker compose down && docker compose build --no-cache && docker compose up',
url: 'http://127.0.0.1:9000',
command: 'npm run dev',
url: 'http://127.0.0.1:5173',
reuseExistingServer: !process.env.CI,
},
});

View File

@ -1,6 +1,6 @@
import babelJest from "babel-jest"
import baseConfig from "./babel.config.cjs"
import baseConfig from "./thebabel.config.cjs"
/**
* Replace `import.meta` with `undefined`