Skip to content

Commit

Permalink
Feat/add option categories page 1732 (#328)
Browse files Browse the repository at this point in the history
* feat: validate optionCategories in prefab components

* feat: add option categories validation to wrapper options

* test: add tests

* feat: throw when option category member refers to non-existing option
  • Loading branch information
NiekBetty authored Jul 28, 2022
1 parent c80e48e commit 0ee9ab4
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 28 deletions.
57 changes: 57 additions & 0 deletions __tests__/utils/validation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import test, { ExecutionContext } from 'ava';

import { checkOptionCategoryReferences } from '../../src/utils/validation';
import type { Prefab } from '../../src/types';

type Context = ExecutionContext<unknown>;

test('Throws when option category references do not match an option', (t: Context): void => {
const prefabs = [
{
name: 'Component name',
icon: 'TitleIcon',
category: 'CONTENT',
structure: [
{
type: 'WRAPPER',
optionCategories: [{ label: 'Category 2', members: ['foo'] }],
options: [
{
key: '0',
type: 'LINKED_OPTION',
value: {
ref: {
componentId: '#textComponent',
optionId: '#textOption',
},
},
},
],
descendants: [
{
ref: {
id: '#textComponent',
},
name: 'Text',
optionCategories: [{ label: 'Category 1', members: ['foo'] }],
options: [
{
ref: {
id: '#textOption',
},
value: '',
label: 'something',
key: 'option1',
type: 'TEXT',
},
],
descendants: [],
},
],
},
],
},
] as unknown as Prefab[];

t.throws(() => checkOptionCategoryReferences(prefabs));
});
128 changes: 102 additions & 26 deletions __tests__/validations/component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -318,25 +318,90 @@ test('Does not throw when nesting wrapper objects', (t: Context): void => {
t.notThrows(() => validatePrefabs(prefabs));
});

test('Does throw when wrapper has options (linked options not supported yet)', (t: Context): void => {
test('Does not throw when component option categories are valid', (t: Context): void => {
const prefabs = [
{
name: 'Component Name',
name: 'Component name',
icon: 'TitleIcon',
category: 'CONTENT',
structure: [
{
name: 'Text',
optionCategories: [
{ label: 'Category 1', members: ['option1'] },
{ label: 'Category 2', members: ['option2'] },
],
options: [
{
value: '',
label: 'something',
key: 'option1',
type: 'TEXT',
},
{
value: '',
label: 'something',
key: 'option2',
type: 'TEXT',
},
],
descendants: [],
},
],
},
] as unknown as Prefab[];

t.notThrows(() => validatePrefabs(prefabs));
});

test('Does not throw when wrapper option categories are valid', (t: Context): void => {
const prefabs = [
{
name: 'Component name',
icon: 'TitleIcon',
category: 'CONTENT',
structure: [
{
type: 'WRAPPER',
optionCategories: [{ label: 'Category 1', members: ['0'] }],
options: [
{
type: "LINKED",
key: '0',
type: 'LINKED_OPTION',
value: {
ref: {
componentId: '#componentId1',
optionId: '#componentId1OptionId1'
}
}
}
optionId: '#componentId1OptionId1',
},
},
},
],
descendants: [],
},
],
},
] as unknown as Prefab[];

t.notThrows(() => validatePrefabs(prefabs));
});

test('Throws when component option category has no label', (t: Context): void => {
const prefabs = [
{
name: 'Component name',
icon: 'TitleIcon',
category: 'CONTENT',
structure: [
{
name: 'Text',
optionCategories: [{ members: ['option1'] }],
options: [
{
value: '',
label: 'something',
key: 'option1',
type: 'TEXT',
},
],
descendants: [],
},
Expand All @@ -347,40 +412,52 @@ test('Does throw when wrapper has options (linked options not supported yet)', (
t.throws(() => validatePrefabs(prefabs));
});

test('Does throw when a wrapper nested in the structure has options (linked options not supported yet)', (t: Context): void => {
test('Throws when component option category has no entries', (t: Context): void => {
const prefabs = [
{
name: 'Component Name',
name: 'Component name',
icon: 'TitleIcon',
category: 'CONTENT',
structure: [
{
name: 'something',
name: 'Text',
optionCategories: [],
options: [
{
value: '',
label: 'something',
key: 'something',
key: 'option1',
type: 'TEXT',
},
],
descendants: [
descendants: [],
},
],
},
] as unknown as Prefab[];

t.throws(() => validatePrefabs(prefabs));
});

test('Throws when component option category members has no entries', (t: Context): void => {
const prefabs = [
{
name: 'Component name',
icon: 'TitleIcon',
category: 'CONTENT',
structure: [
{
name: 'Text',
optionCategories: [{ label: 'Category 1', members: [] }],
options: [
{
type: 'WRAPPER',
options: [
{
type: "LINKED",
value: {
ref: {
componentId: '#componentId1',
optionId: '#componentId1OptionId1'
}
}
}
],
descendants: [],
value: '',
label: 'something',
key: 'option1',
type: 'TEXT',
},
],
descendants: [],
},
],
},
Expand Down Expand Up @@ -601,7 +678,6 @@ test('Dont throw when prefab component option has a ref', (t: Context): void =>
t.pass();
});


test('Throw when the prefabs option type is not referring to one the correct types', (t: Context): void => {
const prefabs = [
{
Expand Down
7 changes: 6 additions & 1 deletion src/bb-components-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import { checkUpdateAvailableCLI } from './utils/checkUpdateAvailable';
import hash from './utils/hash';
import readFilesByType from './utils/readFilesByType';
import transpile from './utils/transpile';
import { checkNameReferences } from './utils/validation';
import {
checkNameReferences,
checkOptionCategoryReferences,
} from './utils/validation';
import validateComponents from './validations/component';
import validateInteractions from './validations/interaction';
import validatePrefabs from './validations/prefab';
Expand Down Expand Up @@ -331,6 +334,8 @@ void (async (): Promise<void> => {
interactions && validateInteractions(interactions),
]);

checkOptionCategoryReferences(prefabs);

const componentsWithHash = components.map((component) => {
return {
...component,
Expand Down
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export type PrefabPartial = {
export type PrefabWrapper = {
type: 'WRAPPER';
descendants: PrefabReference[];
optionCategories?: PrefabComponentOptionCategory[];
options: PrefabComponentOption[];
};
export interface PrefabComponent {
Expand Down Expand Up @@ -98,6 +99,7 @@ export interface PrefabComponent {
};
};
descendants: PrefabReference[];
optionCategories?: PrefabComponentOptionCategory[];
options: PrefabComponentOption[];
ref?: {
id: string;
Expand Down Expand Up @@ -132,6 +134,12 @@ export interface ValueRef {
};
}

export type PrefabComponentOptionCategory = {
label: string;
extended?: boolean;
members: string[];
};

export type PrefabComponentOption = PrefabComponentOptionBase &
(ValueDefault | ValueRef);

Expand Down
34 changes: 34 additions & 0 deletions src/utils/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,37 @@ export const checkNameReferences = (
structure.forEach(checkComponentReferenceNames(componentNames, name));
});
};

export function checkOptionCategoryReferences(prefabs: Prefab[]): void {
function innerFn(structure: PrefabReference[], name: string): void {
structure.forEach((prefabReference) => {
if (
prefabReference.type === undefined ||
prefabReference.type === 'COMPONENT' ||
prefabReference.type === 'WRAPPER'
) {
if (prefabReference?.optionCategories) {
prefabReference.optionCategories.forEach((category) => {
category.members.forEach((member) => {
if (
!prefabReference.options.some((option) => member === option.key)
) {
throw new Error(
chalk.red(
`\nOption category member: "${member}" references to non-existing option\n\nat prefab: ${name}`,
),
);
}
});
});
}

innerFn(prefabReference.descendants, name);
}
});
}

prefabs.forEach((prefab) => {
innerFn(prefab.structure, prefab.name);
});
}
8 changes: 7 additions & 1 deletion src/validations/prefab/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {
PrefabReference,
} from '../../types';
import { findDuplicates } from '../../utils/validation';
import { optionSchema, linkedOptionSchema } from './componentOption';
import {
optionCategorySchema,
optionSchema,
linkedOptionSchema,
} from './componentOption';

type StyleValidator = Record<Component['styleType'], Joi.ObjectSchema>;
type PrefabTypes = 'partial' | 'page' | undefined;
Expand Down Expand Up @@ -100,6 +104,7 @@ const wrapperSchema = (
return Joi.object({
type: Joi.string().valid('WRAPPER').required(),
label: Joi.string(),
optionCategories: Joi.array().items(optionCategorySchema).min(1),
options: Joi.array().items(linkedOptionSchema).required(),
descendants: Joi.array()
.items(Joi.custom(validateComponent(componentStyleMap, prefabType)))
Expand All @@ -124,6 +129,7 @@ const componentSchema = (
ref: Joi.object({
id: Joi.string().required(),
}),
optionCategories: Joi.array().items(optionCategorySchema).min(1),
options: Joi.array().items(optionSchema).required(),
type: Joi.string().valid('COMPONENT').default('COMPONENT'),
descendants: Joi.array()
Expand Down
6 changes: 6 additions & 0 deletions src/validations/prefab/componentOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,9 @@ export const optionSchema = Joi.object({
}),
ref: refSchema,
});

export const optionCategorySchema = Joi.object({
label: Joi.string().required(),
expanded: Joi.boolean(),
members: Joi.array().items(Joi.string()).min(1).required(),
});

0 comments on commit 0ee9ab4

Please sign in to comment.