mirror of
https://github.com/zoriya/v10.git
synced 2026-08-08 23:27:58 +00:00
feat(site): media element API reference builder (#1256)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
0c8e19a4e0
commit
cf357ad9a7
@@ -59,6 +59,24 @@
|
||||
* video/ — Exercises: feature bundle, React skins (*Skin naming),
|
||||
* media element export, tailwind skin exclusion.
|
||||
* audio/ — Exercises: single skin, different media element.
|
||||
*
|
||||
* Media elements (packages/html/src/define/media/ + packages/core/src/dom/media/):
|
||||
* simple-video — Simple media element. Exercises: discovery via static
|
||||
* tagName in define/media/*.ts, minimal delegate (src rw,
|
||||
* engine readonly), shared Attributes/Events/CSS vars
|
||||
* from custom-media-element, slots parsed from template HTML.
|
||||
* complex-video — Complex media element. Exercises: delegate with JSDoc
|
||||
* descriptions, multiple property types (string, boolean,
|
||||
* Record), delegate-vs-native attribute deduplication
|
||||
* (src, preload in delegate → omitted from nativeAttributes).
|
||||
* extending-video — Extending media element. Exercises: delegate inheritance
|
||||
* (ExtendingDelegate extends ComplexDelegate). Builder must
|
||||
* walk the extends chain to include inherited properties.
|
||||
* Child overrides (debug) replace parent definitions.
|
||||
* container.ts — Exclusion case. Not a media element — re-exports an
|
||||
* existing class instead of declaring one inline.
|
||||
* background-video.ts — Exclusion case. Uses MediaAttachMixin(HTMLElement)
|
||||
* without MediaPropsMixin. API reference manually maintained.
|
||||
*/
|
||||
import * as path from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
@@ -66,7 +84,9 @@ import {
|
||||
type FeatureResult,
|
||||
generateComponentReferences,
|
||||
generateFeatureReferences,
|
||||
generateMediaElementReferences,
|
||||
generatePresetReferences,
|
||||
type MediaElementResult,
|
||||
type PresetResult,
|
||||
} from '../pipeline';
|
||||
import { getUtilEntries, type UtilEntry } from '../util-handler';
|
||||
@@ -962,3 +982,263 @@ describe('Preset pipeline (end-to-end)', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// MEDIA ELEMENT PIPELINE
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
//
|
||||
// Media elements are custom elements that wrap native <video>/<audio> with
|
||||
// streaming delegates (HLS, DASH, etc.). They are discovered from
|
||||
// packages/html/src/define/media/*.ts by looking for files that declare a
|
||||
// class with `static tagName`.
|
||||
//
|
||||
// The builder extracts:
|
||||
// - Tag name from the element class's static tagName
|
||||
// - Delegate properties by following the mixin chain to the delegate class
|
||||
// and walking its getter/setter pairs (mirrors MediaPropsMixin at runtime)
|
||||
// - Shared native attributes, events, and CSS vars from custom-media-element
|
||||
// - Slots parsed from the template HTML (getVideoTemplateHTML / getAudioTemplateHTML)
|
||||
// - JSDoc descriptions from delegate getter/setter pairs
|
||||
//
|
||||
// Key behaviors:
|
||||
// - Discovery: files in define/media/ with an inline class declaration + static tagName
|
||||
// - Exclusion: container.ts (re-exports, no inline class), background-video.ts
|
||||
// (no MediaPropsMixin — uses MediaAttachMixin(HTMLElement) directly)
|
||||
// - Delegate inheritance: child delegate extends parent, builder walks the chain
|
||||
// - Deduplication: properties in the delegate that overlap with native Attributes
|
||||
// (e.g., src, preload) appear in delegateProperties and are omitted from nativeAttributes
|
||||
|
||||
describe('Media element pipeline (end-to-end)', () => {
|
||||
const results = generateMediaElementReferences(FIXTURE_ROOT);
|
||||
|
||||
function findElement(name: string): MediaElementResult | undefined {
|
||||
return results.find((r) => r.name === name);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// DISCOVERY
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('Discovery', () => {
|
||||
it('discovers media elements from define/media/ files', () => {
|
||||
const names = results.map((r) => r.name).sort();
|
||||
expect(names).toEqual(['ComplexVideo', 'ExtendingVideo', 'SimpleVideo']);
|
||||
});
|
||||
|
||||
it('excludes container (re-export, not inline class declaration)', () => {
|
||||
expect(findElement('MediaContainer')).toBeUndefined();
|
||||
expect(findElement('MediaContainerElement')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('excludes background-video (no MediaPropsMixin, manually maintained)', () => {
|
||||
expect(findElement('BackgroundVideo')).toBeUndefined();
|
||||
expect(findElement('BackgroundVideoElement')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('produces one result per media element', () => {
|
||||
expect(results.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// SIMPLE MEDIA ELEMENT: SimpleVideo
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// A minimal media element with a simple delegate (src rw, engine readonly).
|
||||
// No JSDoc on delegate properties — descriptions should be undefined.
|
||||
// No overlap between delegate props and native Attributes (engine is not
|
||||
// in Attributes), so nativeAttributes should be the full shared list.
|
||||
|
||||
describe('SimpleVideo (minimal delegate)', () => {
|
||||
it('extracts the tag name', () => {
|
||||
const ref = findElement('SimpleVideo')!.reference;
|
||||
expect(ref.tagName).toBe('simple-video');
|
||||
});
|
||||
|
||||
it('extracts delegate properties with types and readonly flags', () => {
|
||||
const props = findElement('SimpleVideo')!.reference.delegateProperties;
|
||||
|
||||
// src: read-write string
|
||||
expect(props.src).toMatchObject({
|
||||
type: 'string',
|
||||
readonly: false,
|
||||
});
|
||||
expect(props.src.description).toBeUndefined();
|
||||
|
||||
// engine: readonly object
|
||||
expect(props.engine).toMatchObject({
|
||||
type: 'object',
|
||||
readonly: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('excludes delegate methods (attach, detach, destroy)', () => {
|
||||
const props = findElement('SimpleVideo')!.reference.delegateProperties;
|
||||
expect(props.attach).toBeUndefined();
|
||||
expect(props.detach).toBeUndefined();
|
||||
expect(props.destroy).toBeUndefined();
|
||||
});
|
||||
|
||||
it('includes native attributes from the shared Attributes array', () => {
|
||||
const ref = findElement('SimpleVideo')!.reference;
|
||||
// src is in the delegate, so it should be omitted from nativeAttributes
|
||||
expect(ref.nativeAttributes).toEqual(
|
||||
expect.arrayContaining([
|
||||
'autoplay',
|
||||
'controls',
|
||||
'crossorigin',
|
||||
'loop',
|
||||
'muted',
|
||||
'playsinline',
|
||||
'poster',
|
||||
'preload',
|
||||
])
|
||||
);
|
||||
expect(ref.nativeAttributes).not.toContain('src');
|
||||
});
|
||||
|
||||
it('includes events from the shared Events array', () => {
|
||||
const ref = findElement('SimpleVideo')!.reference;
|
||||
expect(ref.events).toEqual(
|
||||
expect.arrayContaining([
|
||||
'abort',
|
||||
'canplay',
|
||||
'durationchange',
|
||||
'ended',
|
||||
'pause',
|
||||
'play',
|
||||
'timeupdate',
|
||||
'volumechange',
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('includes CSS custom properties from VideoCSSVars', () => {
|
||||
const css = findElement('SimpleVideo')!.reference.cssCustomProperties;
|
||||
expect(css['--media-object-fit']).toEqual({
|
||||
description: 'Object fit for the video.',
|
||||
});
|
||||
expect(css['--media-video-border-radius']).toEqual({
|
||||
description: 'Border radius of the video element.',
|
||||
});
|
||||
});
|
||||
|
||||
it('includes slots parsed from the video template HTML', () => {
|
||||
const ref = findElement('SimpleVideo')!.reference;
|
||||
expect(ref.slots).toEqual(expect.arrayContaining(['media', '']));
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// COMPLEX MEDIA ELEMENT: ComplexVideo
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// A full media element with a complex delegate that has JSDoc descriptions,
|
||||
// multiple property types, and overlap with native Attributes (src, preload).
|
||||
// Tests that the builder extracts descriptions from JSDoc on getters and
|
||||
// deduplicates delegate props from nativeAttributes.
|
||||
|
||||
describe('ComplexVideo (full delegate, JSDoc, deduplication)', () => {
|
||||
it('extracts the tag name', () => {
|
||||
const ref = findElement('ComplexVideo')!.reference;
|
||||
expect(ref.tagName).toBe('complex-video');
|
||||
});
|
||||
|
||||
it('extracts all delegate properties', () => {
|
||||
const props = findElement('ComplexVideo')!.reference.delegateProperties;
|
||||
const propNames = Object.keys(props).sort();
|
||||
expect(propNames).toEqual(['config', 'debug', 'engine', 'preferPlayback', 'preload', 'src', 'type']);
|
||||
});
|
||||
|
||||
it('extracts JSDoc descriptions from delegate getters', () => {
|
||||
const props = findElement('ComplexVideo')!.reference.delegateProperties;
|
||||
expect(props.type.description).toBe('Explicit source type. When unset, inferred from the source URL extension.');
|
||||
expect(props.preferPlayback.description).toBe("Whether to prefer `'mse'` or `'native'` playback.");
|
||||
expect(props.debug.description).toBe('Enable debug logging.');
|
||||
expect(props.engine.description).toBe('The underlying playback engine instance.');
|
||||
});
|
||||
|
||||
it('marks readonly properties correctly', () => {
|
||||
const props = findElement('ComplexVideo')!.reference.delegateProperties;
|
||||
// engine: getter only → readonly
|
||||
expect(props.engine.readonly).toBe(true);
|
||||
// src: getter + setter → not readonly
|
||||
expect(props.src.readonly).toBe(false);
|
||||
expect(props.debug.readonly).toBe(false);
|
||||
});
|
||||
|
||||
it('extracts property types', () => {
|
||||
const props = findElement('ComplexVideo')!.reference.delegateProperties;
|
||||
expect(props.src.type).toBe('string');
|
||||
expect(props.debug.type).toBe('boolean');
|
||||
expect(props.config.type).toContain('Record');
|
||||
});
|
||||
|
||||
it('deduplicates delegate props from nativeAttributes', () => {
|
||||
const ref = findElement('ComplexVideo')!.reference;
|
||||
// src and preload are in both the delegate AND native Attributes.
|
||||
// They should appear in delegateProperties...
|
||||
expect(ref.delegateProperties.src).toBeDefined();
|
||||
expect(ref.delegateProperties.preload).toBeDefined();
|
||||
// ...and be omitted from nativeAttributes
|
||||
expect(ref.nativeAttributes).not.toContain('src');
|
||||
expect(ref.nativeAttributes).not.toContain('preload');
|
||||
// Other native attrs remain
|
||||
expect(ref.nativeAttributes).toContain('autoplay');
|
||||
expect(ref.nativeAttributes).toContain('controls');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// EXTENDING MEDIA ELEMENT: ExtendingVideo
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// A media element whose delegate extends another delegate (mirrors
|
||||
// MuxMediaDelegate extending HlsMediaDelegate). The builder must
|
||||
// walk the extends chain to include inherited properties. Child
|
||||
// properties override parent definitions.
|
||||
|
||||
describe('ExtendingVideo (delegate inheritance)', () => {
|
||||
it('extracts the tag name', () => {
|
||||
const ref = findElement('ExtendingVideo')!.reference;
|
||||
expect(ref.tagName).toBe('extending-video');
|
||||
});
|
||||
|
||||
it('includes own properties from ExtendingDelegate', () => {
|
||||
const props = findElement('ExtendingVideo')!.reference.delegateProperties;
|
||||
expect(props.playbackId).toMatchObject({
|
||||
type: 'string',
|
||||
readonly: false,
|
||||
description: 'The playback ID for the video.',
|
||||
});
|
||||
expect(props.customDomain).toMatchObject({
|
||||
type: 'string',
|
||||
readonly: false,
|
||||
description: 'Custom domain for asset delivery.',
|
||||
});
|
||||
});
|
||||
|
||||
it('includes inherited properties from ComplexDelegate', () => {
|
||||
const props = findElement('ExtendingVideo')!.reference.delegateProperties;
|
||||
// These are inherited from ComplexDelegate
|
||||
expect(props.src).toBeDefined();
|
||||
expect(props.type).toBeDefined();
|
||||
expect(props.preferPlayback).toBeDefined();
|
||||
expect(props.config).toBeDefined();
|
||||
expect(props.preload).toBeDefined();
|
||||
expect(props.engine).toBeDefined();
|
||||
});
|
||||
|
||||
it('child overrides replace parent definitions', () => {
|
||||
const props = findElement('ExtendingVideo')!.reference.delegateProperties;
|
||||
// ExtendingDelegate overrides debug with different JSDoc
|
||||
expect(props.debug.description).toBe('Overrides parent debug — adds network logging.');
|
||||
});
|
||||
|
||||
it('inherited readonly flags are preserved', () => {
|
||||
const props = findElement('ExtendingVideo')!.reference.delegateProperties;
|
||||
// engine is readonly in ComplexDelegate and not overridden
|
||||
expect(props.engine.readonly).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Mock complex delegate — mirrors HlsMediaDelegate.
|
||||
*
|
||||
* Exercises: multiple getter/setter pairs with JSDoc descriptions,
|
||||
* readonly properties, boolean type, overlap with native Attributes
|
||||
* (src, preload) that should be deduplicated by the builder.
|
||||
*/
|
||||
export class ComplexDelegate {
|
||||
#src: string = '';
|
||||
#type: string | undefined;
|
||||
#preferPlayback: string | undefined = 'mse';
|
||||
#config: Record<string, unknown> = {};
|
||||
#debug: boolean = false;
|
||||
#preload: string = 'metadata';
|
||||
#engine: object | null = null;
|
||||
|
||||
get src(): string {
|
||||
return this.#src;
|
||||
}
|
||||
|
||||
set src(value: string) {
|
||||
this.#src = value;
|
||||
}
|
||||
|
||||
/** Explicit source type. When unset, inferred from the source URL extension. */
|
||||
get type(): string | undefined {
|
||||
return this.#type;
|
||||
}
|
||||
|
||||
set type(value: string | undefined) {
|
||||
this.#type = value;
|
||||
}
|
||||
|
||||
/** Whether to prefer `'mse'` or `'native'` playback. */
|
||||
get preferPlayback(): string | undefined {
|
||||
return this.#preferPlayback;
|
||||
}
|
||||
|
||||
set preferPlayback(value: string | undefined) {
|
||||
this.#preferPlayback = value;
|
||||
}
|
||||
|
||||
get config(): Record<string, unknown> {
|
||||
return this.#config;
|
||||
}
|
||||
|
||||
set config(value: Record<string, unknown>) {
|
||||
this.#config = value;
|
||||
}
|
||||
|
||||
/** Enable debug logging. */
|
||||
get debug(): boolean {
|
||||
return this.#debug;
|
||||
}
|
||||
|
||||
set debug(value: boolean) {
|
||||
this.#debug = value;
|
||||
}
|
||||
|
||||
get preload(): string {
|
||||
return this.#preload;
|
||||
}
|
||||
|
||||
set preload(value: string) {
|
||||
this.#preload = value;
|
||||
}
|
||||
|
||||
/** The underlying playback engine instance. */
|
||||
get engine(): object | null {
|
||||
return this.#engine;
|
||||
}
|
||||
|
||||
attach(_target: EventTarget): void {}
|
||||
detach(): void {}
|
||||
destroy(): void {}
|
||||
}
|
||||
|
||||
export class ComplexCustomMedia {}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Mock custom media element infrastructure.
|
||||
*
|
||||
* Exercises: shared Events, Attributes, and CSS vars that the builder reads
|
||||
* to populate media element references. Slots are parsed from the template
|
||||
* HTML (getVideoTemplateHTML / getAudioTemplateHTML), not from exported arrays.
|
||||
*
|
||||
* VideoCSSVars/AudioCSSVars follow the `{ camelKey: '--var-name' }` pattern
|
||||
* with JSDoc descriptions, matching UI component css-vars files.
|
||||
*/
|
||||
|
||||
export const Events = [
|
||||
'abort',
|
||||
'canplay',
|
||||
'durationchange',
|
||||
'ended',
|
||||
'pause',
|
||||
'play',
|
||||
'timeupdate',
|
||||
'volumechange',
|
||||
] as const;
|
||||
|
||||
export const Attributes = [
|
||||
'autoplay',
|
||||
'controls',
|
||||
'crossorigin',
|
||||
'loop',
|
||||
'muted',
|
||||
'playsinline',
|
||||
'poster',
|
||||
'preload',
|
||||
'src',
|
||||
] as const;
|
||||
|
||||
/** CSS custom property names for video elements. */
|
||||
export const VideoCSSVars = {
|
||||
/** Border radius of the video element. */
|
||||
borderRadius: '--media-video-border-radius',
|
||||
/** Object fit for the video. */
|
||||
objectFit: '--media-object-fit',
|
||||
/** Object position for the video. */
|
||||
objectPosition: '--media-object-position',
|
||||
/** Duration of the caption track transition. */
|
||||
captionTrackDuration: '--media-caption-track-duration',
|
||||
/** Delay before the caption track transition. */
|
||||
captionTrackDelay: '--media-caption-track-delay',
|
||||
/** Vertical offset of the caption track. */
|
||||
captionTrackY: '--media-caption-track-y',
|
||||
} as const;
|
||||
|
||||
/** CSS custom property names for audio elements. */
|
||||
export const AudioCSSVars = {} as const;
|
||||
|
||||
// Minimal template stubs — the builder parses <slot> elements from these.
|
||||
function getVideoTemplateHTML(attrs: Record<string, string>): string {
|
||||
return /*html*/ `
|
||||
<style>
|
||||
video {
|
||||
border-radius: var(${VideoCSSVars.borderRadius});
|
||||
object-fit: var(${VideoCSSVars.objectFit}, contain);
|
||||
object-position: var(${VideoCSSVars.objectPosition}, center);
|
||||
}
|
||||
</style>
|
||||
<slot name="media">
|
||||
<video></video>
|
||||
</slot>
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
function getAudioTemplateHTML(attrs: Record<string, string>): string {
|
||||
return /*html*/ `
|
||||
<style>
|
||||
audio { width: 100%; }
|
||||
</style>
|
||||
<slot name="media">
|
||||
<audio></audio>
|
||||
</slot>
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
// Minimal stubs — the builder only needs to detect these by name, not run them.
|
||||
export function CustomMediaMixin(base: any, _opts: any) {
|
||||
return base;
|
||||
}
|
||||
|
||||
export const CustomVideoElement = class {
|
||||
static getTemplateHTML = getVideoTemplateHTML;
|
||||
};
|
||||
export const CustomAudioElement = class {
|
||||
static getTemplateHTML = getAudioTemplateHTML;
|
||||
};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Mock extending delegate — mirrors MuxMediaDelegate extending HlsMediaDelegate.
|
||||
*
|
||||
* Exercises: delegate inheritance. The builder must walk the extends chain
|
||||
* to extract properties from both this class and its parent (ComplexDelegate).
|
||||
* Child properties override parent properties of the same name.
|
||||
*/
|
||||
import { ComplexDelegate } from '../complex';
|
||||
|
||||
export class ExtendingDelegate extends ComplexDelegate {
|
||||
#playbackId: string = '';
|
||||
#customDomain: string = '';
|
||||
|
||||
/** The playback ID for the video. */
|
||||
get playbackId(): string {
|
||||
return this.#playbackId;
|
||||
}
|
||||
|
||||
set playbackId(value: string) {
|
||||
this.#playbackId = value;
|
||||
}
|
||||
|
||||
/** Custom domain for asset delivery. */
|
||||
get customDomain(): string {
|
||||
return this.#customDomain;
|
||||
}
|
||||
|
||||
set customDomain(value: string) {
|
||||
this.#customDomain = value;
|
||||
}
|
||||
|
||||
/** Overrides parent debug — adds network logging. */
|
||||
get debug(): boolean {
|
||||
return super.debug;
|
||||
}
|
||||
|
||||
set debug(value: boolean) {
|
||||
super.debug = value;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtendingCustomMedia {}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Mock simple delegate — mirrors DashMediaDelegate.
|
||||
*
|
||||
* Exercises: minimal delegate with just src (read-write) and engine (readonly).
|
||||
* No JSDoc on properties — tests that missing descriptions produce undefined.
|
||||
*/
|
||||
export class SimpleDelegate {
|
||||
#src: string = '';
|
||||
#engine: object = {};
|
||||
|
||||
get src(): string {
|
||||
return this.#src;
|
||||
}
|
||||
|
||||
set src(value: string) {
|
||||
this.#src = value;
|
||||
}
|
||||
|
||||
get engine(): object {
|
||||
return this.#engine;
|
||||
}
|
||||
|
||||
attach(_target: EventTarget): void {}
|
||||
detach(): void {}
|
||||
destroy(): void {}
|
||||
}
|
||||
|
||||
export class SimpleCustomMedia {}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Mock background video registration — mirrors define/media/background-video.ts.
|
||||
*
|
||||
* Exercises: exclusion. BackgroundVideo uses MediaAttachMixin(HTMLElement)
|
||||
* without MediaPropsMixin. The builder should discover this file (it has
|
||||
* static tagName) but skip it because parseMixinChain returns null.
|
||||
* Its API reference is manually maintained in MDX (#1243).
|
||||
*/
|
||||
import { BackgroundVideo } from '../../media/background-video';
|
||||
|
||||
export class BackgroundVideoElement extends BackgroundVideo {
|
||||
static readonly tagName = 'background-video';
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Mock complex video element registration — mirrors define/media/hls-video.ts.
|
||||
*
|
||||
* Exercises: element discovery via static tagName in define/media/*.ts,
|
||||
* with a delegate that has JSDoc and overlapping native attributes.
|
||||
*/
|
||||
import { ComplexVideo } from '../../media/complex-video';
|
||||
|
||||
export class ComplexVideoElement extends ComplexVideo {
|
||||
static readonly tagName = 'complex-video';
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Mock media container registration — mirrors define/media/container.ts.
|
||||
*
|
||||
* Exercises: container exclusion. The real container.ts does NOT define a
|
||||
* new class with `static tagName` inline — it imports an already-defined
|
||||
* class. The builder should exclude this from media element discovery.
|
||||
*/
|
||||
class MediaContainerElement {
|
||||
static readonly tagName = 'media-container';
|
||||
}
|
||||
|
||||
// No `export class ... extends` with `static tagName` — the class is
|
||||
// defined elsewhere and only registered here.
|
||||
export { MediaContainerElement };
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Mock extending video element registration — mirrors define/media/mux-video.ts.
|
||||
*
|
||||
* Exercises: element with a delegate that extends another delegate.
|
||||
*/
|
||||
import { ExtendingVideo } from '../../media/extending-video';
|
||||
|
||||
export class ExtendingVideoElement extends ExtendingVideo {
|
||||
static readonly tagName = 'extending-video';
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Mock simple video element registration — mirrors define/media/dash-video.ts.
|
||||
*
|
||||
* Exercises: element discovery via static tagName in define/media/*.ts.
|
||||
*/
|
||||
import { SimpleVideo } from '../../media/simple-video';
|
||||
|
||||
export class SimpleVideoElement extends SimpleVideo {
|
||||
static readonly tagName = 'simple-video';
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Mock background video — mirrors the real BackgroundVideo.
|
||||
*
|
||||
* Exercises: exclusion of elements that use MediaAttachMixin(HTMLElement)
|
||||
* without MediaPropsMixin. The builder's parseMixinChain returns null
|
||||
* because there is no MediaPropsMixin call in the extends chain.
|
||||
*/
|
||||
function MediaAttachMixin(base: any) {
|
||||
return base;
|
||||
}
|
||||
|
||||
export class BackgroundVideo extends MediaAttachMixin(Object) {}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Mock complex media element — mirrors HlsVideo.
|
||||
*
|
||||
* Exercises: standard mixin composition with a complex delegate
|
||||
* that has JSDoc descriptions on its getter/setters.
|
||||
*/
|
||||
import { ComplexCustomMedia, ComplexDelegate } from '../../../../core/src/dom/media/complex';
|
||||
|
||||
// Stubs — the builder parses the AST, it doesn't run the code.
|
||||
function MediaAttachMixin(base: any) {
|
||||
return base;
|
||||
}
|
||||
function MediaPropsMixin(base: any, _delegate: any) {
|
||||
return base;
|
||||
}
|
||||
|
||||
export class ComplexVideo extends MediaPropsMixin(MediaAttachMixin(ComplexCustomMedia), ComplexDelegate) {}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Mock extending media element — mirrors MuxVideo.
|
||||
*
|
||||
* Exercises: media element with a delegate that inherits from another delegate.
|
||||
*/
|
||||
import { ExtendingCustomMedia, ExtendingDelegate } from '../../../../core/src/dom/media/extending';
|
||||
|
||||
function MediaAttachMixin(base: any) {
|
||||
return base;
|
||||
}
|
||||
function MediaPropsMixin(base: any, _delegate: any) {
|
||||
return base;
|
||||
}
|
||||
|
||||
export class ExtendingVideo extends MediaPropsMixin(MediaAttachMixin(ExtendingCustomMedia), ExtendingDelegate) {}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Mock simple media element — mirrors DashVideo.
|
||||
*
|
||||
* Exercises: standard mixin composition with a simple delegate.
|
||||
* The builder follows this import chain to discover the delegate class
|
||||
* and resolve its properties.
|
||||
*/
|
||||
import { SimpleCustomMedia, SimpleDelegate } from '../../../../core/src/dom/media/simple';
|
||||
|
||||
// Stubs — the builder parses the AST, it doesn't run the code.
|
||||
function MediaAttachMixin(base: any) {
|
||||
return base;
|
||||
}
|
||||
function MediaPropsMixin(base: any, _delegate: any) {
|
||||
return base;
|
||||
}
|
||||
|
||||
export class SimpleVideo extends MediaPropsMixin(MediaAttachMixin(SimpleCustomMedia), SimpleDelegate) {}
|
||||
Reference in New Issue
Block a user