Skip to content

Commit

Permalink
fix: render correct field when value is '0' (#2666)
Browse files Browse the repository at this point in the history
* fix issue in one of field when value is 0

* extract utils from expression field
  • Loading branch information
a-b-r-o-w-n authored Apr 16, 2020
1 parent 6aa5629 commit c186832
Show file tree
Hide file tree
Showing 8 changed files with 572 additions and 176 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,87 +4,16 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
import React, { useState, useMemo } from 'react';
import { FieldProps, JSONSchema7, JSONSchema7Definition } from '@bfc/extension';
import { FieldProps } from '@bfc/extension';
import { Dropdown, IDropdownOption, ResponsiveMode } from 'office-ui-fabric-react/lib/Dropdown';
import formatMessage from 'format-message';

import { FieldLabel } from '../FieldLabel';
import { resolveRef, resolveFieldWidget, getValueType } from '../../utils';
import { usePluginConfig } from '../../hooks';
import { FieldLabel } from '../../FieldLabel';
import { resolveFieldWidget } from '../../../utils';
import { usePluginConfig } from '../../../hooks';
import { oneOfField } from '../styles';

import { oneOfField } from './styles';

const getOptions = (schema: JSONSchema7, definitions?: { [key: string]: JSONSchema7Definition }): IDropdownOption[] => {
const { type, oneOf } = schema;

if (type && Array.isArray(type)) {
const options: IDropdownOption[] = type.map(t => ({
key: t,
text: t,
data: { schema: { ...schema, type: t } },
}));

options.sort(({ text: t1 }, { text: t2 }) => (t1 > t2 ? 1 : -1));

return options;
}

if (oneOf && Array.isArray(oneOf)) {
return oneOf
.map(s => {
if (typeof s === 'object') {
const resolved = resolveRef(s, definitions);

return {
key: resolved.title?.toLowerCase() || resolved.type,
text: resolved.title?.toLowerCase() || resolved.type,
data: { schema: resolved },
} as IDropdownOption;
}
})
.filter(Boolean) as IDropdownOption[];
}

return [];
};

const getSelectedOption = (value: any | undefined, options: IDropdownOption[]): IDropdownOption | undefined => {
if (options.length === 0) {
return;
}

const valueType = getValueType(value);

if (valueType === 'array') {
const item = value[0];
const firstArrayOption = options.find(o => o.data.schema.type === 'array');

// if there is nothing in the array, default to the first array type
if (!item) {
return firstArrayOption;
}

// else, find the option with an item schema that matches item type
return (
options.find(o => {
const {
data: { schema },
} = o;

const itemSchema = Array.isArray(schema.items) ? schema.items[0] : schema.items;
return itemSchema && typeof item === itemSchema.type;
}) || firstArrayOption
);
}

// if the value if undefined, default to the first option
if (!value) {
return options[0];
}

// lastly, attempt to find the option based on value type
return options.find(({ data }) => data.schema.type === valueType) || options[0];
};
import { getOptions, getSelectedOption } from './utils';

const OneOfField: React.FC<FieldProps> = props => {
const { schema, value, definitions } = props;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { getOptions, getSelectedOption } from '../utils';

function makeOption(schema, type) {
return {
key: type,
text: type,
data: { schema: { ...schema, type } },
};
}

describe('getOptions', () => {
describe('when there is an array of types', () => {
const schema = {
title: 'test schema',
type: ['string' as const, 'boolean' as const, 'number' as const],
};

it('returns all of the types, sorted', () => {
expect(getOptions(schema, {})).toEqual([
makeOption(schema, 'boolean'),
makeOption(schema, 'number'),
makeOption(schema, 'string'),
]);
});
});

describe('when there is a oneOf property', () => {
const schema = {
title: 'Test Schema',
oneOf: [
{
type: 'string' as const,
title: 'My Awesome String',
},
{
type: 'boolean' as const,
},
{
type: 'number' as const,
},
{
$ref: '#/definitions/Microsoft.AnotherType',
},
],
};

const definitions = {
'Microsoft.AnotherType': {
title: 'Another Type',
type: 'object' as const,
},
};

it('returns one of options', () => {
expect(getOptions(schema, definitions)).toEqual([
{
key: 'my awesome string',
text: 'my awesome string',
data: {
schema: {
title: 'My Awesome String',
type: 'string',
},
},
},
{
key: 'boolean',
text: 'boolean',
data: {
schema: {
type: 'boolean',
},
},
},
{
key: 'number',
text: 'number',
data: {
schema: {
type: 'number',
},
},
},
{
key: 'another type',
text: 'another type',
data: {
schema: {
title: 'Another Type',
type: 'object',
},
},
},
]);
});
});
});

describe('getSelectedOption', () => {
const options = [
{
key: 'string',
text: 'string',
data: {
schema: {
type: 'string',
},
},
},
{
key: 'integer',
text: 'integer',
data: {
schema: {
type: 'integer',
},
},
},
{
key: 'object',
text: 'object',
data: {
schema: {
type: 'object',
},
},
},
{
key: 'array1',
text: 'array1',
data: {
schema: {
type: 'array',
},
},
},
{
key: 'array2',
text: 'array2',
data: {
schema: {
type: 'array',
items: {
type: 'integer',
},
},
},
},
];

it('returns undefined if there are no options', () => {
expect(getSelectedOption(123, [])).toBe(undefined);
});

it('returns the first option if the value is null or undefined', () => {
expect(getSelectedOption(undefined, options)).toEqual(options[0]);
expect(getSelectedOption(null, options)).toEqual(options[0]);
});

it('returns the option that matches the value type', () => {
expect(getSelectedOption('foo', options)).toEqual(options[0]);
expect(getSelectedOption(123, options)).toEqual(options[1]);
expect(getSelectedOption({ foo: 'bar' }, options)).toEqual(options[2]);
});

it("returns the first option if it can't find a match", () => {
expect(getSelectedOption(true, options)).toEqual(options[0]);
});

describe('when the value is an array', () => {
it('returns the first array type if there are no array items', () => {
expect(getSelectedOption([], options)).toEqual(options[3]);
});

it('returns the option that matches the first item type', () => {
expect(getSelectedOption([123], options)).toEqual(options[4]);
});

it('returns the first option if no type match found', () => {
expect(getSelectedOption(['foo'], options)).toEqual(options[3]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

export { OneOfField } from './OneOfField';
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { JSONSchema7, JSONSchema7Definition } from '@bfc/extension';
import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';

import { resolveRef, getValueType } from '../../../utils';

export function getOptions(
schema: JSONSchema7,
definitions?: { [key: string]: JSONSchema7Definition }
): IDropdownOption[] {
const { type, oneOf } = schema;

if (type && Array.isArray(type)) {
const options: IDropdownOption[] = type.map(t => ({
key: t,
text: t,
data: { schema: { ...schema, type: t } },
}));

options.sort(({ text: t1 }, { text: t2 }) => (t1 > t2 ? 1 : -1));

return options;
}

if (oneOf && Array.isArray(oneOf)) {
return oneOf
.map(s => {
if (typeof s === 'object') {
const resolved = resolveRef(s, definitions);

return {
key: resolved.title?.toLowerCase() || resolved.type,
text: resolved.title?.toLowerCase() || resolved.type,
data: { schema: resolved },
} as IDropdownOption;
}
})
.filter(Boolean) as IDropdownOption[];
}

return [];
}

export function getSelectedOption(value: any | undefined, options: IDropdownOption[]): IDropdownOption | undefined {
if (options.length === 0) {
return;
}

// if the value if undefined, default to the first option
if (typeof value === 'undefined' || value === null) {
return options[0];
}

const valueType = getValueType(value);

if (valueType === 'array') {
const item = value[0];
const firstArrayOption = options.find(o => o.data.schema.type === 'array');

// if there is nothing in the array, default to the first array type
if (!item) {
return firstArrayOption;
}

// else, find the option with an item schema that matches item type
return (
options.find(o => {
const {
data: { schema },
} = o;

const itemSchema = Array.isArray(schema.items) ? schema.items[0] : schema.items;
return itemSchema && getValueType(item) === itemSchema.type;
}) || firstArrayOption
);
}

// lastly, attempt to find the option based on value type
return options.find(({ data }) => data.schema.type === valueType) || options[0];
}
1 change: 1 addition & 0 deletions Composer/packages/ui-plugins/expressions/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
'office-ui-fabric-react/lib/(.*)$': 'office-ui-fabric-react/lib-commonjs/$1',
'@uifabric/fluent-theme/lib/(.*)$': '@uifabric/fluent-theme/lib-commonjs/$1',
},
testPathIgnorePatterns: ['/node_modules/'],
globals: {
'ts-jest': {
tsConfig: path.resolve(__dirname, './tsconfig.json'),
Expand Down
Loading

0 comments on commit c186832

Please sign in to comment.