* 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.
174 lines
6.3 KiB
TypeScript
174 lines
6.3 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
|
|
import type { SupportEnvironment } from '@/app/shell/support/detect'
|
|
import {
|
|
SYSTEM_REQUIREMENTS_URL,
|
|
WEBVIEW2_DOWNLOAD_URL,
|
|
bootFailureNotice,
|
|
unsupportedNotice
|
|
} from '@/app/shell/support/guidance'
|
|
|
|
const MISSING = ['String.prototype.isWellFormed']
|
|
|
|
function environment(overrides: Partial<SupportEnvironment>): SupportEnvironment {
|
|
return {
|
|
host: 'browser',
|
|
os: 'unknown',
|
|
browser: 'unknown',
|
|
userAgent: 'test-agent',
|
|
...overrides
|
|
}
|
|
}
|
|
|
|
function reportLink(notice: ReturnType<typeof unsupportedNotice>): URL {
|
|
const link = notice.links.find((entry) => entry.label === 'Report a problem')
|
|
expect(link).toBeDefined()
|
|
return new URL(link?.url ?? '')
|
|
}
|
|
|
|
describe('unsupported engine guidance', () => {
|
|
test('tells a desktop Mac on a supported macOS to install Software Update', () => {
|
|
const notice = unsupportedNotice(
|
|
environment({ host: 'desktop', os: 'macos', browser: 'safari', osVersion: '13.7.8' }),
|
|
MISSING
|
|
)
|
|
expect(notice.heading).toBe('Update macOS to run OpenPencil')
|
|
expect(notice.paragraphs[0]).toContain('macOS 13.7.8')
|
|
expect(notice.paragraphs[1]).toContain('Software Update')
|
|
expect(reportLink(notice).searchParams.get('platform')).toBe('macOS (Tauri)')
|
|
})
|
|
|
|
test('tells a desktop Mac below the minimum macOS to upgrade the OS', () => {
|
|
const notice = unsupportedNotice(
|
|
environment({ host: 'desktop', os: 'macos', browser: 'safari', osVersion: '12.6.4' }),
|
|
MISSING
|
|
)
|
|
expect(notice.heading).toBe('OpenPencil needs macOS 13 or later')
|
|
})
|
|
|
|
test('names the WebKitGTK package on Linux desktops', () => {
|
|
const notice = unsupportedNotice(
|
|
environment({ host: 'desktop', os: 'linux', browser: 'safari', webviewVersion: '2.38.6' }),
|
|
MISSING
|
|
)
|
|
expect(notice.heading).toBe('Update WebKitGTK to run OpenPencil')
|
|
expect(notice.paragraphs[0]).toContain('2.38.6')
|
|
expect(notice.paragraphs[1]).toContain('webkit2gtk-4.1')
|
|
})
|
|
|
|
test('links the WebView2 runtime on Windows desktops', () => {
|
|
const notice = unsupportedNotice(
|
|
environment({ host: 'desktop', os: 'windows', browser: 'edge', browserVersion: 100 }),
|
|
MISSING
|
|
)
|
|
expect(notice.heading).toBe('Update WebView2 to run OpenPencil')
|
|
expect(notice.links.map((link) => link.url)).toContain(WEBVIEW2_DOWNLOAD_URL)
|
|
})
|
|
|
|
test.each([
|
|
[
|
|
'chrome',
|
|
105,
|
|
'Update Chrome to run OpenPencil',
|
|
'Chrome 105 detected. OpenPencil needs Chrome 111 or later.'
|
|
],
|
|
[
|
|
'edge',
|
|
109,
|
|
'Update Edge to run OpenPencil',
|
|
'Edge 109 detected. OpenPencil needs Edge 111 or later.'
|
|
],
|
|
[
|
|
'firefox',
|
|
115,
|
|
'Update Firefox to run OpenPencil',
|
|
'Firefox 115 detected. OpenPencil needs Firefox 128 or later.'
|
|
],
|
|
[
|
|
'safari',
|
|
16.2,
|
|
'Update Safari to run OpenPencil',
|
|
'Safari 16.2 detected. OpenPencil needs Safari 16.4 or later.'
|
|
],
|
|
[
|
|
'opera',
|
|
108,
|
|
'Update Opera to run OpenPencil',
|
|
'Opera 108 detected. OpenPencil needs Opera built on Chrome 111 or later.'
|
|
]
|
|
] as const)(
|
|
'names the outdated %s version and the minimum',
|
|
(browser, version, heading, first) => {
|
|
const notice = unsupportedNotice(
|
|
environment({ os: 'windows', browser, browserVersion: version }),
|
|
MISSING
|
|
)
|
|
expect(notice.heading).toBe(heading)
|
|
expect(notice.paragraphs[0]).toBe(first)
|
|
expect(notice.paragraphs[1]).not.toHaveLength(0)
|
|
}
|
|
)
|
|
|
|
test('sends every iOS browser to the system software update', () => {
|
|
const notice = unsupportedNotice(
|
|
environment({ os: 'ios', browser: 'safari', browserVersion: 16.1 }),
|
|
MISSING
|
|
)
|
|
expect(notice.heading).toBe('Update iOS to run OpenPencil')
|
|
expect(notice.paragraphs[0]).toContain('iOS 16.4')
|
|
})
|
|
|
|
test('does not ask a browser that already meets the baseline to update', () => {
|
|
const notice = unsupportedNotice(
|
|
environment({ os: 'windows', browser: 'chrome', browserVersion: 120 }),
|
|
MISSING
|
|
)
|
|
expect(notice.heading).toBe('A required browser feature is unavailable')
|
|
expect(notice.paragraphs[0]).toContain('Chrome 120')
|
|
expect(notice.paragraphs[0]).toContain('String.prototype.isWellFormed')
|
|
})
|
|
|
|
test('falls back to the full requirements for an unknown browser', () => {
|
|
const notice = unsupportedNotice(environment({}), MISSING)
|
|
expect(notice.heading).toBe("OpenPencil can't run in this browser")
|
|
expect(notice.paragraphs[0]).toBe(
|
|
'OpenPencil needs Chrome 111, Edge 111, Firefox 128, or Safari 16.4 or later.'
|
|
)
|
|
})
|
|
|
|
test('prefills the bug report with the missing features and environment', () => {
|
|
const notice = unsupportedNotice(
|
|
environment({ os: 'linux', browser: 'firefox', browserVersion: 115 }),
|
|
MISSING
|
|
)
|
|
const url = reportLink(notice)
|
|
expect(url.origin + url.pathname).toBe('https://github.com/open-pencil/open-pencil/issues/new')
|
|
expect(url.searchParams.get('template')).toBe('bug_report.yml')
|
|
expect(url.searchParams.get('platform')).toBe('Web (app.openpencil.dev)')
|
|
expect(url.searchParams.get('browser')).toBe('test-agent')
|
|
expect(url.searchParams.get('logs')).toContain('Missing: String.prototype.isWellFormed')
|
|
expect(url.searchParams.get('logs')).toContain('Browser: Firefox 115')
|
|
expect(notice.links.map((link) => link.url)).toContain(SYSTEM_REQUIREMENTS_URL)
|
|
})
|
|
})
|
|
|
|
describe('boot failure guidance', () => {
|
|
test('explains a blank window and carries the error into the details', () => {
|
|
const error = new TypeError('Promise.withResolvers is not a function')
|
|
const notice = bootFailureNotice(
|
|
environment({ host: 'desktop', os: 'macos', browser: 'safari', osVersion: '13.7.8' }),
|
|
error
|
|
)
|
|
expect(notice.heading).toBe("OpenPencil couldn't start")
|
|
expect(notice.paragraphs[1]).toContain('Safari updates')
|
|
expect(notice.details[0]).toBe('TypeError: Promise.withResolvers is not a function')
|
|
expect(reportLink(notice).searchParams.get('logs')).toContain('OS: macos 13.7.8')
|
|
})
|
|
|
|
test('states the browser requirements for a web boot failure', () => {
|
|
const notice = bootFailureNotice(environment({ os: 'windows', browser: 'chrome' }), 'boom')
|
|
expect(notice.paragraphs[1]).toContain('Chrome 111')
|
|
expect(notice.details[0]).toBe('boom')
|
|
})
|
|
})
|