Gatsby JS, Can't Create Node In Callback - javascript

so basically I want to use createNode() within the callback of the file.walk() command, but when I do that, no nodes are created and you can't query the component type in graphiQL.
Uncommenting the other and commenting out the file section allows me to query the component (since it isn't in the callback.)
// Gatsby Node File
const file = require('file');
exports.sourceNodes = async ({ actions, createNodeId, createContentDigest }) => {
const { createNode } = actions;
// const components = [{
// key: 123,
// foo: `ywgavevw`,
// bar: `Baz`
// },
// {
// key: 456,
// foo: 'asdfsadf',
// bar: 'asdfasdf'
// }];
file.walk('./components/', (_, some, thing, files) => {
console.log(files);
let component = {
key: 456,
foo: 'asdfsadf',
bar: 'asdfasdf'
}
const nodeContent = JSON.stringify(component);
const nodeMeta = {
id: createNodeId(`kstat-component-${component.key}`),
parent: null,
children: [],
internal: {
type: `KstatComponent`,
mediaType: `text/html`,
content: nodeContent,
contentDigest: createContentDigest(component)
}
}
const node = Object.assign({}, component, nodeMeta);
createNode(node);
});
// components.forEach((component) => {
// const nodeContent = JSON.stringify(component);
// const nodeMeta = {
// id: createNodeId(`kstat-component-${component.key}`),
// parent: null,
// children: [],
// internal: {
// type: `KstatComponent`,
// mediaType: `text/html`,
// content: nodeContent,
// contentDigest: createContentDigest(component)
// }
// }
// const node = Object.assign({}, component, nodeMeta);
// createNode(node);
// });
}
On a higher level, I am trying to achieve creating a number of nodes based on files in the filesystem. Is there a less-crazy way of doing this? Should I consider something else? Thanks!

Have you tried awaiting for the callback?
await file.walk('./components/', (_, some, thing, files) => {
console.log(files);
let component = {
key: 456,
foo: 'asdfsadf',
bar: 'asdfasdf'
}
const nodeContent = JSON.stringify(component);
const nodeMeta = {
id: createNodeId(`kstat-component-${component.key}`),
parent: null,
children: [],
internal: {
type: `KstatComponent`,
mediaType: `text/html`,
content: nodeContent,
contentDigest: createContentDigest(component)
}
}
const node = Object.assign({}, component, nodeMeta);
createNode(node);
});

Related

How to create Gatsby Image data for nested nodes?

I am trying to use createRemoteFileNode to create optimised images for an array of nodes that exist on a Product.
I have a Product that has items and on each item, it has a featuredImg. I can create a featuredImg for a Product but as soon as I try to create it for the child nodes (items) then it is not queryable.
I am creating my nodes as such:
const products = [
{
id: "product_1",
imageUrl: "https://images.unsplash.com/photo-1665081661649-8656335a6cbb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1738&q=80",
items: [
{
id: 'item_1',
imageUrl: "https://images.unsplash.com/photo-1666120565124-7e763880444a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80"
}
]
}
]
const sourceNodes = async ({ actions, createNodeId, createContentDigest }, options) => {
products.forEach((testNode) => {
const node = {
...testNode,
id: createNodeId(`Product-${testNode.id}`),
}
actions.createNode({
...node,
internal: {
type: 'Product',
contentDigest: createContentDigest(node),
},
});
testNode.items.forEach(item => {
const itemNode = {
...item,
id: createNodeId(`Item-${item.id}`),
}
actions.createNode({
...itemNode,
parent: node.id,
internal: {
type: 'Item',
contentDigest: createContentDigest(itemNode),
},
});
})
})
};
module.exports = sourceNodes;
Then on the node creation, I am running the onCreateNode function which should create the remote file node for each item featuredImg.
const { createRemoteFileNode } = require(`gatsby-source-filesystem`);
const onCreateNode = async ({ node, cache, store, getCache, actions: { createNode, createNodeField }, createNodeId }) => {
if( node.internal.type === 'Item') {
const fileNode = await createRemoteFileNode({
url: node.imageUrl,
parentNodeId: node.parent,
createNode,
createNodeId,
getCache,
})
if (fileNode) {
createNodeField({ node, name: "localFile", value: fileNode.id })
}
}
if( node.internal.type === 'Product') {
const fileNode = await createRemoteFileNode({
url: node.imageUrl,
parentNodeId: node.id,
createNode,
createNodeId,
getCache,
})
if (fileNode) {
createNodeField({ node, name: "localFile", value: fileNode.id })
}
}
};
module.exports = onCreateNode
I have defined my types here:
module.exports = ({ actions }) => {
const { createTypes } = actions;
const typeDefs = `
type Product implements Node {
id: String!
imageUrl: String!
featuredImg: File #link(from: "fields.localFile")
items: [Item]
}
type Item implements Node {
id: String!
imageUrl: String!
featuredImg: File #link(from: "fields.localFile")
}
`;
createTypes(typeDefs);
};
For some reason, when I query Products.items[i].featuredImg it always returns null. However, I can see the node is generated because I can query item.featuredImg and it returns the gatsbyImageData.
I have created a simple example here and included a read me on how to replicate it: https://github.com/stretch0/gatsby-sandbox
I have also noticed that this post is a similar issue of not being able to create remote file nodes within a loop but because they have a different file structure, I can't figure out how their solution to use createSchemaCustomization or createResolvers would apply to my setup.

Quasar QSelect is not opening when performing AJAX call

I have been trying to create a simple auto complete using Quasar's select but I'm not sure if this is a bug or if I'm doing something wrong.
Problem
Whenever I click the QSelect component, it doesn't show the dropdown where I can pick the options from.
video of the problem
As soon as I click on the QSelect component, I make a request to fetch a list of 50 tags, then I populate the tags to my QSelect but the dropdown doesn't show.
Code
import type { PropType } from "vue";
import { defineComponent, h, ref } from "vue";
import type { TagCodec } from "#/services/api/resources/tags/codec";
import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from "quasar";
export const TagAutoComplete = defineComponent({
name: "TagAutoComplete",
props: {
modelValue: { type: Array as PropType<TagCodec[]> },
},
emits: ["update:modelValue"],
setup(props, context) {
const loading = ref(false);
const tags = ref<TagCodec[]>([]);
// eslint-disable-next-line #typescript-eslint/ban-types
const onFilterTest = (val: string, doneFn: (update: Function) => void) => {
const parameters = val === "" ? {} : { title: val };
doneFn(async () => {
loading.value = true;
const response = await list(parameters);
if (val) {
const needle = val.toLowerCase();
tags.value = response.data.data.filter(
(tag) => tag.title.toLowerCase().indexOf(needle) > -1
);
} else {
tags.value = response.data.data;
}
loading.value = false;
});
};
const onInput = (values: TagCodec[]) => {
context.emit("update:modelValue", values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: "title",
optionValue: "id",
outlined: true,
useInput: true,
useChips: true,
placeholder: "Start typing to search",
onFilter: onFilterTest,
"onUpdate:modelValue": onInput,
loading: loading.value,
});
};
},
});
What I have tried
I have tried to use the several props that is available for the component but nothing seemed to work.
My understanding is that whenever we want to create an AJAX request using QSelect we should use the onFilter event emitted by QSelect and handle the case from there.
Questions
Is this the way to create a Quasar AJAX Autocomplete? (I have tried to search online but all the answers are in Quasar's forums that are currently returning BAD GATEWAY)
What am I doing wrong that it is not displaying the dropdown as soon as I click on the QSelect?
It seems updateFn may not allow being async. Shift the async action a level up to solve the issue.
const onFilterTest = async (val, update /* abort */) => {
const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await list(parameters);
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
I tested it by the following code and mocked values.
// import type { PropType } from 'vue';
import { defineComponent, h, ref } from 'vue';
// import type { TagCodec } from "#/services/api/resources/tags/codec";
// import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from 'quasar';
export const TagAutoComplete = defineComponent({
name: 'TagAutoComplete',
props: {
modelValue: { type: [] },
},
emits: ['update:modelValue'],
setup(props, context) {
const loading = ref(false);
const tags = ref([]);
const onFilterTest = async (val, update /* abort */) => {
// const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await new Promise((resolve) => {
setTimeout(() => {
resolve({
data: {
data: [
{
id: 1,
title: 'Vue',
},
{
id: 2,
title: 'Vuex',
},
{
id: 3,
title: 'Nuxt',
},
{
id: 4,
title: 'SSR',
},
],
},
});
}, 3000);
});
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
const onInput = (values) => {
context.emit('update:modelValue', values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: 'title',
optionValue: 'id',
outlined: true,
useInput: true,
useChips: true,
placeholder: 'Start typing to search',
onFilter: onFilterTest,
'onUpdate:modelValue': onInput,
loading: loading.value,
});
};
},
});

Deleting JSON object based on attribute

I am working with React, I have got a variable named newtreeData, which looks like:
var newTreeData = {
name: submitted_title,
resource_link: submitted_resource_link,
details: submitted_details,
uuid: submitted_uuid,
website_image: submitted_website_img,
children: [
{
name: "Edit and save",
resource_link: "uh",
uuid: uuid.v4(),
details: "hi",
website_image:
"https://cdn3.iconfinder.com/data/icons/harmonicons-06/64/plus-circle-512.png",
children: [{...}, {}, ...]
},
{
name: "Edit and save",
resource_link: "uh",
uuid: uuid.v4(),
details: "hi",
website_image:
"https://cdn3.iconfinder.com/data/icons/harmonicons-06/64/plus-circle-512.png"
}
]
};
The line children: [{...}, {}] is just representing that newTreeData's children can have children which can have children...
Anyways, I wrote a method name findUUIDthenDelete which should do in pseudocode: if(object.uuid == toFindUUID) then delete object, and here's the full code for findUUIDthenDelete:
findUUIDthenDelete = (orig_data, to_delete_uuid) => {
var targetIsFound = false;
if (orig_data.uuid == to_delete_uuid) {
targetIsFound = true;
}
if (targetIsFound == false) {
if (orig_data.children === undefined) {
} else {
//if target not found, run recursion
orig_data.children.map(eachChildren =>
this.findUUIDthenDelete(eachChildren, to_delete_uuid)
);
}
} else {
console.log(orig_data, "this is the child ");
console.log(orig_data.parent, "is found, deleting its parent");
delete orig_data
}
};
As you can see this method is two parts: first I locate the object which has the uuid that we are trying to seek (potentially with some recursions), then delete the object. However, right now I am getting the "delete in local variable strict mode blah blah" error because of doing delete orig_data. Any insights to any workarounds to that error or some totally new way of tackling this? Also sincere apologies if there is an obvious solution I am out of mental energy and unable to think of anything algorithmic at the moment.
This should do it:
function findUUIDthenDelete(tree, uuid) {
if (!tree.children) return;
tree.children = tree.children.filter(c => c.uuid !== uuid);
tree.children.forEach(c => findUUIDthenDelete(c, uuid));
}
Should be pretty self-explanatory.
First, if the current node has no children, exit right away.
Next, potentially remove a child from the children array if the uuid matches using filter().
Finally, recursion.
Ok I'll admit this turned out to be more complicated than I thought, but the solution below will work if you can use Immutable. Essentially it walks your objects and collects the path to find the object that has the uuid and then once it has done that, it removes it.
const testMap = Immutable.fromJS({
uuid: 1,
children: [{
uuid: 2,
children: [{
uuid: 3,
children:[{
uuid: 8
}]
},
{
uuid: 4
},
{
uuid: 5
},
]
},
{
uuid: 7
}]
});
function findPath(checkMap, uuid, pathMap, currentIndex) {
if (checkMap.has('uuid') && checkMap.get('uuid') === uuid) {
const updatePathMap = pathMap.get('path').push(currentIndex);
return new Immutable.Map({
found: true,
path: pathMap.get('path').push(currentIndex)
});
} else {
if (checkMap.has('children') && checkMap.get('children').size > 0) {
for (let i = 0; i < checkMap.get('children').size; i++) {
const child = checkMap.get('children').get(i);
const checkChildPath = findPath(child, uuid, pathMap, i);
if (checkChildPath.get('found') === true) {
let updatePath = checkChildPath.get('path').push('children');
updatePath = updatePath.push(currentIndex);
return new Immutable.Map({
found: true,
path: updatePath
});
}
}
}
return pathMap;
}
}
const testPath = findPath(testMap, 7, new Immutable.Map({
found: false,
path: new Immutable.List()
}), 0);
console.info(testPath);
const testPath2 = findPath(testMap, 8, new Immutable.Map({
found: false,
path: new Immutable.List()
}), 0);
console.info(testPath2);
if (testPath2.get('found') === true) {
const path = testPath2.get('path');
if (path.size === 1 && path.get(0) === 0) {
// Your highlest level map has the uuid
} else {
const truePath = path.shift();
const cleanedUpMap = testMap.removeIn(truePath);
console.info(cleanedUpMap);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.js"></script>

Jasmine test with selecting items from list

I have an angular application and I need to do some unit testing on some methods with Jasmine. IN this case I do a unit test on a select list. So that the select list will not be empty.
The method looks like this:
createStatusOptions(listValueoptions: OptionModel[], resources: any): OptionModel[] {
const processStatusOptions = listValueoptions.map(listValueOption => {
listValueOption.value = `${caseStatusEnum.inProgress}_${listValueOption.value}`;
listValueOption.selected = true;
return listValueOption;
});
const caseStatusEnumKeys = Object.keys(caseStatusEnum).filter(key => !isNaN(Number(key)));
const enumOptions = this.optionService.createOptions(
new ConfigOptionModel({ source: caseStatusEnumKeys, resources, resourcesModel: enumResourcesModel, isCustomEnum: true, }));
return [
this.getEnumOption(enumOptions, caseStatusEnum.submitted, true),
...processStatusOptions,
this.getEnumOption(enumOptions, caseStatusEnum.closed),
];
}
private getEnumOption(options: OptionModel[], enumType, isSelected = false): OptionModel {
const option = options.filter(enumOption => enumOption.value === `${enumType}`)[0];
option.selected = isSelected;
return option;
}
And I have the unit test like this:
it('should create status options when there ar list value options are provided', () => {
optionService.options = [
{
value: caseStatusEnum.submitted.toString(),
},
{
value: caseStatusEnum.inProgress.toString(),
},
{
value: caseStatusEnum.closed.toString(),
},
] as OptionModel[];
// tslint:disable-next-line:max-line-length
const result = service.createStatusOptions(optionService.options, [[103], [104], [105] ]);
console.log(result);
expect(result.length).toBe(2);
expect(result).toEqual([{ value: '103', selected: true }, { value: '105', selected: false }]);
});
But I get an error like this:
Services: CaseService > should create status options when there ar list value options are provided
TypeError: Cannot set property 'selected' of undefined
at <Jasmine>
at CaseService.getEnumOption (http://localhost:9878/src/app/case/src/services/case.service.ts?:130:9)
at CaseService.getEnumOption [as createStatusOptions] (http://localhost:9878/src/app/case/src/services/case.service.ts?:109:22)
at UserContext.<anonymous> (http://localhost:9878/src/app/case/src/services/case.service.spec.ts?:149:32)
at ZoneDelegate.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (http://localhost:9878/E:/Projects/Source/Repos/VLR/Web/vlrworkspace/node_modules/zone.js/dist/zone.js?:388:1)
at ProxyZoneSpec.push.../../node_modules/zone.js/dist/proxy.js.ProxyZoneSpec.onInvoke (http://localhost:9878/E:/Projects/Source/Repos/VLR/Web/vlrworkspace/node_modules/zone.js/dist/proxy.js?:128:1)
at ZoneDelegate.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (http://localhost:9878/E:/Projects/Source/Repos/VLR/Web/vlrworkspace/node_modules/zone.js/dist/zone.js?:387:1)
at Zone.../../node_modules/zone.js/dist/zone.js.Zone.run (http://localhost:9878/E:/Projects/Source/Repos/VLR/Web/vlrworkspace/node_modules/zone.js/dist/zone.js?:138:1)
at runInTestZone (http://localhost:9878/E:/Projects/Source/Repos/VLR/Web/vlrworkspace/node_modules/zone.js/dist/jasmine-patch.js?:145:1)
at UserContext.<anonymous> (http://localhost:9878/E:/Projects/Source/Repos/VLR/Web/vlrworkspace/node_modules/zone.js/dist/jasmine-patch.js?:160:1)
at <Jasmine>
So my question is: How to solve this?
Thank you
if I do this:
console.log(optionService.options);
I get this output:
Array(3)
0: {value: "103", selected: true}
1: {value: "104"}
2: {value: "105", selected: false}
length: 3
__proto__: Array(0)
this is the file:
import { fakeAsync, tick } from '#angular/core/testing';
import { FormServiceMock, MultiFileUploadServiceMock } from 'afw/forms/testing';
import { AfwHttp } from 'afw/generic-services';
import { AfwHttpMock, OptionServiceMock } from 'afw/generic-services/testing';
import { OptionModel, SearchResultModel } from 'afw/models';
import { FeedbackStoreServiceMock } from 'afw/store-services/testing';
import { RouterMock } from 'afw/testing';
import { PagingDataModel, TableSortDataModel } from 'afw/ui-components';
import { caseOwnerEnum, caseStatusEnum, caseTypeEnum, MultiFileUploadResourcesModel } from 'lr/models';
import { Observable, observable } from 'rxjs';
import { CaseTypeInfoModel } from 'support-shared/base/models';
import { CaseTypeInfoStoreServiceMock } from 'support-shared/base/services/case-type-info-store.service.mock';
import { CaseFormComponent } from '../case-base/src/case-form/case-form.component';
import { CaseBaseModel, CaseReferenceModel } from '../models';
import { CaseService } from './case.service';
let service: CaseService;
let afwHttpMock: AfwHttpMock;
// tslint:disable-next-line:prefer-const
let formServiceMock: FormServiceMock;
let multiFileUploadService: MultiFileUploadServiceMock;
let router: RouterMock;
let feedbackStoreService: FeedbackStoreServiceMock;
let optionService: OptionServiceMock;
let caseTypeInfoStoreService: CaseTypeInfoStoreServiceMock;
// tslint:disable-next-line:prefer-const
let component: CaseFormComponent;
fdescribe('Services: CaseService', () => {
beforeEach(() => {
afwHttpMock = new AfwHttpMock();
multiFileUploadService = new MultiFileUploadServiceMock();
router = new RouterMock();
feedbackStoreService = new FeedbackStoreServiceMock();
optionService = new OptionServiceMock();
caseTypeInfoStoreService = new CaseTypeInfoStoreServiceMock();
service = new CaseService(afwHttpMock as any, multiFileUploadService as any, router as any,
feedbackStoreService as any, optionService as any, caseTypeInfoStoreService as any);
});
it('should create an instance', () => {
expect(service).toBeTruthy();
});
it('should get case reference details', () => {
afwHttpMock.setupOnlyResponse({ type: caseTypeEnum.revisionRequest, details: { bsn: 'bsnLabel' } }, 200);
const d = service.getCaseReferenceDetails('spinnerMessage', { reference: '112314121', type: caseTypeEnum.revisionRequest });
d.subscribe(r => {
expect(r.details.length === 1);
expect(r.details[0].key).toBe('bsn');
expect(r.details[0].value).toBe('bsnLabel');
expect((r.details[0] as any).resourceKey).toBe('bsn');
});
afwHttpMock.returnSuccessResponse();
});
// tslint:disable-next-line:no-identical-functions
it('should get case reference details with full response', () => {
afwHttpMock.setupOnlyResponse({ body: { type: caseTypeEnum.revisionRequest, details: [{ key: 'hoi' }] } }, 200);
const d = service.getCaseReferenceDetailsFullResponse('spinnerMessage', { reference: '100001075', type: caseTypeEnum.revisionRequest });
// tslint:disable-next-line:no-commented-code
// tslint:disable-next-line:no-identical-functions
/* let result;
d.subscribe(r => {
result = r;
}); */
d.subscribe(r => {
expect(r.ok === true);
expect(r.body.details[0].key).toBe('hoi');
});
afwHttpMock.returnSuccessResponse();
// expect(result.ok === true);
// expect(result.)
});
// tslint:disable-next-line:no-commented-code
it('shoud get case type info configuration that is used on various views when snapshot exists', () => {
let result99: Observable<CaseTypeInfoModel[]>;
result99 = service.getCaseTypeInfo('spinner') as Observable<CaseTypeInfoModel[]>;
const response = [{ mock: 'mock' } as any];
service['caseTypeInfoSnapshot'] = response;
service.getCaseTypeInfo('spinner').subscribe(i => {
expect(i).toEqual(response);
});
});
// tslint:disable-next-line:no-identical-functions
it('shoud get case type info configuration that is used on various views when snapshot doesnt exists', () => {
let result99: Observable<CaseTypeInfoModel[]>;
const spy = spyOn(caseTypeInfoStoreService, 'addCaseTypeInfoToStore');
result99 = service.getCaseTypeInfo('spinner') as Observable<CaseTypeInfoModel[]>;
const response = [{ mock: 'mock' } as any];
service['caseTypeInfoSnapshot'] = response;
// caseTypeInfoStoreService..subscribe((result) => { expect(result).toBe(false); });
result99.subscribe((result) => {
expect(response).toEqual(response);
});
afwHttpMock.setupOnlyResponse(result99, 200);
afwHttpMock.returnSuccessResponse();
});
it('should create status options when no list value options are provided', () => {
optionService.options = [
{
value: caseStatusEnum.submitted.toString(),
},
{
value: caseStatusEnum.inProgress.toString(),
},
{
value: caseStatusEnum.closed.toString(),
},
] as OptionModel[];
// tslint:disable-next-line:no-commented-code
// const spy = spyOn(service, 'createStatusOptions');
const result = service.createStatusOptions([], {});
expect(result.length).toBe(2);
expect(result).toEqual([{ value: '103', selected: true }, { value: '105', selected: false }]);
// tslint:disable-next-line:no-commented-code
// const response = [{ mock: 'mock' } as any];
// expect(spy).toBe(result);
});
it('should create status options when there ar list value options are provided', () => {
optionService.options = [
{
value: caseStatusEnum.submitted.toString(),
},
{
value: caseStatusEnum.inProgress.toString(),
},
{
value: caseStatusEnum.closed.toString(),
},
] as OptionModel[];
// tslint:disable-next-line:max-line-length
const result = service.createStatusOptions(optionService.options, 103);
console.log(optionService.options);
expect(result.length).toBe(2);
expect(result).toEqual([{ value: '103', selected: true }, { value: '105', selected: false }]);
});
it('should get case reference without details', () => {
afwHttpMock.setupOnlyResponse({}, 200);
const spy = spyOn(afwHttpMock, 'post').and.callThrough();
const model = new CaseReferenceModel({ reference: '112314121', type: caseTypeEnum.revisionRequest });
const d = service.getCaseReferenceDetails('spinnerMessage', model);
d.subscribe(r => {
expect(r).toBeDefined();
});
expect(spy).toHaveBeenCalledWith('api/support/cases/get-reference-details', model, 'spinnerMessage');
afwHttpMock.returnSuccessResponse();
});
it('should add case reference without details', () => {
afwHttpMock.setupOnlyResponse({}, 200);
const spy = spyOn(afwHttpMock, 'post').and.callThrough();
const model = new CaseReferenceModel({ reference: '112314121', type: caseTypeEnum.revisionRequest });
const d = service.addCase('spinnerMessage', model as any);
d.subscribe(r => {
expect(r).toBeDefined();
});
expect(spy).toHaveBeenCalledWith('api/support/cases', model, 'spinnerMessage');
afwHttpMock.returnSuccessResponse();
});
it('should search for cases', () => {
const formModel: any = { makeQueryString: () => 'name=test' };
const pagingModel = new PagingDataModel({ currentPage: 10, itemsPerPage: 20 });
const sortModel = new TableSortDataModel({ columnName: 'kol', isDescending: false });
const spy = spyOn(afwHttpMock, 'get').and.callThrough();
const mockData = [
new CaseBaseModel({
id: 100000001,
type: caseTypeEnum.revisionRequest,
status: caseStatusEnum.inProgress,
substatus: 5266,
verdict: null,
owner: caseOwnerEnum.caseManager,
dateSubmitted: '02-02-2009',
dateClosed: '',
reference: 'aaa',
}),
];
const setupResponse = new SearchResultModel<CaseBaseModel>();
setupResponse.result = mockData;
setupResponse.totalResultCount = 27;
afwHttpMock.setupOnlyResponse(setupResponse, 200);
let response: SearchResultModel<CaseBaseModel>;
service.search(formModel, sortModel, pagingModel, 'spinnerText').subscribe(result => {
response = result;
});
afwHttpMock.returnOnlyResponse();
expect(spy).toHaveBeenCalledWith('api/support/cases?name=test&columnName=kol&isDescending=false&currentPage=10&itemsPerPage=20',
'spinnerText');
expect(response).toEqual(setupResponse);
expect(response.result[0].getResourceForStatus).toBeDefined();
});
it('should save documents', fakeAsync(() => {
const spy = spyOn(multiFileUploadService, 'syncFilesWithBackend').and.callThrough();
const spyRouter = spyOn(router, 'navigate').and.callThrough();
const spyFeedback = spyOn(feedbackStoreService, 'addSuccessMessageOnMainPortal');
service.saveDocuments(1, [{} as any], MultiFileUploadResourcesModel.keys, '../', { key: 'da', value: 'fa' });
expect(spy).toHaveBeenCalledWith('api/support/cases/1/documents', [{}],
MultiFileUploadResourcesModel.keys.bijlageToevoegenSpinnerTekst,
MultiFileUploadResourcesModel.keys.bijlageVerwijderenSpinnerTekst
);
tick();
expect(spyRouter).toHaveBeenCalledWith(['../']);
expect(spyFeedback).toHaveBeenCalled();
}));
it('should not save documents if there are no documents in array', fakeAsync(() => {
const spy = spyOn(multiFileUploadService, 'syncFilesWithBackend').and.callThrough();
const spyRouter = spyOn(router, 'navigate').and.callThrough();
const spyFeedback = spyOn(feedbackStoreService, 'addSuccessMessageOnMainPortal');
service.saveDocuments(1, [], MultiFileUploadResourcesModel.keys, '../', { key: 'da', value: 'fa' });
expect(spy).not.toHaveBeenCalled();
tick();
expect(spyRouter).toHaveBeenCalledWith(['../']);
expect(spyFeedback).toHaveBeenCalled();
}));
it('should save documents and report errors', fakeAsync(() => {
multiFileUploadService.setResponse([{}, { error: {} }]);
spyOn(multiFileUploadService, 'makeWarningMessageForUnsyncedFiles').and.returnValue('mock');
const spyRouter = spyOn(router, 'navigate').and.callThrough();
const spyFeedback = spyOn(feedbackStoreService, 'addWarningMessageOnMainPortal');
const spy = spyOn(multiFileUploadService, 'syncFilesWithBackend').and.callThrough();
service.saveDocuments(1, [{} as any], MultiFileUploadResourcesModel.keys, '../', { key: 'da', value: 'fa' });
expect(spy).toHaveBeenCalledWith('api/support/cases/1/documents', [{}],
MultiFileUploadResourcesModel.keys.bijlageToevoegenSpinnerTekst,
MultiFileUploadResourcesModel.keys.bijlageVerwijderenSpinnerTekst
);
tick();
expect(spyRouter).toHaveBeenCalledWith(['../']);
expect(spyFeedback).toHaveBeenCalled();
}));
it('should get case by id', () => {
const id = 66208014;
const setupResponse = new CaseBaseModel({
id,
dateSubmitted: '',
owner: caseOwnerEnum.caseManager,
reference: 'ksjhkjshdf',
status: caseStatusEnum.submitted,
type: caseTypeEnum.revisionRequest,
});
afwHttpMock.setupOnlyResponse(setupResponse, 200);
service.getCase(id, 'spinner').subscribe(r => {
expect(r).toEqual(setupResponse);
});
afwHttpMock.returnSuccessResponse();
});
it('edit the case with model', () => {
const spy = spyOn(service, 'editCase').and.callThrough();
const caseUpdate = new CaseBaseModel({
id: 100001075,
dateSubmitted: '',
owner: caseOwnerEnum.caseManager,
reference: 'ksjhkjshdf',
status: caseStatusEnum.submitted,
type: caseTypeEnum.revisionRequest,
});
service.editCase('spinner', caseUpdate);
expect(spy).toHaveBeenCalledWith('spinner', caseUpdate);
expect(caseUpdate.id).toEqual(100001075);
});
});
Based on what you showed so far, my guess is that the options parameter passed to getEnumOption() is undefined, which is causing the error you see. A quick console.log(options) within getEnumOption() would verify this.
If your code is working fine otherwise, but only failing in the test then I would make a second guess that you haven't properly mocked/spiedOn this.optionService.createOptions() since it sets up the options parameter that is potentially undefined. That would have been done earlier in the .spec file - if you post the whole file then that would help others who read your question to determine if this is the case.
Update with Stackblitz
I put all your code into a Stackblitz to test it. There was a lot of code I didn't have access to that I just guessed at the functionality of. However, I did discover a few things.
First, when you are testing you appear to be using the same variable both for the mock of the return expected by this.optionService.createOptions() as well as in the call to service.createStatusOptions() - which is likely not what you want to do.
Here is the code snippet I am talking about:
optionService.options = [
{
value: caseStatusEnum.submitted.toString(),
},
{
value: caseStatusEnum.inProgress.toString(),
},
{
value: caseStatusEnum.closed.toString(),
},
] as OptionModel[];
// tslint:disable-next-line:max-line-length
const result = service.createStatusOptions(optionService.options, [[103], [104], [105] ]);
When I called it this way in the Stackblitz I ran into a mutability issue - you are changing the data within the members of the objects inside the array, which will change it whereever that variable is accessed. To overcome this in the Stackblitz I made two copies of the data, one to use in the mock returnValue and another completely separate array of objects for the call to service.createStatusOptions(). Also, I am not familiar with the way you are mocking your service call, so I replaced it with a simple Jasmine spy in the Stackblitz.
Feel free to have a look at what I produced. Perhaps it will be helpful.

Relay Modern Mutations, RANGE_ADD / Append

I have a collection and a mutation to add a new item to it. I haven't been able to get Relay Modern to update the UI after a successful mutation.
I've got a PaginationContainer setup with the following query: prop
{
query: graphql`
fragment ContactsList_query on WPQuery {
id
contacts: posts(
first: $count,
after: $cursor
post_type: $postType,
order: $order,
category_name: $categoryName
) #connection(key: "ContactsList_contacts" ) {
edges {
node {
id
...ContactListItem_contact
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`
},
That fetches correctly. I've then got a mutation to add a contact to this list.
Neither the config RANGE_ADD or the updater: callback techniques work at all.
I'm triggering this mutation like so
onSave = (fields) => {
insertPost(
fields.toJS(),
this.props.query.id,
this.props.relay.environment
);
}
No errors, just nothing updates.
const mutation = graphql`
mutation InsertPostMutation(
$data: InsertPostInput!
) {
insert_post(input: $data) {
wp_query {
id
}
postEdge {
node {
id
title
}
}
}
}
`;
export default function insertPost(data, id, environment) {
const variables = {
data,
};
commitMutation(
environment,
{
mutation,
variables,
onCompleted: (response, errors) => {
console.log('Response received from server.')
},
onError: err => console.error(err),
configs: [{
type: 'RANGE_ADD',
parentID: id,
connectionInfo: [{
key: 'ContactsList_contacts',
rangeBehavior: 'append',
}],
edgeName: 'postEdge'
}],
// updater: (store) => {
// // const inspector = new RecordSourceInspector(store)
// const payload = store.getRootField('insert_post')
// const newEdge = payload.getLinkedRecord('postEdge')
// const proxy = store.get(id)
// // Conn is always undefined here
// const conn = ConnectionHandler.getConnection(proxy, 'ContactsList_contacts')
// ConnectionHandler.insertEdgeAfter(conn, newEdge)
// }
},
);
}
Well, I was able to fix this by changing the line
#connection(key: "ContactsList_contacts")
To
#connection(key: "ContactsList_contacts", filters: [])
Seems it couldn't find the connection otherwise...
https://facebook.github.io/relay/docs/pagination-container.html#connection-directive
Then using the updater function the connection was found.

Categories