From fc6a5c4528da87bede1244ed809416976215f184 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 18 Sep 2024 17:28:51 -0400 Subject: [PATCH] iOS install instructions (barely), stupid, stupid workaround for babel config inexplicably messing with vite bundling, try not to kill anyone out of frustration --- .prettierrc | 19 +++ app/entry.server.tsx | 1 + app/root.test.tsx | 14 +- app/root.tsx | 105 ++++++++------- e2e/example.spec.ts | 42 ------ e2e/install.spec.ts | 77 +++++++++++ jest.config.ts | 3 +- package-lock.json | 171 +++++++++++++++++++----- package.json | 9 +- playwright.config.ts | 37 ++--- babel.config.cjs => thebabel.config.cjs | 0 transform.js | 2 +- 12 files changed, 331 insertions(+), 149 deletions(-) create mode 100644 .prettierrc delete mode 100644 e2e/example.spec.ts create mode 100644 e2e/install.spec.ts rename babel.config.cjs => thebabel.config.cjs (100%) diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..70c334c --- /dev/null +++ b/.prettierrc @@ -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 + } + } + ] +} \ No newline at end of file diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 45db322..c2fd369 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -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; diff --git a/app/root.test.tsx b/app/root.test.tsx index 5bb3585..a148c7c 100644 --- a/app/root.test.tsx +++ b/app/root.test.tsx @@ -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() - await waitFor(() => screen.findByText("Whoah"), { timeout: 2000 }) + await waitFor(() => screen.findByText(/Install/), { timeout: 2000 }) expect(true).toBe(true) }) diff --git a/app/root.tsx b/app/root.tsx index e14a618..75353aa 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -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 ( - - - - - - - - - {children} -
Whoah
- - - - - ); + return ( + + + + + + + + + {children} + + + + + ) +} + +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() - const message = isIOS() ? "Hey it's IOS!" : "Oh no" + return <> - return
{message}
} -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
WHY
- 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
{message}
} - -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'; -} \ No newline at end of file diff --git a/e2e/example.spec.ts b/e2e/example.spec.ts deleted file mode 100644 index a0bb636..0000000 --- a/e2e/example.spec.ts +++ /dev/null @@ -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() - }) -}) \ No newline at end of file diff --git a/e2e/install.spec.ts b/e2e/install.spec.ts new file mode 100644 index 0000000..4845d6f --- /dev/null +++ b/e2e/install.spec.ts @@ -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() + }) + }) +}) diff --git a/jest.config.ts b/jest.config.ts index fc12253..f811af5 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -4,7 +4,8 @@ const ignorePatterns = [ "\\/\\.vscode\\/", "\\/\\.tmp\\/", "\\/\\.cache\\/", - "data" + "data", + "e2e" ]; module.exports = { diff --git a/package-lock.json b/package-lock.json index b2b0249..36c9e3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index ad19674..318152e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/playwright.config.ts b/playwright.config.ts index c8dbb37..3769e80 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -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, }, }); diff --git a/babel.config.cjs b/thebabel.config.cjs similarity index 100% rename from babel.config.cjs rename to thebabel.config.cjs diff --git a/transform.js b/transform.js index 262a252..b60b3c8 100644 --- a/transform.js +++ b/transform.js @@ -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`