Skip to content

Commit 9f87686

Browse files
committed
Add instanceof test type and use it to fix Worklet tests
1 parent b1956da commit 9f87686

File tree

7 files changed

+112
-53
lines changed

7 files changed

+112
-53
lines changed

src/classes/Feature/InterfaceFeature.js

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,74 @@ import supportsInterface from '../../supports/interface.js';
44
import supportsMember from '../../supports/member.js';
55

66
export class MemberFeature extends Feature {
7-
constructor (def, parent) {
8-
super(def, parent);
7+
static children = {
8+
/** The member needs to be an instance of this class */
9+
instanceof: { type: MemberFeature },
10+
}
11+
12+
static gatingTest = true;
13+
14+
get base () {
15+
return this.closest(f => f instanceof InterfaceFeature)?.base;
16+
}
917

10-
let fromParent = this.def.fromParent;
11-
this.memberType = fromParent === 'properties' || fromParent === 'functions' ? 'static' : 'instance';
12-
this.memberKind = fromParent === 'methods' || fromParent === 'functions' ? 'method' : 'property';
13-
this.base = this.parent.base;
18+
get memberType () {
19+
return this.def.fromParent === 'instanceof' ? this.parent.def.fromParent : this.def.fromParent;
1420
}
1521

1622
get code () {
17-
if (this.memberKind === 'method') {
23+
if (this.def.fromParent === 'functions' || this.def.fromParent === 'methods') {
1824
return this.id + '()';
1925
}
2026

27+
if (this.def.fromParent === 'instanceof') {
28+
return 'instanceof ' + this.id;
29+
}
30+
2131
return this.id;
2232
}
2333

2434
testSelf () {
25-
let {memberType: type, memberKind: kind, base} = this;
35+
let options = {};
2636

27-
let interfaceName = base.id;
28-
let interfaceCallback = this.interface ?? base.interface;
29-
let context = {name: interfaceName, callback: interfaceCallback};
37+
let isInstanceOf = this.def.fromParent === 'instanceof';
38+
let memberType = this.memberType;
3039

31-
return supportsMember(this.id, {type, kind, context});
40+
options.path = memberType === 'properties' || memberType === 'functions' ? '' : 'prototype';
41+
options.typeof = memberType === 'methods' || memberType === 'functions' ? 'function' : '';
42+
options.instanceof = isInstanceOf ? this.id : '';
43+
let member = isInstanceOf ? this.parent.id : this.id;
44+
let base = this.base;
45+
options.context = {name: base.id, callback: base.interface};
46+
47+
return supportsMember(member, options);
3248
}
3349
}
3450

3551
export default class InterfaceFeature extends Feature {
3652
static children = {
53+
/** @deprecated Alias of members */
3754
tests: { type: MemberFeature },
55+
56+
/** The object needs to be an instance of this class */
57+
instanceof: { type: InterfaceFeature },
58+
59+
/** The object should be a subclass of this class */
3860
extends: { type: InterfaceFeature },
39-
members: { type: MemberFeature },
40-
methods: { type: MemberFeature },
61+
62+
/** Properties that should exist on this object */
4163
properties: { type: MemberFeature },
64+
65+
/** Properties that should exist on this object and should be functions */
4266
functions: { type: MemberFeature },
67+
68+
/** Properties that should exist on this object's prototype */
69+
members: { type: MemberFeature },
70+
71+
/** Properties that should exist on this object's prototype and should be functions */
72+
methods: { type: MemberFeature },
4373
}
74+
4475
static gatingTest = true;
4576

4677
constructor (def, parent) {
@@ -54,6 +85,10 @@ export default class InterfaceFeature extends Feature {
5485
return 'extends ' + this.id;
5586
}
5687

88+
if (this.def.fromParent === 'instanceof') {
89+
return 'instanceof ' + this.id;
90+
}
91+
5792
return this.id;
5893
}
5994

@@ -76,6 +111,13 @@ export default class InterfaceFeature extends Feature {
76111
return testExtends(Class, SuperClass);
77112
}
78113

114+
if (this.def.fromParent === 'instanceof') {
115+
let Class = this.id;
116+
let name = this.parent.id;
117+
118+
return supportsInterface(name, {instanceof: Class});
119+
}
120+
79121
return supportsInterface(this.id);
80122
}
81123
}

src/supports/interface.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,17 @@ import { domPrefixes as prefixes, prefixCamelCase as prefixName } from './shared
22

33
let cached = {};
44

5-
/**
6-
* Low-level, no caching, no prefixes
7-
* @param {string} name
8-
*/
9-
export function isSupported (name) {
10-
return name in window;
11-
}
12-
13-
export default function (name) {
5+
export default function (name, options = {}) {
146
let cachedResult = cached[name];
157
let success, prefix, resolvedName;
168

179
if (cachedResult === undefined) {
18-
prefix = prefixes.find(prefix => isSupported(prefixName(prefix, name)));
10+
prefix = prefixes.find(prefix => prefixName(prefix, name) in globalThis);
1911

2012
if (prefix === undefined && name.indexOf('CSS') === 0) {
2113
// Last ditch effort to find a prefix: try CSS[Prefix]Name
2214
let nameWithoutCSS = name.slice(3);
23-
prefix = prefixes.find(prefix => isSupported('CSS' + prefixName(prefix, nameWithoutCSS)));
15+
prefix = prefixes.find(prefix => 'CSS' + prefixName(prefix, nameWithoutCSS) in globalThis);
2416

2517
if (prefix !== undefined) {
2618
resolvedName = 'CSS' + prefixName(prefix, nameWithoutCSS);
@@ -39,8 +31,23 @@ export default function (name) {
3931
resolvedName = cachedResult === true ? name : (cachedResult || undefined);
4032
}
4133

34+
let object = globalThis[resolvedName];
35+
36+
if (options.instanceof) {
37+
let SuperClass = typeof options.instanceof === 'string' ? globalThis[options.instanceof] : options.instanceof;
38+
39+
if (!SuperClass) {
40+
return {success: false, object, note: `Class "${options.instanceof}" not found`};
41+
}
42+
43+
if (!(object instanceof SuperClass)) {
44+
return {success: false, object, note: `Not an instance of ${SuperClass.name}`};
45+
}
46+
}
47+
4248
return {
4349
success,
4450
name: resolvedName,
51+
object,
4552
};
4653
}

src/supports/member.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default function member (name, options) {
1818
}
1919

2020
if (typeof options === 'string') {
21-
options = {context: options};
21+
options = {context: {name: options}};
2222
}
2323
else if (!options.context) {
2424
options = {context: options};
@@ -39,9 +39,8 @@ export default function member (name, options) {
3939
object = globalThis[contextName];
4040
}
4141

42-
if (options.type !== 'static' && !(contextObject || callback)) {
43-
// Non-static member, and the object was not provided
44-
object = object.prototype;
42+
if (options.path) {
43+
object = object[options.path];
4544
}
4645

4746
if (!object) {
@@ -51,22 +50,38 @@ export default function member (name, options) {
5150
let prefix = prefixes.find(prefix => prefixName(prefix, name) in object);
5251

5352
if (prefix === undefined) {
54-
return {success: false};
53+
// Not supported
54+
return {success: false, object};
5555
}
5656

5757
let resolvedName = prefixName(prefix, name);
58+
let memberValue = object[resolvedName];
5859

59-
if (options.type === "function") {
60+
if (options.typeof === "function") {
6061
let actualType = typeof object[resolvedName];
6162

6263
if (actualType !== "function") {
63-
return {success: false, type: actualType};
64+
return {success: false, type: actualType, object, memberValue};
65+
}
66+
}
67+
68+
if (options.instanceof) {
69+
let Class = typeof options.instanceof === 'string' ? globalThis[options.instanceof] : options.instanceof;
70+
71+
if (!Class) {
72+
return {success: false, object, note: `Class "${options.instanceof}" not found`};
73+
}
74+
75+
if (!(memberValue instanceof Class)) {
76+
return {success: false, object, note: `Object is not an instance of ${Class.name}`};
6477
}
6578
}
6679

6780
return {
6881
success: true,
6982
prefix,
7083
resolved: resolvedName,
84+
object,
85+
memberValue,
7186
};
7287
}

tests/css-animation-worklet-1.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,11 @@ export default {
77
interfaces: {
88
CSS: {
99
link: '#animation-worklet-desc',
10-
properties: ['animationWorklet'],
11-
},
12-
Worklet: {
13-
link: '#animation-worklet-desc',
14-
interface: function() {
15-
return CSS.animationWorklet;
10+
properties: {
11+
animationWorklet: {
12+
instanceof: 'Worklet',
13+
}
1614
},
17-
methods: ['addModule'],
1815
},
1916
WorkletAnimation: {
2017
link: '#worklet-animation-interface',

tests/css-layout-api-1.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,10 @@ export default {
1313
interfaces: {
1414
CSS: {
1515
link: '#layout-worklet',
16-
properties: ['layoutWorklet'],
17-
},
18-
Worklet: {
19-
link: '#layout-worklet',
20-
methods: ['addModule'],
21-
interface: function () {
22-
return CSS.layoutWorklet;
16+
properties: {
17+
layoutWorklet: {
18+
instanceof: 'Worklet',
19+
}
2320
},
2421
},
2522
},

tests/css-paint-api-1.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,10 @@ export default {
1717
interfaces: {
1818
CSS: {
1919
link: '#paint-worklet',
20-
properties: ['paintWorklet'],
21-
},
22-
Worklet: {
23-
link: '#paint-worklet',
24-
methods: ['addModule'],
25-
interface: function () {
26-
return CSS.paintWorklet;
20+
properties: {
21+
paintWorklet: {
22+
instanceof: 'Worklet',
23+
}
2724
},
2825
},
2926
}

tests/html.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,9 @@ export default {
2525
extends: 'Event',
2626
members: ['viewTransition'],
2727
},
28+
Worklet: {
29+
link: 'https://html.spec.whatwg.org/multipage/worklets.html#worklets-worklet',
30+
methods: ['addModule'],
31+
}
2832
}
2933
};

0 commit comments

Comments
 (0)