-import {defined, isArray, isFunction, isObject, resolveObjectKey, _capitalize} from './helpers.core';
+import {defined, isArray, isFunction, isObject, resolveObjectKey, valueOrDefault, _capitalize} from './helpers.core';
/**
* Creates a Proxy for resolving raw values for options.
[Symbol.toStringTag]: 'Object',
_cacheable: true,
_scopes: scopes,
- override: (scope) => _createResolver([scope].concat(scopes), prefixes),
+ override: (scope) => _createResolver([scope, ...scopes], prefixes),
};
return new Proxy(cache, {
/**
_stack.delete(prop);
if (isObject(value)) {
// When scriptable option returns an object, create a resolver on that.
- value = createSubResolver([value].concat(_proxy._scopes), prop, value);
+ value = createSubResolver(_proxy._scopes, prop, value);
}
return value;
}
const scopes = _proxy._scopes.filter(s => s !== arr);
value = [];
for (const item of arr) {
- const resolver = createSubResolver([item].concat(scopes), prop, item);
+ const resolver = createSubResolver(scopes, prop, item);
value.push(_attachContext(resolver, _context, _subProxy && _subProxy[prop]));
}
}
function createSubResolver(parentScopes, prop, value) {
const set = new Set([value]);
- const {keys, includeParents} = _resolveSubKeys(parentScopes, prop, value);
- for (const key of keys) {
- for (const item of parentScopes) {
+ const lookupScopes = [value, ...parentScopes];
+ const {keys, includeParents} = _resolveSubKeys(lookupScopes, prop, value);
+ while (keys.length) {
+ const key = keys.shift();
+ for (const item of lookupScopes) {
const scope = resolveObjectKey(item, key);
if (scope) {
set.add(scope);
+ // fallback detour?
+ const fallback = scope._fallback;
+ if (defined(fallback)) {
+ keys.push(...resolveFallback(fallback, key, scope).filter(k => k !== key));
+ }
+
} else if (key !== prop && scope === false) {
// If any of the fallback scopes is explicitly false, return false
// For example, options.hover falls back to options.interaction, when
return _createResolver([...set]);
}
+function resolveFallback(fallback, prop, value) {
+ const resolved = isFunction(fallback) ? fallback(prop, value) : fallback;
+ return isArray(resolved) ? resolved : typeof resolved === 'string' ? [resolved] : [];
+}
+
function _resolveSubKeys(parentScopes, prop, value) {
- const fallback = _resolve('_fallback', parentScopes.map(scope => scope[prop] || scope));
+ const fallback = valueOrDefault(_resolve('_fallback', parentScopes.map(scope => scope[prop] || scope)), true);
const keys = [prop];
if (defined(fallback)) {
- const resolved = isFunction(fallback) ? fallback(prop, value) : fallback;
- keys.push(...(isArray(resolved) ? resolved : [resolved]));
+ keys.push(...resolveFallback(fallback, prop, value));
}
- return {keys: keys.filter(v => v), includeParents: fallback !== prop};
+ return {keys: keys.filter(v => v), includeParents: fallback !== false && fallback !== prop};
}
function _resolveWithPrefixes(prop, prefixes, scopes) {
expect(sub.opt).toEqual('opt');
});
- it('should follow _fallback', function() {
- const defaults = {
- interaction: {
- mode: 'test',
- priority: 'fall'
- },
- hover: {
- _fallback: 'interaction',
- priority: 'main'
- }
- };
- const options = {
- interaction: {
- a: 1
- },
- hover: {
- b: 2
- }
- };
- const resolver = _createResolver([options, defaults]);
- expect(resolver.hover).toEqualOptions({
- mode: 'test',
- priority: 'main',
- a: 1,
- b: 2
- });
- });
-
it('should support overriding options', function() {
const defaults = {
option1: 'defaults1',
expect(resolver.getter).toEqual('options getter');
});
+
+ describe('_fallback', function() {
+ it('should follow simple _fallback', function() {
+ const defaults = {
+ interaction: {
+ mode: 'test',
+ priority: 'fall'
+ },
+ hover: {
+ _fallback: 'interaction',
+ priority: 'main'
+ }
+ };
+ const options = {
+ interaction: {
+ a: 1
+ },
+ hover: {
+ b: 2
+ }
+ };
+ const resolver = _createResolver([options, defaults]);
+ expect(resolver.hover).toEqualOptions({
+ mode: 'test',
+ priority: 'main',
+ a: 1,
+ b: 2
+ });
+ });
+
+ it('should not fallback when _fallback is false', function() {
+ const defaults = {
+ hover: {
+ _fallback: false,
+ a: 'defaults.hover'
+ },
+ controllers: {
+ y: 'defaults.controllers',
+ bar: {
+ z: 'defaults.controllers.bar',
+ hover: {
+ b: 'defaults.controllers.bar.hover'
+ }
+ }
+ },
+ x: 'defaults root'
+ };
+ const options = {
+ x: 'options',
+ hover: {
+ c: 'options.hover',
+ sub: {
+ f: 'options.hover.sub'
+ }
+ },
+ controllers: {
+ y: 'options.controllers',
+ bar: {
+ z: 'options.controllers.bar',
+ hover: {
+ d: 'options.controllers.bar.hover',
+ sub: {
+ e: 'options.controllers.bar.hover.sub'
+ }
+ }
+ }
+ }
+ };
+ const resolver = _createResolver([options, options.controllers.bar, options.controllers, defaults.controllers.bar, defaults.controllers, defaults]);
+ expect(resolver.hover).toEqualOptions({
+ a: 'defaults.hover',
+ b: 'defaults.controllers.bar.hover',
+ c: 'options.hover',
+ d: 'options.controllers.bar.hover',
+ e: undefined,
+ f: undefined,
+ x: undefined,
+ y: undefined,
+ z: undefined
+ });
+ expect(resolver.hover.sub).toEqualOptions({
+ a: undefined,
+ b: undefined,
+ c: undefined,
+ d: undefined,
+ e: 'options.controllers.bar.hover.sub',
+ f: 'options.hover.sub',
+ x: undefined,
+ y: undefined,
+ z: undefined
+ });
+ });
+
+ it('should fallback to specific scope', function() {
+ const defaults = {
+ hover: {
+ _fallback: 'hover',
+ a: 'defaults.hover'
+ },
+ controllers: {
+ y: 'defaults.controllers',
+ bar: {
+ z: 'defaults.controllers.bar',
+ hover: {
+ b: 'defaults.controllers.bar.hover'
+ }
+ }
+ },
+ x: 'defaults root'
+ };
+ const options = {
+ x: 'options',
+ hover: {
+ c: 'options.hover',
+ sub: {
+ f: 'options.hover.sub'
+ }
+ },
+ controllers: {
+ y: 'options.controllers',
+ bar: {
+ z: 'options.controllers.bar',
+ hover: {
+ d: 'options.controllers.bar.hover',
+ sub: {
+ e: 'options.controllers.bar.hover.sub'
+ }
+ }
+ }
+ }
+ };
+ const resolver = _createResolver([options, options.controllers.bar, options.controllers, defaults.controllers.bar, defaults.controllers, defaults]);
+ expect(resolver.hover).toEqualOptions({
+ a: 'defaults.hover',
+ b: 'defaults.controllers.bar.hover',
+ c: 'options.hover',
+ d: 'options.controllers.bar.hover',
+ e: undefined,
+ f: undefined,
+ x: undefined,
+ y: undefined,
+ z: undefined
+ });
+ expect(resolver.hover.sub).toEqualOptions({
+ a: 'defaults.hover',
+ b: 'defaults.controllers.bar.hover',
+ c: 'options.hover',
+ d: 'options.controllers.bar.hover',
+ e: 'options.controllers.bar.hover.sub',
+ f: 'options.hover.sub',
+ x: undefined,
+ y: undefined,
+ z: undefined
+ });
+ });
+
+ it('should fallback throuhg multiple routes', function() {
+ const defaults = {
+ root: {
+ a: 'root'
+ },
+ level1: {
+ _fallback: 'root',
+ b: 'level1',
+ },
+ level2: {
+ _fallback: 'level1',
+ level1: {
+ g: 'level2.level1'
+ },
+ c: 'level2',
+ sublevel1: {
+ d: 'sublevel1'
+ },
+ sublevel2: {
+ e: 'sublevel2',
+ level1: {
+ f: 'sublevel2.level1'
+ }
+ }
+ }
+ };
+ const resolver = _createResolver([defaults]);
+ expect(resolver.level1).toEqualOptions({
+ a: 'root',
+ b: 'level1',
+ c: undefined
+ });
+ expect(resolver.level2).toEqualOptions({
+ a: 'root',
+ b: 'level1',
+ c: 'level2',
+ d: undefined
+ });
+ expect(resolver.level2.sublevel1).toEqualOptions({
+ a: 'root',
+ b: 'level1',
+ c: 'level2', // TODO: this should be undefined
+ d: 'sublevel1',
+ e: undefined,
+ f: undefined,
+ g: 'level2.level1'
+ });
+ expect(resolver.level2.sublevel2).toEqualOptions({
+ a: 'root',
+ b: 'level1',
+ c: 'level2', // TODO: this should be undefined
+ d: undefined,
+ e: 'sublevel2',
+ f: undefined,
+ g: 'level2.level1'
+ });
+ expect(resolver.level2.sublevel2.level1).toEqualOptions({
+ a: 'root',
+ b: 'level1',
+ c: 'level2', // TODO: this should be undefined
+ d: undefined,
+ e: 'sublevel2', // TODO: this should be undefined
+ f: 'sublevel2.level1',
+ g: 'level2.level1'
+ });
+ });
+ });
});
describe('_attachContext', function() {