openpencil/tests/app/shell/support/detect.test.ts

81 lines
3.5 KiB
TypeScript
Raw Normal View History

fix: explain unsupported browsers instead of a blank window (#745) * fix: explain unsupported browsers instead of a blank window The desktop app on macOS 13 with WebKit older than Safari 17.4 opened an empty window because startup called Promise.withResolvers, which Vite lowers nothing for: build.target only rewrites syntax and never polyfills APIs, and the target itself was an implicit Vite default (#744). Make the supported baseline explicit in src/app/shell/support/baseline.ts and feed it to build.target, a lint rule that rejects newer static built-ins in browser-shipped sources, and the documented system requirements. Replace Promise.withResolvers with a createDeferred() helper. Turn src/main.ts into a small gate that checks sentinel features before dynamically importing the app, so an old engine still evaluates enough code to render platform-specific update guidance: macOS/Safari via Software Update, WebKitGTK and WebView2 on Linux and Windows, and each browser's own update path on the web, with a prefilled bug report link. Render-blocking errors during the first route are captured through app.config.errorHandler and shown the same way instead of leaving the window blank. Desktop facts come from tauri-plugin-os and a webview_version command; the bundle now declares macOS 13 as its minimum system version. * build: enforce the browser baseline from compatibility data Replace the hand-maintained list of built-ins newer than the baseline with two data-driven checks. The app and browser-shipped packages pin their TypeScript lib to ES2023, the last edition Chrome 111, Firefox 128 and Safari 16.4 implement in full, so a newer built-in such as Promise.withResolvers fails type-checking. Web APIs, which lib.dom does not version, go through eslint-plugin-compat under oxlint with the same browsers in settings.browsers, scoped to sources that ship to a browser. A unit test keeps the oxlint browser list and the tsconfig libs derived from src/app/shell/support/baseline.ts, so the three cannot drift apart. * fix: recognise production error codes in the boot observer Vue passes the error reference URL as the errorHandler info argument in production builds instead of the development string, so the observer never classified a setup or render failure as fatal in the shipped app and the boot-failure notice only appeared on the dev server. Match Vue's exported ErrorCodes in both forms, and cover the component-setup path in the E2E spec; the scenario was also verified against a production build.
2026-09-22 10:40:59 +00:00
import { describe, expect, test } from 'bun:test'
import { baselineBrowserFor, describeEnvironment, parseUserAgent } from '@/app/shell/support/detect'
const MAC_SAFARI =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Safari/605.1.15'
const MAC_WKWEBVIEW =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)'
const WINDOWS_CHROME =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
const WINDOWS_EDGE =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.78'
const LINUX_OPERA =
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 OPR/94.0.0.0'
const LINUX_FIREFOX = 'Mozilla/5.0 (X11; Linux x86_64; rv:115.0) Gecko/20100101 Firefox/115.0'
const IPHONE_CHROME =
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/110.0.5481.83 Mobile/15E148 Safari/604.1'
const IPAD_DESKTOP_SAFARI =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15'
describe('user agent detection', () => {
test: typecheck the test suites and fix what that found (#896) * build: typecheck the test suites Tests were in no TypeScript program: no tsconfig included tests/** or packages/*/tests/**, and bun strips types without checking them, so a fixture could drop a required field and keep passing until something read it. @types/bun moves to the root because it was installed per package only, and #cli-tests/* joins the paths the root config already carries. * test: fix the type errors the test suites were hiding Typechecking the tests turned up 1123 errors. Most were ordinary strictness, but some were real: `NodeChange` bound to Figma's plugin typings rather than the Kiwi codec in thirteen .fig tests, materializeInstance was called with six arguments against five so the blobs and source children were dropped, CanvasKit pixels were written to a plain object that never reached WASM, and assertions were made through accessors that do not exist, so they asserted nothing. Fixtures that had quietly lost a required field now carry it, nullable results are narrowed through the existing expectDefined helper rather than assumed, and stand-ins for CanvasKit and the editor go through one named helper instead of an unexplained cast at each site. No test was deleted, skipped, or weakened, and no `any`, non-null assertion, or ts-expect-error was introduced. * docs: record what typechecking the tests established Pins the app program's global types with an assertion rather than a note, since an unpinned types list lets any root @types package decide which platform src/** is judged against. The two environment faults that look like code regressions — Vite's dependency pre-bundle outliving a package rebuild, and heavy .fig suites failing under load — go to the development docs, where an explanation belongs. * fix: align @types/bun and keep node types resolvable when extended The root manifest declared a newer @types/bun than every package, which check:monorepo rejects, and pinning the app program's types left them unresolvable from a config that extends this one out of tree. * fix: fail the test typecheck when the compiler itself fails The gate matched diagnostics by substring, so a compiler or config failure that named no test file printed a pass while having checked nothing. Diagnostics are now split by whether they name a file: an unscoped one is the run failing and stops the gate, a test file's is a finding, and a source file's stays out by design. Also drops the parameter planComponentConstruction never read, and makes the inner-shadow verification script exit non-zero when it renders no image instead of logging and succeeding. * chore: merge master into tests-typecheck
2026-10-05 12:42:38 +00:00
test.each<[string, ReturnType<typeof parseUserAgent>]>([
fix: explain unsupported browsers instead of a blank window (#745) * fix: explain unsupported browsers instead of a blank window The desktop app on macOS 13 with WebKit older than Safari 17.4 opened an empty window because startup called Promise.withResolvers, which Vite lowers nothing for: build.target only rewrites syntax and never polyfills APIs, and the target itself was an implicit Vite default (#744). Make the supported baseline explicit in src/app/shell/support/baseline.ts and feed it to build.target, a lint rule that rejects newer static built-ins in browser-shipped sources, and the documented system requirements. Replace Promise.withResolvers with a createDeferred() helper. Turn src/main.ts into a small gate that checks sentinel features before dynamically importing the app, so an old engine still evaluates enough code to render platform-specific update guidance: macOS/Safari via Software Update, WebKitGTK and WebView2 on Linux and Windows, and each browser's own update path on the web, with a prefilled bug report link. Render-blocking errors during the first route are captured through app.config.errorHandler and shown the same way instead of leaving the window blank. Desktop facts come from tauri-plugin-os and a webview_version command; the bundle now declares macOS 13 as its minimum system version. * build: enforce the browser baseline from compatibility data Replace the hand-maintained list of built-ins newer than the baseline with two data-driven checks. The app and browser-shipped packages pin their TypeScript lib to ES2023, the last edition Chrome 111, Firefox 128 and Safari 16.4 implement in full, so a newer built-in such as Promise.withResolvers fails type-checking. Web APIs, which lib.dom does not version, go through eslint-plugin-compat under oxlint with the same browsers in settings.browsers, scoped to sources that ship to a browser. A unit test keeps the oxlint browser list and the tsconfig libs derived from src/app/shell/support/baseline.ts, so the three cannot drift apart. * fix: recognise production error codes in the boot observer Vue passes the error reference URL as the errorHandler info argument in production builds instead of the development string, so the observer never classified a setup or render failure as fatal in the shipped app and the boot-failure notice only appeared on the dev server. Match Vue's exported ErrorCodes in both forms, and cover the component-setup path in the E2E spec; the scenario was also verified against a production build.
2026-09-22 10:40:59 +00:00
[MAC_SAFARI, { os: 'macos', browser: 'safari', browserVersion: 16.3 }],
[MAC_WKWEBVIEW, { os: 'macos', browser: 'safari', browserVersion: undefined }],
[WINDOWS_CHROME, { os: 'windows', browser: 'chrome', browserVersion: 105 }],
[WINDOWS_EDGE, { os: 'windows', browser: 'edge', browserVersion: 109 }],
[LINUX_OPERA, { os: 'linux', browser: 'opera', browserVersion: 108 }],
[LINUX_FIREFOX, { os: 'linux', browser: 'firefox', browserVersion: 115 }],
[IPHONE_CHROME, { os: 'ios', browser: 'safari', browserVersion: 16.3 }],
['curl/8.0', { os: 'unknown', browser: 'unknown', browserVersion: undefined }]
])('parses %s', (userAgent, expected) => {
expect(parseUserAgent(userAgent)).toEqual(expected)
})
test('recognises iPadOS behind a desktop-class Macintosh user agent', () => {
expect(parseUserAgent(IPAD_DESKTOP_SAFARI, 5)).toEqual({
os: 'ios',
browser: 'safari',
browserVersion: 16.1
})
})
test('prefers the desktop shell facts over the WebView user agent', () => {
const env = describeEnvironment(
{ userAgent: MAC_WKWEBVIEW, maxTouchPoints: 0 },
{ platform: 'macos', osVersion: '13.7.8', webviewVersion: '18615.1.26.11.23' }
)
expect(env).toEqual({
host: 'desktop',
os: 'macos',
osVersion: '13.7.8',
browser: 'safari',
browserVersion: undefined,
webviewVersion: '18615.1.26.11.23',
userAgent: MAC_WKWEBVIEW
})
})
test('keeps the user agent operating system when the shell cannot name one', () => {
const env = describeEnvironment(
{ userAgent: LINUX_FIREFOX, maxTouchPoints: 0 },
{ platform: 'unknown' }
)
expect(env.host).toBe('desktop')
expect(env.os).toBe('linux')
})
test: typecheck the test suites and fix what that found (#896) * build: typecheck the test suites Tests were in no TypeScript program: no tsconfig included tests/** or packages/*/tests/**, and bun strips types without checking them, so a fixture could drop a required field and keep passing until something read it. @types/bun moves to the root because it was installed per package only, and #cli-tests/* joins the paths the root config already carries. * test: fix the type errors the test suites were hiding Typechecking the tests turned up 1123 errors. Most were ordinary strictness, but some were real: `NodeChange` bound to Figma's plugin typings rather than the Kiwi codec in thirteen .fig tests, materializeInstance was called with six arguments against five so the blobs and source children were dropped, CanvasKit pixels were written to a plain object that never reached WASM, and assertions were made through accessors that do not exist, so they asserted nothing. Fixtures that had quietly lost a required field now carry it, nullable results are narrowed through the existing expectDefined helper rather than assumed, and stand-ins for CanvasKit and the editor go through one named helper instead of an unexplained cast at each site. No test was deleted, skipped, or weakened, and no `any`, non-null assertion, or ts-expect-error was introduced. * docs: record what typechecking the tests established Pins the app program's global types with an assertion rather than a note, since an unpinned types list lets any root @types package decide which platform src/** is judged against. The two environment faults that look like code regressions — Vite's dependency pre-bundle outliving a package rebuild, and heavy .fig suites failing under load — go to the development docs, where an explanation belongs. * fix: align @types/bun and keep node types resolvable when extended The root manifest declared a newer @types/bun than every package, which check:monorepo rejects, and pinning the app program's types left them unresolvable from a config that extends this one out of tree. * fix: fail the test typecheck when the compiler itself fails The gate matched diagnostics by substring, so a compiler or config failure that named no test file printed a pass while having checked nothing. Diagnostics are now split by whether they name a file: an unscoped one is the run failing and stops the gate, a test file's is a finding, and a source file's stays out by design. Also drops the parameter planComponentConstruction never read, and makes the inner-shadow verification script exit non-zero when it renders no image instead of logging and succeeding. * chore: merge master into tests-typecheck
2026-10-05 12:42:38 +00:00
test.each<[string, ReturnType<typeof baselineBrowserFor>]>([
fix: explain unsupported browsers instead of a blank window (#745) * fix: explain unsupported browsers instead of a blank window The desktop app on macOS 13 with WebKit older than Safari 17.4 opened an empty window because startup called Promise.withResolvers, which Vite lowers nothing for: build.target only rewrites syntax and never polyfills APIs, and the target itself was an implicit Vite default (#744). Make the supported baseline explicit in src/app/shell/support/baseline.ts and feed it to build.target, a lint rule that rejects newer static built-ins in browser-shipped sources, and the documented system requirements. Replace Promise.withResolvers with a createDeferred() helper. Turn src/main.ts into a small gate that checks sentinel features before dynamically importing the app, so an old engine still evaluates enough code to render platform-specific update guidance: macOS/Safari via Software Update, WebKitGTK and WebView2 on Linux and Windows, and each browser's own update path on the web, with a prefilled bug report link. Render-blocking errors during the first route are captured through app.config.errorHandler and shown the same way instead of leaving the window blank. Desktop facts come from tauri-plugin-os and a webview_version command; the bundle now declares macOS 13 as its minimum system version. * build: enforce the browser baseline from compatibility data Replace the hand-maintained list of built-ins newer than the baseline with two data-driven checks. The app and browser-shipped packages pin their TypeScript lib to ES2023, the last edition Chrome 111, Firefox 128 and Safari 16.4 implement in full, so a newer built-in such as Promise.withResolvers fails type-checking. Web APIs, which lib.dom does not version, go through eslint-plugin-compat under oxlint with the same browsers in settings.browsers, scoped to sources that ship to a browser. A unit test keeps the oxlint browser list and the tsconfig libs derived from src/app/shell/support/baseline.ts, so the three cannot drift apart. * fix: recognise production error codes in the boot observer Vue passes the error reference URL as the errorHandler info argument in production builds instead of the development string, so the observer never classified a setup or render failure as fatal in the shipped app and the boot-failure notice only appeared on the dev server. Match Vue's exported ErrorCodes in both forms, and cover the component-setup path in the E2E spec; the scenario was also verified against a production build.
2026-09-22 10:40:59 +00:00
[WINDOWS_CHROME, 'chrome'],
[LINUX_OPERA, 'chrome'],
[WINDOWS_EDGE, 'edge'],
[LINUX_FIREFOX, 'firefox'],
[MAC_SAFARI, 'safari'],
[IPHONE_CHROME, 'ios'],
['curl/8.0', undefined]
])('maps %s to its baseline entry', (userAgent, expected) => {
const env = describeEnvironment({ userAgent, maxTouchPoints: 0 })
expect(baselineBrowserFor(env)).toBe(expected)
})
})