@gjsify/dom-elements 0.4.0 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/types/location-stub.d.ts +1 -1
- package/package.json +78 -75
- package/src/attr.ts +0 -61
- package/src/character-data.ts +0 -79
- package/src/comment.ts +0 -31
- package/src/document-fragment.ts +0 -137
- package/src/document.ts +0 -103
- package/src/dom-matrix.ts +0 -109
- package/src/dom-token-list.ts +0 -140
- package/src/element.ts +0 -316
- package/src/font-face.ts +0 -97
- package/src/gst-time.ts +0 -26
- package/src/html-canvas-element.ts +0 -90
- package/src/html-element.ts +0 -502
- package/src/html-image-element.spec.ts +0 -285
- package/src/html-image-element.ts +0 -295
- package/src/html-media-element.ts +0 -123
- package/src/html-video-element.ts +0 -143
- package/src/image.ts +0 -31
- package/src/index.spec.ts +0 -914
- package/src/index.ts +0 -39
- package/src/intersection-observer.ts +0 -42
- package/src/location-stub.ts +0 -20
- package/src/match-media.ts +0 -32
- package/src/mutation-observer.ts +0 -39
- package/src/named-node-map.ts +0 -159
- package/src/namespace-uri.ts +0 -11
- package/src/node-list.ts +0 -52
- package/src/node-type.ts +0 -14
- package/src/node.ts +0 -280
- package/src/property-symbol.ts +0 -23
- package/src/register/canvas.ts +0 -23
- package/src/register/document.ts +0 -64
- package/src/register/font-face.ts +0 -18
- package/src/register/helpers.ts +0 -15
- package/src/register/image.ts +0 -8
- package/src/register/location.ts +0 -6
- package/src/register/match-media.ts +0 -6
- package/src/register/navigator.ts +0 -6
- package/src/register/observers.ts +0 -10
- package/src/register.spec.ts +0 -136
- package/src/register.ts +0 -13
- package/src/resize-observer.ts +0 -28
- package/src/stubs.spec.ts +0 -284
- package/src/test.browser.mts +0 -686
- package/src/test.mts +0 -9
- package/src/text.ts +0 -67
- package/src/types/i-html-image-element.ts +0 -44
- package/src/types/image-data.ts +0 -12
- package/src/types/index.ts +0 -3
- package/src/types/predefined-color-space.ts +0 -1
- package/tsconfig.json +0 -37
- package/tsconfig.tsbuildinfo +0 -1
package/src/property-symbol.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// Adapted from happy-dom (refs/happy-dom/packages/happy-dom/src/PropertySymbol.ts)
|
|
2
|
-
// Copyright (c) David Ortner (capricorn86). MIT license.
|
|
3
|
-
// Modifications: Minimal subset for gjsify — only symbols needed by Node/Element/HTMLElement
|
|
4
|
-
|
|
5
|
-
// Node
|
|
6
|
-
export const nodeType = Symbol('nodeType');
|
|
7
|
-
export const parentNode = Symbol('parentNode');
|
|
8
|
-
export const childNodesList = Symbol('childNodesList');
|
|
9
|
-
export const elementChildren = Symbol('elementChildren');
|
|
10
|
-
export const isConnected = Symbol('isConnected');
|
|
11
|
-
|
|
12
|
-
// Element
|
|
13
|
-
export const tagName = Symbol('tagName');
|
|
14
|
-
export const localName = Symbol('localName');
|
|
15
|
-
export const namespaceURI = Symbol('namespaceURI');
|
|
16
|
-
export const prefix = Symbol('prefix');
|
|
17
|
-
export const attributes = Symbol('attributes');
|
|
18
|
-
export const propertyEventListeners = Symbol('propertyEventListeners');
|
|
19
|
-
|
|
20
|
-
// Attr
|
|
21
|
-
export const name = Symbol('name');
|
|
22
|
-
export const value = Symbol('value');
|
|
23
|
-
export const ownerElement = Symbol('ownerElement');
|
package/src/register/canvas.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// Registers: HTMLCanvasElement, CanvasRenderingContext2D, DOMMatrix,
|
|
2
|
-
// DOMMatrixReadOnly + the '2d' context factory.
|
|
3
|
-
|
|
4
|
-
import { CanvasRenderingContext2D } from '@gjsify/canvas2d-core';
|
|
5
|
-
|
|
6
|
-
import { HTMLCanvasElement } from '../html-canvas-element.js';
|
|
7
|
-
import { DOMMatrix, DOMMatrixReadOnly } from '../dom-matrix.js';
|
|
8
|
-
import { defineGlobal } from './helpers.js';
|
|
9
|
-
|
|
10
|
-
defineGlobal('HTMLCanvasElement', HTMLCanvasElement);
|
|
11
|
-
defineGlobal('CanvasRenderingContext2D', CanvasRenderingContext2D);
|
|
12
|
-
defineGlobal('DOMMatrix', DOMMatrix);
|
|
13
|
-
defineGlobal('DOMMatrixReadOnly', DOMMatrixReadOnly);
|
|
14
|
-
|
|
15
|
-
// Register the '2d' context factory on HTMLCanvasElement.
|
|
16
|
-
const CANVAS2D_KEY = Symbol.for('gjsify_canvas2d_context');
|
|
17
|
-
HTMLCanvasElement.registerContextFactory('2d', (canvas, options) => {
|
|
18
|
-
const existing = (canvas as any)[CANVAS2D_KEY];
|
|
19
|
-
if (existing) return existing;
|
|
20
|
-
const ctx = new CanvasRenderingContext2D(canvas as any, options);
|
|
21
|
-
(canvas as any)[CANVAS2D_KEY] = ctx;
|
|
22
|
-
return ctx;
|
|
23
|
-
});
|
package/src/register/document.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
// Registers: document, Text, Comment, DocumentFragment, DOMTokenList
|
|
2
|
-
// + browser environment globals: self, window, Window, focus, blur, top,
|
|
3
|
-
// alert, devicePixelRatio, addEventListener/removeEventListener/dispatchEvent
|
|
4
|
-
|
|
5
|
-
import { EventTarget as OurEventTarget } from '@gjsify/dom-events';
|
|
6
|
-
|
|
7
|
-
import { Comment } from '../comment.js';
|
|
8
|
-
import { document } from '../document.js';
|
|
9
|
-
import { DocumentFragment } from '../document-fragment.js';
|
|
10
|
-
import { DOMTokenList } from '../dom-token-list.js';
|
|
11
|
-
import { Text } from '../text.js';
|
|
12
|
-
import { defineGlobal, defineGlobalIfMissing } from './helpers.js';
|
|
13
|
-
|
|
14
|
-
defineGlobal('Text', Text);
|
|
15
|
-
defineGlobal('Comment', Comment);
|
|
16
|
-
defineGlobal('DocumentFragment', DocumentFragment);
|
|
17
|
-
defineGlobal('DOMTokenList', DOMTokenList);
|
|
18
|
-
defineGlobal('document', document);
|
|
19
|
-
|
|
20
|
-
// self — three.js checks `typeof self !== 'undefined'` for animation context
|
|
21
|
-
defineGlobalIfMissing('self', globalThis);
|
|
22
|
-
|
|
23
|
-
// window + Window — Excalibur's _applyDisplayMode uses `this.parent instanceof Window`
|
|
24
|
-
class Window {}
|
|
25
|
-
defineGlobalIfMissing('Window', Window);
|
|
26
|
-
defineGlobalIfMissing('window', globalThis);
|
|
27
|
-
|
|
28
|
-
// window.focus() / window.blur() stubs
|
|
29
|
-
defineGlobalIfMissing('focus', () => {});
|
|
30
|
-
defineGlobalIfMissing('blur', () => {});
|
|
31
|
-
|
|
32
|
-
// globalThis.addEventListener / removeEventListener / dispatchEvent
|
|
33
|
-
if (typeof (globalThis as any).addEventListener !== 'function') {
|
|
34
|
-
const _globalEventTarget = new OurEventTarget();
|
|
35
|
-
(globalThis as any).__gjsify_globalEventTarget = _globalEventTarget;
|
|
36
|
-
(globalThis as any).addEventListener = (type: string, listener: any, options?: any) =>
|
|
37
|
-
_globalEventTarget.addEventListener(type, listener, options);
|
|
38
|
-
(globalThis as any).removeEventListener = (type: string, listener: any, options?: any) =>
|
|
39
|
-
_globalEventTarget.removeEventListener(type, listener, options);
|
|
40
|
-
(globalThis as any).dispatchEvent = (event: Event) =>
|
|
41
|
-
_globalEventTarget.dispatchEvent(event as any);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// devicePixelRatio — defaults to 1 (no HiDPI scaling in GTK GL context)
|
|
45
|
-
defineGlobalIfMissing('devicePixelRatio', 1);
|
|
46
|
-
|
|
47
|
-
// scrollX/scrollY — always 0 in a GTK widget (no page scrolling). Excalibur's
|
|
48
|
-
// getPosition() does `rect.x + window.scrollX`, producing NaN if scrollX is
|
|
49
|
-
// undefined and breaking all pointer coordinates.
|
|
50
|
-
defineGlobalIfMissing('scrollX', 0);
|
|
51
|
-
defineGlobalIfMissing('scrollY', 0);
|
|
52
|
-
defineGlobalIfMissing('pageXOffset', 0);
|
|
53
|
-
defineGlobalIfMissing('pageYOffset', 0);
|
|
54
|
-
|
|
55
|
-
// alert — stub redirecting to console.error
|
|
56
|
-
defineGlobalIfMissing('alert', (...args: unknown[]) => console.error('alert:', ...args));
|
|
57
|
-
|
|
58
|
-
// window.top — prevents Excalibur's iframe detection from crashing
|
|
59
|
-
if (typeof (globalThis as any).top === 'undefined') {
|
|
60
|
-
Object.defineProperty(globalThis, 'top', {
|
|
61
|
-
get: () => globalThis,
|
|
62
|
-
configurable: true,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// Registers: FontFace + patches document.fonts
|
|
2
|
-
|
|
3
|
-
import { FontFace, FontFaceSet } from '../font-face.js';
|
|
4
|
-
import { defineGlobalIfMissing } from './helpers.js';
|
|
5
|
-
|
|
6
|
-
defineGlobalIfMissing('FontFace', FontFace);
|
|
7
|
-
if (typeof (globalThis as any).FontFace === 'undefined') {
|
|
8
|
-
(globalThis as any).FontFace = FontFace;
|
|
9
|
-
}
|
|
10
|
-
// Patch document.fonts stub onto the existing document object
|
|
11
|
-
const _doc = (globalThis as any).document;
|
|
12
|
-
if (_doc && typeof _doc.fonts === 'undefined') {
|
|
13
|
-
Object.defineProperty(_doc, 'fonts', {
|
|
14
|
-
value: new FontFaceSet(),
|
|
15
|
-
configurable: true,
|
|
16
|
-
writable: true,
|
|
17
|
-
});
|
|
18
|
-
}
|
package/src/register/helpers.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/** Unconditionally expose a DOM class on `globalThis` (writable + configurable). */
|
|
2
|
-
export function defineGlobal(name: string, value: unknown): void {
|
|
3
|
-
Object.defineProperty(globalThis, name, {
|
|
4
|
-
value,
|
|
5
|
-
writable: true,
|
|
6
|
-
configurable: true,
|
|
7
|
-
});
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** Only set the global if it hasn't already been defined. */
|
|
11
|
-
export function defineGlobalIfMissing(name: string, value: unknown): void {
|
|
12
|
-
if (typeof (globalThis as any)[name] === 'undefined') {
|
|
13
|
-
defineGlobal(name, value);
|
|
14
|
-
}
|
|
15
|
-
}
|
package/src/register/image.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// Registers: Image, HTMLImageElement
|
|
2
|
-
|
|
3
|
-
import { HTMLImageElement } from '../html-image-element.js';
|
|
4
|
-
import { Image } from '../image.js';
|
|
5
|
-
import { defineGlobal } from './helpers.js';
|
|
6
|
-
|
|
7
|
-
defineGlobal('HTMLImageElement', HTMLImageElement);
|
|
8
|
-
defineGlobal('Image', Image);
|
package/src/register/location.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// Registers: MutationObserver, ResizeObserver, IntersectionObserver
|
|
2
|
-
|
|
3
|
-
import { IntersectionObserver } from '../intersection-observer.js';
|
|
4
|
-
import { MutationObserver } from '../mutation-observer.js';
|
|
5
|
-
import { ResizeObserver } from '../resize-observer.js';
|
|
6
|
-
import { defineGlobal } from './helpers.js';
|
|
7
|
-
|
|
8
|
-
defineGlobal('MutationObserver', MutationObserver);
|
|
9
|
-
defineGlobal('ResizeObserver', ResizeObserver);
|
|
10
|
-
defineGlobal('IntersectionObserver', IntersectionObserver);
|
package/src/register.spec.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
// GJS-only tests for dom-elements/register side effects.
|
|
2
|
-
// Verifies that importing /register correctly wires browser globals onto globalThis:
|
|
3
|
-
// - FontFace, FontFaceSet, document.fonts (Excalibur FontSource.load() path)
|
|
4
|
-
// - FontFace.load() registers TTF in PangoCairo default FontMap (real font rendering)
|
|
5
|
-
// - globalThis.addEventListener/removeEventListener/dispatchEvent via __gjsify_globalEventTarget
|
|
6
|
-
// (Regression: Excalibur's Keyboard.init() calls window.addEventListener)
|
|
7
|
-
//
|
|
8
|
-
// These tests require /register to have run (GJS only — GTK/Gio present).
|
|
9
|
-
// Cross-platform stub tests live in stubs.spec.ts.
|
|
10
|
-
|
|
11
|
-
import { describe, it, expect, on } from '@gjsify/unit';
|
|
12
|
-
import '@gjsify/dom-elements/register';
|
|
13
|
-
import { KeyboardEvent as OurKeyboardEvent } from '@gjsify/dom-events';
|
|
14
|
-
|
|
15
|
-
export default async () => {
|
|
16
|
-
await on('Gjs', async () => {
|
|
17
|
-
|
|
18
|
-
await describe('globalThis FontFace / document.fonts', async () => {
|
|
19
|
-
// Excalibur's FontSource.load() uses these via globalThis (no import).
|
|
20
|
-
await it('FontFace is available as a constructor on globalThis', async () => {
|
|
21
|
-
expect(typeof (globalThis as any).FontFace).toBe('function');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
await it('document.fonts is a FontFaceSet with add() and ready', async () => {
|
|
25
|
-
const fonts = (globalThis as any).document?.fonts;
|
|
26
|
-
expect(fonts).toBeDefined();
|
|
27
|
-
expect(typeof fonts.add).toBe('function');
|
|
28
|
-
const ready = await fonts.ready;
|
|
29
|
-
expect(ready).toBe(fonts);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
await it('FontFace.load() via globalThis resolves and sets status=loaded', async () => {
|
|
33
|
-
const FF = (globalThis as any).FontFace;
|
|
34
|
-
const face = new FF('Round9x13', 'url(/res/fonts/Round9x13.ttf)');
|
|
35
|
-
expect(face.status).toBe('unloaded');
|
|
36
|
-
await face.load();
|
|
37
|
-
expect(face.status).toBe('loaded');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
await it('document.fonts.add() after load does not throw', async () => {
|
|
41
|
-
const FF = (globalThis as any).FontFace;
|
|
42
|
-
const face = new FF('Round9x13', 'url(/res/fonts/Round9x13.ttf)');
|
|
43
|
-
await face.load();
|
|
44
|
-
let threw = false;
|
|
45
|
-
try { (globalThis as any).document.fonts.add(face); } catch { threw = true; }
|
|
46
|
-
expect(threw).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
await describe('FontFace real load via PangoCairo', async () => {
|
|
51
|
-
// Verifies that a file:// URL triggers add_font_file on the PangoCairo
|
|
52
|
-
// default FontMap, making the font available to Canvas2D fillText.
|
|
53
|
-
// Uses DejaVuSans which is present on Fedora and most Linux distros.
|
|
54
|
-
const TTF = '/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf';
|
|
55
|
-
|
|
56
|
-
await it('load() with file:// URL registers font and sets status=loaded', async () => {
|
|
57
|
-
const FF = (globalThis as any).FontFace;
|
|
58
|
-
const face = new FF('DejaVuTestFont', `url(file://${TTF})`);
|
|
59
|
-
expect(face.status).toBe('unloaded');
|
|
60
|
-
await face.load();
|
|
61
|
-
expect(face.status).toBe('loaded');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Note: ink rendering test (fillText with registered font) lives in
|
|
65
|
-
// packages/dom/canvas2d-core or @gjsify/canvas2d tests — canvas2d
|
|
66
|
-
// depends on dom-elements so importing it here would be circular.
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
await describe('globalThis keyboard EventTarget wiring', async () => {
|
|
70
|
-
// Regression: Excalibur's Keyboard.init() calls window.addEventListener('keydown', ...)
|
|
71
|
-
// which must route through __gjsify_globalEventTarget (set in dom-elements/register.ts).
|
|
72
|
-
await it('addEventListener stores listener on __gjsify_globalEventTarget', async () => {
|
|
73
|
-
const received: string[] = [];
|
|
74
|
-
(globalThis as any).addEventListener('keydown', (e: OurKeyboardEvent) => {
|
|
75
|
-
received.push(e.key);
|
|
76
|
-
});
|
|
77
|
-
const evt = new OurKeyboardEvent('keydown', {
|
|
78
|
-
key: 'ArrowRight', code: 'ArrowRight', bubbles: true, cancelable: true,
|
|
79
|
-
});
|
|
80
|
-
(globalThis as any).__gjsify_globalEventTarget.dispatchEvent(evt);
|
|
81
|
-
expect(received).toContain('ArrowRight');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
await it('removeEventListener deregisters listener', async () => {
|
|
85
|
-
const received: string[] = [];
|
|
86
|
-
const handler = (e: OurKeyboardEvent) => received.push(e.key);
|
|
87
|
-
(globalThis as any).addEventListener('keydown', handler);
|
|
88
|
-
(globalThis as any).removeEventListener('keydown', handler);
|
|
89
|
-
const evt = new OurKeyboardEvent('keydown', { key: 'a', bubbles: true, cancelable: true });
|
|
90
|
-
(globalThis as any).__gjsify_globalEventTarget.dispatchEvent(evt);
|
|
91
|
-
expect(received.length).toBe(0);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
await it('__gjsify_globalEventTarget is exposed on globalThis', async () => {
|
|
95
|
-
expect((globalThis as any).__gjsify_globalEventTarget).toBeDefined();
|
|
96
|
-
expect(typeof (globalThis as any).__gjsify_globalEventTarget.addEventListener).toBe('function');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
await it('multiple listeners for the same event type all fire', async () => {
|
|
100
|
-
const log: number[] = [];
|
|
101
|
-
const a = () => log.push(1);
|
|
102
|
-
const b = () => log.push(2);
|
|
103
|
-
(globalThis as any).addEventListener('keyup', a);
|
|
104
|
-
(globalThis as any).addEventListener('keyup', b);
|
|
105
|
-
const evt = new OurKeyboardEvent('keyup', { key: 'x', bubbles: true, cancelable: true });
|
|
106
|
-
(globalThis as any).__gjsify_globalEventTarget.dispatchEvent(evt);
|
|
107
|
-
expect(log).toContain(1);
|
|
108
|
-
expect(log).toContain(2);
|
|
109
|
-
(globalThis as any).removeEventListener('keyup', a);
|
|
110
|
-
(globalThis as any).removeEventListener('keyup', b);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
await describe('window scroll globals', async () => {
|
|
115
|
-
// Excalibur's getPosition() computes `rect.x + window.scrollX`.
|
|
116
|
-
// Without these stubs scrollX is undefined → NaN coords → blank
|
|
117
|
-
// canvas on any drag/pan. Regression test for map-editor.
|
|
118
|
-
await it('scrollX and scrollY default to 0', async () => {
|
|
119
|
-
expect((globalThis as any).scrollX).toBe(0);
|
|
120
|
-
expect((globalThis as any).scrollY).toBe(0);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
await it('legacy pageXOffset and pageYOffset default to 0', async () => {
|
|
124
|
-
expect((globalThis as any).pageXOffset).toBe(0);
|
|
125
|
-
expect((globalThis as any).pageYOffset).toBe(0);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
await it('rect.x + window.scrollX is a number (no NaN)', async () => {
|
|
129
|
-
const rect = (globalThis as any).document.createElement('div').getBoundingClientRect();
|
|
130
|
-
const pageX = rect.x + (globalThis as any).scrollX;
|
|
131
|
-
expect(Number.isFinite(pageX)).toBe(true);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
});
|
|
136
|
-
};
|
package/src/register.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Catch-all side-effect module: registers ALL DOM globals on GJS.
|
|
2
|
-
// Prefer granular imports (e.g. '@gjsify/dom-elements/register/navigator')
|
|
3
|
-
// when only specific globals are needed — the --globals auto mode does this
|
|
4
|
-
// automatically.
|
|
5
|
-
|
|
6
|
-
import './register/document.js';
|
|
7
|
-
import './register/canvas.js';
|
|
8
|
-
import './register/image.js';
|
|
9
|
-
import './register/observers.js';
|
|
10
|
-
import './register/font-face.js';
|
|
11
|
-
import './register/match-media.js';
|
|
12
|
-
import './register/location.js';
|
|
13
|
-
import './register/navigator.js';
|
package/src/resize-observer.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// ResizeObserver stub for GJS — original implementation
|
|
2
|
-
// Reference: refs/happy-dom/packages/happy-dom/src/resize-observer/ResizeObserver.ts
|
|
3
|
-
// Copyright (c) David Ortner (capricorn86). MIT license.
|
|
4
|
-
// Modifications: Stub implementation — no actual resize tracking
|
|
5
|
-
|
|
6
|
-
import type { Element } from './element.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* ResizeObserver stub.
|
|
10
|
-
* Many libraries check for ResizeObserver existence; this prevents crashes.
|
|
11
|
-
*
|
|
12
|
-
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
|
|
13
|
-
*/
|
|
14
|
-
export class ResizeObserver {
|
|
15
|
-
constructor(_callback: (...args: unknown[]) => void) {}
|
|
16
|
-
|
|
17
|
-
observe(_target: Element): void {
|
|
18
|
-
// Stub
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
unobserve(_target: Element): void {
|
|
22
|
-
// Stub
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
disconnect(): void {
|
|
26
|
-
// Stub
|
|
27
|
-
}
|
|
28
|
-
}
|
package/src/stubs.spec.ts
DELETED
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
// Tests for DOM/browser stubs added for Excalibur.js and other game engines:
|
|
2
|
-
// - DOMMatrix (2D math, Canvas 2D compatibility)
|
|
3
|
-
// - CSSStyleDeclaration.setProperty / getPropertyValue
|
|
4
|
-
// - FontFace / FontFaceSet (font loading no-op)
|
|
5
|
-
// - MediaQueryList / matchMedia (responsive checks)
|
|
6
|
-
// - location (file:// stub)
|
|
7
|
-
//
|
|
8
|
-
// Cross-platform: runs on both Node.js and GJS without /register side effects.
|
|
9
|
-
// GJS-specific globalThis tests (FontFace on globalThis, keyboard EventTarget) live in register.spec.ts.
|
|
10
|
-
//
|
|
11
|
-
// Locks in the fixes made to ship the excalibur-jelly-jumper showcase on GJS.
|
|
12
|
-
|
|
13
|
-
import { describe, it, expect } from '@gjsify/unit';
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
DOMMatrix,
|
|
17
|
-
CSSStyleDeclaration,
|
|
18
|
-
FontFace,
|
|
19
|
-
FontFaceSet,
|
|
20
|
-
MediaQueryList,
|
|
21
|
-
matchMedia,
|
|
22
|
-
location,
|
|
23
|
-
} from '@gjsify/dom-elements';
|
|
24
|
-
|
|
25
|
-
export default async () => {
|
|
26
|
-
|
|
27
|
-
await describe('DOMMatrix', async () => {
|
|
28
|
-
|
|
29
|
-
await describe('constructor', async () => {
|
|
30
|
-
await it('creates an identity matrix by default', async () => {
|
|
31
|
-
const m = new DOMMatrix();
|
|
32
|
-
expect(m.a).toBe(1);
|
|
33
|
-
expect(m.b).toBe(0);
|
|
34
|
-
expect(m.c).toBe(0);
|
|
35
|
-
expect(m.d).toBe(1);
|
|
36
|
-
expect(m.e).toBe(0);
|
|
37
|
-
expect(m.f).toBe(0);
|
|
38
|
-
expect(m.is2D).toBe(true);
|
|
39
|
-
expect(m.isIdentity).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
await it('accepts a 6-element 2D affine array', async () => {
|
|
43
|
-
const m = new DOMMatrix([1, 2, 3, 4, 5, 6]);
|
|
44
|
-
expect(m.a).toBe(1);
|
|
45
|
-
expect(m.b).toBe(2);
|
|
46
|
-
expect(m.c).toBe(3);
|
|
47
|
-
expect(m.d).toBe(4);
|
|
48
|
-
expect(m.e).toBe(5);
|
|
49
|
-
expect(m.f).toBe(6);
|
|
50
|
-
expect(m.is2D).toBe(true);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
await it('mirrors 2D components into m11-m42 3D fields', async () => {
|
|
54
|
-
const m = new DOMMatrix([1, 2, 3, 4, 5, 6]);
|
|
55
|
-
expect(m.m11).toBe(1); // a
|
|
56
|
-
expect(m.m12).toBe(2); // b
|
|
57
|
-
expect(m.m21).toBe(3); // c
|
|
58
|
-
expect(m.m22).toBe(4); // d
|
|
59
|
-
expect(m.m41).toBe(5); // e
|
|
60
|
-
expect(m.m42).toBe(6); // f
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
await it('accepts a 16-element 3D array and sets is2D=false', async () => {
|
|
64
|
-
const m = new DOMMatrix([
|
|
65
|
-
1, 2, 3, 4,
|
|
66
|
-
5, 6, 7, 8,
|
|
67
|
-
9, 10, 11, 12,
|
|
68
|
-
13, 14, 15, 16,
|
|
69
|
-
]);
|
|
70
|
-
expect(m.m11).toBe(1);
|
|
71
|
-
expect(m.m44).toBe(16);
|
|
72
|
-
expect(m.is2D).toBe(false);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
await it('isIdentity is false for non-identity matrices', async () => {
|
|
76
|
-
const m = new DOMMatrix([2, 0, 0, 2, 0, 0]);
|
|
77
|
-
expect(m.isIdentity).toBe(false);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
await describe('multiply', async () => {
|
|
82
|
-
await it('multiplying by identity yields the original matrix', async () => {
|
|
83
|
-
const a = new DOMMatrix([2, 0, 0, 3, 10, 20]);
|
|
84
|
-
const i = new DOMMatrix();
|
|
85
|
-
const r = a.multiply(i);
|
|
86
|
-
expect(r.a).toBe(2);
|
|
87
|
-
expect(r.d).toBe(3);
|
|
88
|
-
expect(r.e).toBe(10);
|
|
89
|
-
expect(r.f).toBe(20);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
await it('composes two scale matrices correctly', async () => {
|
|
93
|
-
const a = new DOMMatrix([2, 0, 0, 3, 0, 0]);
|
|
94
|
-
const b = new DOMMatrix([4, 0, 0, 5, 0, 0]);
|
|
95
|
-
const r = a.multiply(b);
|
|
96
|
-
// Scale(2,3) * Scale(4,5) = Scale(8, 15)
|
|
97
|
-
expect(r.a).toBe(8);
|
|
98
|
-
expect(r.d).toBe(15);
|
|
99
|
-
expect(r.e).toBe(0);
|
|
100
|
-
expect(r.f).toBe(0);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
await it('composes a translate after a scale', async () => {
|
|
104
|
-
// scale(2,2) then translate(10,20): the translate is multiplied
|
|
105
|
-
// by the current scale, so the result translates by (20,40).
|
|
106
|
-
const scale = new DOMMatrix([2, 0, 0, 2, 0, 0]);
|
|
107
|
-
const translate = new DOMMatrix([1, 0, 0, 1, 10, 20]);
|
|
108
|
-
const r = scale.multiply(translate);
|
|
109
|
-
expect(r.a).toBe(2);
|
|
110
|
-
expect(r.d).toBe(2);
|
|
111
|
-
expect(r.e).toBe(20);
|
|
112
|
-
expect(r.f).toBe(40);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
await it('returns a new matrix without mutating the receiver', async () => {
|
|
116
|
-
const a = new DOMMatrix([2, 0, 0, 2, 0, 0]);
|
|
117
|
-
const b = new DOMMatrix([3, 0, 0, 3, 0, 0]);
|
|
118
|
-
a.multiply(b);
|
|
119
|
-
expect(a.a).toBe(2); // unchanged
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
await describe('inverse', async () => {
|
|
124
|
-
await it('identity inverse is identity', async () => {
|
|
125
|
-
const m = new DOMMatrix().inverse();
|
|
126
|
-
expect(m.a).toBe(1);
|
|
127
|
-
expect(m.d).toBe(1);
|
|
128
|
-
expect(m.e).toBe(0);
|
|
129
|
-
expect(m.f).toBe(0);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
await it('scale(2,2) inverse is scale(0.5, 0.5)', async () => {
|
|
133
|
-
const m = new DOMMatrix([2, 0, 0, 2, 0, 0]).inverse();
|
|
134
|
-
expect(m.a).toBe(0.5);
|
|
135
|
-
expect(m.d).toBe(0.5);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
await it('translate(10,20) inverse is translate(-10,-20)', async () => {
|
|
139
|
-
const m = new DOMMatrix([1, 0, 0, 1, 10, 20]).inverse();
|
|
140
|
-
expect(m.e).toBe(-10);
|
|
141
|
-
expect(m.f).toBe(-20);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
await it('inverse of non-invertible matrix returns identity (graceful)', async () => {
|
|
145
|
-
// det = 0 (both rows proportional)
|
|
146
|
-
const m = new DOMMatrix([1, 2, 2, 4, 0, 0]).inverse();
|
|
147
|
-
expect(m.a).toBe(1);
|
|
148
|
-
expect(m.d).toBe(1);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
await describe('translate / scale helpers', async () => {
|
|
153
|
-
await it('translate returns a new matrix with the composed translation', async () => {
|
|
154
|
-
const m = new DOMMatrix().translate(5, 7);
|
|
155
|
-
expect(m.e).toBe(5);
|
|
156
|
-
expect(m.f).toBe(7);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
await it('scale returns a new matrix with the composed scale', async () => {
|
|
160
|
-
const m = new DOMMatrix().scale(3, 4);
|
|
161
|
-
expect(m.a).toBe(3);
|
|
162
|
-
expect(m.d).toBe(4);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
await it('scale with a single argument uses uniform scale', async () => {
|
|
166
|
-
const m = new DOMMatrix().scale(2);
|
|
167
|
-
expect(m.a).toBe(2);
|
|
168
|
-
expect(m.d).toBe(2);
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
await describe('CSSStyleDeclaration', async () => {
|
|
174
|
-
await it('stores properties via setProperty and reads them via getPropertyValue', async () => {
|
|
175
|
-
const style = new CSSStyleDeclaration();
|
|
176
|
-
style.setProperty('--ex-pixel-ratio', '2');
|
|
177
|
-
expect(style.getPropertyValue('--ex-pixel-ratio')).toBe('2');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
await it('removeProperty returns and clears the value', async () => {
|
|
181
|
-
const style = new CSSStyleDeclaration();
|
|
182
|
-
style.setProperty('color', 'red');
|
|
183
|
-
const removed = style.removeProperty('color');
|
|
184
|
-
expect(removed).toBe('red');
|
|
185
|
-
expect(style.getPropertyValue('color')).toBe('');
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
await it('getPropertyValue returns empty string for unknown properties', async () => {
|
|
189
|
-
const style = new CSSStyleDeclaration();
|
|
190
|
-
expect(style.getPropertyValue('nonexistent')).toBe('');
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
await it('getPropertyPriority is always empty (stub)', async () => {
|
|
194
|
-
const style = new CSSStyleDeclaration();
|
|
195
|
-
style.setProperty('color', 'red');
|
|
196
|
-
expect(style.getPropertyPriority('color')).toBe('');
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
await it('cssText setter parses declarations into camelCase properties', async () => {
|
|
200
|
-
const style = new CSSStyleDeclaration();
|
|
201
|
-
style.cssText = 'background-color: rgba(0,0,0,0); color: red';
|
|
202
|
-
expect((style as any).backgroundColor).toBe('rgba(0,0,0,0)');
|
|
203
|
-
expect((style as any).color).toBe('red');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
await it('cssText getter returns the last assigned value', async () => {
|
|
207
|
-
const style = new CSSStyleDeclaration();
|
|
208
|
-
style.cssText = 'color: blue';
|
|
209
|
-
expect(style.cssText).toBe('color: blue');
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
await it('cssText setter handles single-property strings (Excalibur rgbaSupport check)', async () => {
|
|
213
|
-
// Excalibur does: el.style.cssText = 'background-color:rgba(135,100,100,.5)'
|
|
214
|
-
// then reads el.style.backgroundColor to detect rgba support.
|
|
215
|
-
const style = new CSSStyleDeclaration();
|
|
216
|
-
style.cssText = 'background-color:rgba(135,100,100,.5)';
|
|
217
|
-
const bg = (style as any).backgroundColor as string;
|
|
218
|
-
expect(typeof bg).toBe('string');
|
|
219
|
-
expect(bg.length).toBeGreaterThan(0);
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
await describe('FontFace / FontFaceSet stubs', async () => {
|
|
224
|
-
await it('FontFace constructor accepts family and source', async () => {
|
|
225
|
-
const face = new FontFace('MyFont', 'url(font.ttf)');
|
|
226
|
-
expect(face.family).toBe('MyFont');
|
|
227
|
-
expect(face.source).toBe('url(font.ttf)');
|
|
228
|
-
expect(face.status).toBe('unloaded');
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
await it('FontFace.load resolves with the face and marks it loaded', async () => {
|
|
232
|
-
const face = new FontFace('MyFont', 'url(font.ttf)');
|
|
233
|
-
const loaded = await face.load();
|
|
234
|
-
expect(loaded).toBe(face);
|
|
235
|
-
expect(face.status).toBe('loaded');
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
await it('FontFaceSet add/has/size track added faces', async () => {
|
|
239
|
-
const set = new FontFaceSet();
|
|
240
|
-
const face = new FontFace('A', 'url(a.ttf)');
|
|
241
|
-
expect(set.has(face)).toBe(false);
|
|
242
|
-
expect(set.size).toBe(0);
|
|
243
|
-
set.add(face);
|
|
244
|
-
expect(set.has(face)).toBe(true);
|
|
245
|
-
expect(set.size).toBe(1);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
await it('FontFaceSet.ready resolves with the set', async () => {
|
|
249
|
-
const set = new FontFaceSet();
|
|
250
|
-
const ready = await set.ready;
|
|
251
|
-
expect(ready).toBe(set);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
await it('FontFaceSet does NOT inherit from EventTarget', async () => {
|
|
255
|
-
// This is critical: dom-elements/register runs BEFORE dom-events/
|
|
256
|
-
// register, so EventTarget would be undefined at class load time.
|
|
257
|
-
// The stub provides its own no-op addEventListener instead.
|
|
258
|
-
const set = new FontFaceSet();
|
|
259
|
-
expect(typeof set.addEventListener).toBe('function');
|
|
260
|
-
expect(typeof set.removeEventListener).toBe('function');
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
await describe('MediaQueryList / matchMedia', async () => {
|
|
265
|
-
await it('matchMedia returns a MediaQueryList with matches=false', async () => {
|
|
266
|
-
const mq = matchMedia('(min-width: 800px)');
|
|
267
|
-
expect(mq instanceof MediaQueryList).toBe(true);
|
|
268
|
-
expect(mq.media).toBe('(min-width: 800px)');
|
|
269
|
-
expect(mq.matches).toBe(false);
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
await describe('location stub', async () => {
|
|
274
|
-
await it('exposes file:// origin and protocol', async () => {
|
|
275
|
-
expect(location.origin).toBe('file://');
|
|
276
|
-
expect(location.protocol).toBe('file:');
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
await it('toString returns href', async () => {
|
|
280
|
-
expect(location.toString()).toBe(location.href);
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
};
|