Skip to content

Commit

Permalink
feat: nested fields and integer values for filter relation widget (#7177
Browse files Browse the repository at this point in the history
)
  • Loading branch information
JimmyOei committed Apr 16, 2024
1 parent 28b1d63 commit 0e6f93b
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 12 deletions.
18 changes: 13 additions & 5 deletions packages/decap-cms-widget-relation/src/RelationControl.js
Expand Up @@ -351,11 +351,19 @@ export default class RelationControl extends React.Component {

const options = hits.reduce((acc, hit) => {
if (
filters.every(
filter =>
Object.prototype.hasOwnProperty.call(hit.data, filter.field) &&
filter.values.includes(hit.data[filter.field]),
)
filters.every(filter => {
// check if the value for the (nested) filter field is in the filter values
const fieldKeys = filter.field.split('.');
let value = hit.data;
for (let i = 0; i < fieldKeys.length; i++) {
if (Object.prototype.hasOwnProperty.call(value, fieldKeys[i])) {
value = value[fieldKeys[i]];
} else {
return false;
}
}
return filter.values.includes(value);
})
) {
const valuesPaths = stringTemplate.expandPath({ data: hit.data, path: valueField });
for (let i = 0; i < valuesPaths.length; i++) {
Expand Down
85 changes: 79 additions & 6 deletions packages/decap-cms-widget-relation/src/__tests__/relation.spec.js
Expand Up @@ -77,6 +77,20 @@ const filterStringFieldConfig = {
],
};

const filterIntegerFieldConfig = {
name: 'post',
collection: 'posts',
display_fields: ['title', 'slug'],
search_fields: ['title', 'body'],
value_field: 'title',
filters: [
{
field: 'num',
values: [1, 5, 9],
},
],
};

const multipleFiltersFieldConfig = {
name: 'post',
collection: 'posts',
Expand Down Expand Up @@ -109,13 +123,28 @@ const emptyFilterFieldConfig = {
],
};

const nestedFilterFieldConfig = {
name: 'post',
collection: 'posts',
display_fields: ['title', 'slug'],
search_fields: ['title', 'body'],
value_field: 'title',
filters: [
{
field: 'deeply.nested.post.field',
values: ['Deeply nested field'],
},
],
};

function generateHits(length) {
const hits = Array.from({ length }, (val, idx) => {
const title = `Post # ${idx + 1}`;
const slug = `post-number-${idx + 1}`;
const draft = idx % 2 === 0;
const num = idx + 1;
const path = `posts/${slug}.md`;
return { collection: 'posts', data: { title, slug, draft }, slug, path };
return { collection: 'posts', data: { title, slug, draft, num }, slug, path };
});

return [
Expand Down Expand Up @@ -338,7 +367,9 @@ describe('Relation widget', () => {
const value = 'Post # 1';
const label = 'Post # 1 post-number-1';
const metadata = {
post: { posts: { 'Post # 1': { title: 'Post # 1', draft: true, slug: 'post-number-1' } } },
post: {
posts: { 'Post # 1': { title: 'Post # 1', draft: true, num: 1, slug: 'post-number-1' } },
},
};

fireEvent.keyDown(input, { key: 'ArrowDown' });
Expand All @@ -356,7 +387,9 @@ describe('Relation widget', () => {
const { getByText, onChangeSpy, setQueryHitsSpy } = setup({ field, value });
const label = 'Post # 1 post-number-1';
const metadata = {
post: { posts: { 'Post # 1': { title: 'Post # 1', draft: true, slug: 'post-number-1' } } },
post: {
posts: { 'Post # 1': { title: 'Post # 1', draft: true, num: 1, slug: 'post-number-1' } },
},
};

setQueryHitsSpy(generateHits(1));
Expand Down Expand Up @@ -405,7 +438,9 @@ describe('Relation widget', () => {
const label = 'post-number-1 post-number-1 md';
const metadata = {
post: {
posts: { 'post-number-1': { title: 'Post # 1', draft: true, slug: 'post-number-1' } },
posts: {
'post-number-1': { title: 'Post # 1', draft: true, num: 1, slug: 'post-number-1' },
},
},
};

Expand Down Expand Up @@ -462,10 +497,14 @@ describe('Relation widget', () => {
const field = fromJS({ ...fieldConfig, multiple: true });
const { getByText, input, onChangeSpy } = setup({ field });
const metadata1 = {
post: { posts: { 'Post # 1': { title: 'Post # 1', draft: true, slug: 'post-number-1' } } },
post: {
posts: { 'Post # 1': { title: 'Post # 1', draft: true, num: 1, slug: 'post-number-1' } },
},
};
const metadata2 = {
post: { posts: { 'Post # 2': { title: 'Post # 2', draft: false, slug: 'post-number-2' } } },
post: {
posts: { 'Post # 2': { title: 'Post # 2', draft: false, num: 2, slug: 'post-number-2' } },
},
};

fireEvent.keyDown(input, { key: 'ArrowDown' });
Expand Down Expand Up @@ -593,6 +632,29 @@ describe('Relation widget', () => {
});
});

it('should list 3 option hits on initial load using a filter on integer value', async () => {
const field = fromJS(filterIntegerFieldConfig);
const { getAllByText, input } = setup({ field });
const expectedOptions = [
'Post # 1 post-number-1',
'Post # 5 post-number-5',
'Post # 9 post-number-9',
];
fireEvent.keyDown(input, { key: 'ArrowDown' });

await waitFor(() => {
const displayedOptions = getAllByText(/^Post # (\d{1,2}) post-number-\1$/);
expect(displayedOptions).toHaveLength(expectedOptions.length);
for (let i = 0; i < expectedOptions.length; i++) {
const expectedOption = expectedOptions[i];
const optionFound = displayedOptions.some(
option => option.textContent === expectedOption,
);
expect(optionFound).toBe(true);
}
});
});

it('should list 4 option hits on initial load using multiple filters', async () => {
const field = fromJS(multipleFiltersFieldConfig);
const { getAllByText, input } = setup({ field });
Expand Down Expand Up @@ -626,5 +688,16 @@ describe('Relation widget', () => {
expect(() => getAllByText(/^Post # (\d{1,2}) post-number-\1$/)).toThrow(Error);
});
});

it('should list 1 option hit on initial load on nested filter field', async () => {
const field = fromJS(nestedFilterFieldConfig);
const { getAllByText, input } = setup({ field });
fireEvent.keyDown(input, { key: 'ArrowDown' });

await waitFor(() => {
expect(() => getAllByText(/^Post # (\d{1,2}) post-number-\1$/)).toThrow(Error);
expect(getAllByText('Deeply nested post post-deeply-nested')).toHaveLength(1);
});
});
});
});
2 changes: 1 addition & 1 deletion packages/decap-cms-widget-relation/src/schema.js
Expand Up @@ -15,7 +15,7 @@ export default {
type: 'object',
properties: {
field: { type: 'string' },
values: { type: 'array', minItems: 1, items: { type: ['string', 'boolean'] } },
values: { type: 'array', minItems: 1, items: { type: ['string', 'boolean', 'integer'] } },
},
required: ['field', 'values'],
},
Expand Down

0 comments on commit 0e6f93b

Please sign in to comment.