blob: c2d906a87a1eb25373c1943d0813eaba58975cc8 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Browser APIs jsdom does not implement, stubbed for the Vitest runner.
*
* Karma ran the suite in a real Chrome, so every DOM API the app or Ionic touched simply
* existed. Vitest's default environment is jsdom, which is a DOM *implementation* rather than a
* browser: it has no layout, so anything measuring or scrolling is absent, and a handful of
* newer globals are simply not there. Each stub below exists because a real spec failed without
* it, and each is written to be honest about what it does not do.
*
* The alternative to stubbing is Vitest's browser mode, which runs the suite in a real Chrome
* via Playwright and needs none of this. That is the better long-run answer and is recorded as
* the open question in DOCS/adr/0004-vitest-migration.md — it is a slower, heavier runner, and
* choosing it should be a decision made against a migrated suite rather than a precondition for
* migrating.
*/
// `CSS.escape` — used by specs to build `[data-testid="..."]` selectors safely. jsdom has no
// `CSS` object at all. The polyfill covers the identifier characters those selectors contain;
// it is not a complete CSSOM `escape` implementation and is not used by application code.
if (globalThis.CSS === undefined) {
(globalThis as { CSS?: unknown }).CSS = {};
}
const cssObject = globalThis.CSS as unknown as { escape?: (value: string) => string };
cssObject.escape ??= (value: string) => String(value).replaceAll(/[^\w-]/g, (ch) => `\\${ch}`);
// `Element.prototype.scrollTo` — Ionic's `ion-segment` scrolls the active button into view on
// render. jsdom has no layout and no scrolling, so the method is absent and the call throws
// during change detection, failing specs that never mention scrolling. A no-op is correct here:
// there is no viewport for the result to be observable in.
const noop = () => {
/* jsdom has no viewport, so there is nothing for a scroll to do. */
};
Element.prototype.scrollTo ??= noop;
Element.prototype.scrollIntoView ??= noop;
// `window.matchMedia` — read by `ThemeService` to follow the OS colour-scheme preference.
// jsdom's implementation lacks the `EventTarget` half, so a listener registration throws.
// Defaulting `matches` to `false` means "no preference expressed", which is what a spec that
// does not stub it should see.
if (window !== undefined && typeof window.matchMedia !== 'function') {
window.matchMedia = ((query: string) => ({
matches: false,
media: query,
onchange: null,
addEventListener: noop,
removeEventListener: noop,
addListener: noop,
removeListener: noop,
dispatchEvent: () => false,
})) as typeof window.matchMedia;
}