Skip to content

Commit

Permalink
fix(theme): mapping application order. Closes #218 (#220)
Browse files Browse the repository at this point in the history
* fix(theme): mapping application order

* test(theme): add uncovered cases tests & refactor to match snapshots

* refactor(theme): apply naming convention
  • Loading branch information
artyorsh authored and malashkevich committed Dec 28, 2018
1 parent 48c6cfe commit ca37382
Show file tree
Hide file tree
Showing 5 changed files with 619 additions and 516 deletions.
40 changes: 40 additions & 0 deletions src/framework/theme/service/mappingUtil.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {

export const APPEARANCE_DEFAULT = 'default';

const FALLBACK_MAPPING_APPEARANCE = {};
const FALLBACK_MAPPING_VARIANT = {};

/**
* @param component: string - component name
* @param mapping: ThemeMappingType - theme mapping configuration object
Expand Down Expand Up @@ -42,6 +45,23 @@ export function getAppearanceMappingSafe(mapping: ComponentMappingType,
return getAppearanceMapping(mapping, appearance) || fallback;
}

export function getStateAppearanceMapping(mapping: ComponentMappingType,
appearance: string,
state: string): any {

const appearanceMapping = getAppearanceMappingSafe(mapping, appearance, FALLBACK_MAPPING_APPEARANCE);

return getMappingState(appearanceMapping, state);
}

export function getStatelessAppearanceMapping(mapping: ComponentMappingType,
appearance: string): any {

const { state, ...params } = getAppearanceMappingSafe(mapping, appearance, FALLBACK_MAPPING_APPEARANCE);

return params;
}

export function getAppearanceVariants(mapping: ComponentMappingType,
appearance: string): VariantGroupType | undefined {

Expand Down Expand Up @@ -71,6 +91,26 @@ export function getVariantMappingSafe(mapping: ComponentMappingType,
return getVariantMapping(mapping, appearance, variant) || fallback;
}

export function getStateVariantMapping(mapping: ComponentMappingType,
appearance: string,
variant: string,
state: string): any {

const variantMapping = getVariantMappingSafe(mapping, appearance, variant, FALLBACK_MAPPING_VARIANT);

return getMappingState(variantMapping, state);
}

export function getStatelessVariantMapping(mapping: ComponentMappingType,
appearance: string,
variant: string): any {

const { state, ...params } = getVariantMappingSafe(mapping, appearance, variant, FALLBACK_MAPPING_VARIANT);

return params;
}


export function getMappingState(mapping: MappingType,
state: string): StateType | undefined {

Expand Down
123 changes: 60 additions & 63 deletions src/framework/theme/service/styleUtil.service.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,56 @@
import {
getAppearanceMappingSafe,
getVariantMappingSafe,
APPEARANCE_DEFAULT,
getStatelessAppearanceMapping,
getStatelessVariantMapping,
getStateAppearanceMapping,
getStateVariantMapping,
} from './mappingUtil.service';
import {
ComponentMappingType,
MappingType,
StateType,
ThemeType,
StyleType,
} from '../component';

const SEPARATOR_STATE = '.';
const FALLBACK_MAPPING_APPEARANCE: MappingType = {};
const FALLBACK_MAPPING_VARIANT: MappingType = {};

/**
* Creates style object for variant/list of variants and its state/list of states(optional)
* Creates style object for variant/list of variants(optional) and its state/list of states(optional)
*
* Examples
* Example
*
* 1. Default:
* appearance = 'outline';
* variants = ['success', 'large'];
* state = ['active', 'checked'];
*
* <Component />
* a = `default` + `outline` - acc appearance (apce) mapping
*
* - will return styles for `default` appearance
* v1 = `success` of `default` - `success` variant mapping of `default` apce
* v2 = `success` of `outline` - `success` variant mapping of `outline` apce
* v3 = `large` of `default` - `large` variant mapping of `default` apce
* v4 = `large` of `outline` - `large` variant mapping of `outline` apce
*
* 2. Custom appearance:
* s1 = `active` of `default` - `active` state mapping of `default` apce
* s2 = `active` of `outline` - `active` state mapping of `outline` apce
* s3 = `active` of `default success` - `active` state mapping of `success` variant of `default` apce
* s4 = `active` of `outline success` - `active` state mapping of `success` variant of `outline` apce
* s5 = `active` of `default large` - `active` state mapping of `large` variant of `default` apce
* s6 = `active` of `outline large` - `active` state mapping of `large` variant of `outline` apce
*
* <Component appearance='bold'/>
* s7 = `checked` of `default` - `checked` state mapping of `default` apce
* s8 = `checked` of `outline` - `checked` state mapping of `outline` apce
* s9 = `checked` of `default success` - `checked` state mapping of `success` variant of `default` apce
* s10 = `checked` of `outline success` - `checked` state mapping of `success` variant of `outline` apce
* s11 = `checked` of `default large` - `checked` state mapping of `large` variant of `default` apce
* s12 = `checked` of `outline large` - `checked` state mapping of `large` variant of `outline` apce
*
* - will return styles for `default` + `bold` appearances
* s13 = `active.checked` of `default` - `active.checked` state mapping of `default` apce
* s14 = `active.checked` of `outline` - `active.checked` state mapping of `outline` apce
* s15 = `active.checked` of `default success` - `active.checked` state mapping of `success` variant of `default` apce
* s16 = `active.checked` of `outline success` - `active.checked` state mapping of `success` variant of `outline` apce
* s17 = `active.checked` of `default large` - `active.checked` state mapping of `large` variant of `default` apce
* s18 = `active.checked` of `outline large` - `active.checked` state mapping of `large` variant of `outline` apce
*
* 3. With Variants:
*
* <Component status='success' size='tiny'>
*
* - will return styles for `default` appearance + (`success` + `tiny`) variants
*
* 4. With states:
*
* <Component status='success'> which is currently `active` and `checked`
*
* - will return styles for
* `default` appearance + (`active` + `checked` + `active.checked`) states
* + `success` variant + (`active` + `checked` + `active.checked`) variant states
*
* State merging is the same as variant merging
* But state parameters override variant parameters
* res = a + (v1 + v2 + ... + vn) + (s1 + s2 + ... + sn)
*
* @param theme: ThemeType - theme object
* @param mapping: ComponentMappingType - component theme mapping configuration
Expand All @@ -56,52 +59,42 @@ const FALLBACK_MAPPING_VARIANT: MappingType = {};
* @param states: string[] - states in which component is. Default is []
*
* @return StyleType - compiled component styles declared in mappings, mapped to theme values
*
*/
export function createStyle(theme: ThemeType,
mapping: ComponentMappingType,
appearance: string = APPEARANCE_DEFAULT,
variants: string[] = [],
states: string[] = []): StyleType {

const appearanceMapping = createAppearanceMapping(
mapping,
normalizeAppearance(appearance),
normalizeVariants(variants),
normalizeStates(states),
);
return createStyleFromMapping(appearanceMapping, theme);
}
const normalizedAppearance = normalizeAppearance(appearance);
const normalizedVariants = normalizeVariants(variants);
const normalizedStates = normalizeStates(states);

function createAppearanceMapping(mapping: ComponentMappingType,
appearances: string[],
variants: string[],
states: string[]): any {
const appearanceMapping = reduce(normalizedAppearance, apce => {
return getStatelessAppearanceMapping(mapping, apce);
});

return appearances.reduce((acc, current) => {
const { state, ...appearanceMapping } = getAppearanceMappingSafe(mapping, current, FALLBACK_MAPPING_APPEARANCE);
const stateMapping = state && createStateMapping(state, states);
const variantMapping = createVariantMapping(mapping, current, variants, states);
const variantMapping = reduce(normalizedVariants, variant => {
return reduce(normalizedAppearance, apce => {
return getStatelessVariantMapping(mapping, apce, variant);
});
});

return { ...acc, ...appearanceMapping, ...stateMapping, ...variantMapping };
}, {});
}
const stateMapping = reduce(normalizedStates, state => {
const appearanceStateMapping = reduce(normalizedAppearance, apce => {
return getStateAppearanceMapping(mapping, apce, state);
});

function createVariantMapping(mapping: ComponentMappingType,
appearance: string,
variants: string[],
states: string[]): any {
const variantStateMapping = reduce(normalizedVariants, variant => {
return reduce(normalizedAppearance, apce => {
return getStateVariantMapping(mapping, apce, variant, state);
});
});

return variants.reduce((acc, current) => {
const { state, ...variantMapping } = getVariantMappingSafe(mapping, appearance, current, FALLBACK_MAPPING_VARIANT);
const stateMapping = state && createStateMapping(state, states);
return {...appearanceStateMapping, ...variantStateMapping};
});

return { ...acc, ...variantMapping, ...stateMapping };
}, {});
}

function createStateMapping(state: StateType, states: string[]): any {
return states.reduce((acc, current) => ({ ...acc, ...state[current] }), {});
return createStyleFromMapping({ ...appearanceMapping, ...variantMapping, ...stateMapping }, theme);
}

function createStyleFromMapping(mapping: any, theme: ThemeType): StyleType {
Expand All @@ -112,6 +105,10 @@ function createStyleFromMapping(mapping: any, theme: ThemeType): StyleType {
}, {});
}

function reduce(items: string[], next: (item: string) => any): any {
return items.reduce((acc, current) => ({ ...acc, ...next(current) }), {});
}

/**
* Creates normalized to design system array of component appearances
*
Expand Down
161 changes: 161 additions & 0 deletions src/framework/theme/tests/__snapshots__/style.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`@style: service methods checks * styling * custom appearance * no variant and no state 1`] = `
Object {
"borderColor": "#9E9E9E",
"borderWidth": 4,
"innerSize": 24,
"selectColor": "transparent",
"size": 36,
}
`;

exports[`@style: service methods checks * styling * custom appearance * with state * explicit (should apply own) 1`] = `
Object {
"borderColor": "#E0E0E0",
"borderWidth": 4,
"innerSize": 24,
"selectColor": "transparent",
"size": 36,
}
`;

exports[`@style: service methods checks * styling * custom appearance * with state * implicit (should apply from default appearance) 1`] = `
Object {
"borderColor": "#2196F3",
"borderWidth": 4,
"innerSize": 24,
"selectColor": "#2196F3",
"size": 36,
}
`;

exports[`@style: service methods checks * styling * custom appearance * with variant * explicit (should apply own) 1`] = `
Object {
"borderColor": "#009688",
"borderWidth": 4,
"innerSize": 24,
"selectColor": "transparent",
"size": 36,
}
`;

exports[`@style: service methods checks * styling * custom appearance * with variant * implicit (should apply from default appearance) 1`] = `
Object {
"borderColor": "#9E9E9E",
"borderWidth": 4,
"innerSize": 28,
"selectColor": "transparent",
"size": 42,
}
`;

exports[`@style: service methods checks * styling * default appearance * no variant and no state 1`] = `
Object {
"borderColor": "#9E9E9E",
"borderWidth": 2,
"innerSize": 24,
"selectColor": "transparent",
"size": 36,
}
`;

exports[`@style: service methods checks * styling * default appearance * with state * multiple 1`] = `
Object {
"borderColor": "#1976D2",
"borderWidth": 2,
"innerSize": 24,
"selectColor": "#2196F3",
"size": 36,
}
`;

exports[`@style: service methods checks * styling * default appearance * with state * single 1`] = `
Object {
"borderColor": "#616161",
"borderWidth": 2,
"innerSize": 24,
"selectColor": "transparent",
"size": 36,
}
`;

exports[`@style: service methods checks * styling * default appearance * with variant * multiple * no state 1`] = `
Object {
"borderColor": "#9E9E9E",
"borderWidth": 2,
"innerSize": 28,
"selectColor": "transparent",
"size": 42,
}
`;

exports[`@style: service methods checks * styling * default appearance * with variant * multiple * with state * multiple 1`] = `
Object {
"borderColor": "#00796B",
"borderWidth": 2,
"innerSize": 28,
"selectColor": "#009688",
"size": 42,
}
`;

exports[`@style: service methods checks * styling * default appearance * with variant * multiple * with state * single 1`] = `
Object {
"borderColor": "#616161",
"borderWidth": 2,
"innerSize": 28,
"selectColor": "transparent",
"size": 42,
}
`;

exports[`@style: service methods checks * styling * default appearance * with variant * single * no state 1`] = `
Object {
"borderColor": "#9E9E9E",
"borderWidth": 2,
"innerSize": 24,
"selectColor": "transparent",
"size": 36,
}
`;

exports[`@style: service methods checks * styling * default appearance * with variant * single * with state * multiple 1`] = `
Object {
"borderColor": "#00796B",
"borderWidth": 2,
"innerSize": 24,
"selectColor": "#009688",
"size": 36,
}
`;

exports[`@style: service methods checks * styling * default appearance * with variant * single * with state * single explicit (should apply own) 1`] = `
Object {
"borderColor": "#009688",
"borderWidth": 2,
"innerSize": 24,
"selectColor": "#009688",
"size": 36,
}
`;

exports[`@style: service methods checks * styling * default appearance * with variant * single * with state * single implicit (should apply from appearance) 1`] = `
Object {
"borderColor": "#616161",
"borderWidth": 2,
"innerSize": 24,
"selectColor": "transparent",
"size": 36,
}
`;

exports[`@style: service methods checks * styling * undefined appearance * no variant and no state (should apply default appearance) 1`] = `
Object {
"borderColor": "#9E9E9E",
"borderWidth": 2,
"innerSize": 24,
"selectColor": "transparent",
"size": 36,
}
`;
Loading

0 comments on commit ca37382

Please sign in to comment.