Skip to content

Commit

Permalink
Add tests for query-soql-utils
Browse files Browse the repository at this point in the history
  • Loading branch information
paustint committed Jun 25, 2024
1 parent 6dc2c39 commit f5f8378
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 3 deletions.
159 changes: 159 additions & 0 deletions libs/features/query/src/utils/__tests__/query-soql-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { FieldType as QueryFieldType, parseQuery } from 'soql-parser-js';
import { SoqlMetadataTree, __TEST_EXPORTS__ } from '../query-soql-utils';

const {
getFieldsFromAllPartsOfQuery,
getParsableFields,
getParsableFieldsFromFilter,
fetchAllMetadata,
findRequiredRelationships,
fetchRecursiveMetadata,
getLowercaseFieldMap,
getLowercaseFieldMapWithFullPath,
} = __TEST_EXPORTS__;

describe('getParsableFields', () => {
it('should return an empty array when given an empty array of fields', () => {
const fields: QueryFieldType[] = [];
const result = getParsableFields(fields);
expect(result.fields).toEqual([]);
expect(result.subqueries).toEqual({});
});

it('should return the correct fields when given an array of fields', () => {
const fields: QueryFieldType[] = [
{ type: 'Field', field: 'Name' },
{ type: 'Field', field: 'Account.Name' },
{ type: 'Field', field: 'Account.Owner.Name' },
{ type: 'FieldRelationship', rawValue: 'Parent' },
{ type: 'FieldRelationship', rawValue: 'Parent.Account' },
{ type: 'FieldFunctionExpression', parameters: ['CreatedDate'] },
{
type: 'FieldTypeof',
objectType: 'Account',
field: 'Name',
conditions: [
{
type: 'WHEN',
objectType: 'User',
fieldList: ['Id'],
},
{
type: 'WHEN',
objectType: 'Group',
fieldList: ['CreatedById'],
},
],
},
{
type: 'FieldSubquery',
subquery: {
relationshipName: 'Contacts',
fields: [
{ type: 'Field', field: 'Name' },
{ type: 'Field', field: 'Email' },
],
},
},
] as any;
const result = getParsableFields(fields);
expect(result.fields).toEqual([
'account.name',
'account.owner.name',
'createddate',
'name',
'parent',
'parent.account',
'user!name.id',
]);
expect(result.subqueries).toEqual({
Contacts: ['email', 'name'],
});
});
});

describe('getParsableFieldsFromFilter', () => {
it('should return an empty array when given a null filter', () => {
const result = getParsableFieldsFromFilter(null);
expect(result).toEqual([]);
});

it('should return an empty array when given a filter without value conditions', () => {
const filter = parseQuery(
`SELECT Id FROM Account WHERE (Id IN ('1', '2', '3') OR (NOT Id = '2') OR (Name LIKE '%FOO%' OR (Name LIKE '%ARM%' AND FOO = 'bar')))`
).where;
const result = getParsableFieldsFromFilter(filter);
expect(result).toEqual(['id', 'name', 'foo']);
});

it('should return the correct fields when given a filter with value conditions', () => {
const filter = parseQuery(`SELECT Id, Name, Account.Name FROM Contact WHERE Account.Industry = 'media'`).where;
const result = getParsableFieldsFromFilter(filter);
expect(result).toEqual(['account.industry']);
});

it('Should ignore subqueries in WHERE clause', () => {
const filter = parseQuery(
`SELECT Id, Name FROM Account WHERE Id IN (SELECT AccountId FROM Contact WHERE LastName LIKE 'apple%') AND Id IN (SELECT AccountId FROM Opportunity WHERE isClosed = FALSE)`
).where;
const result = getParsableFieldsFromFilter(filter);
expect(result).toEqual([]);
});
});

describe('findRequiredRelationships', () => {
it('should return an empty array when given an empty array of fields', () => {
const fields: string[] = [];
const result = findRequiredRelationships(fields);
expect(result).toEqual([]);
});

it('should return the correct relationships when given an array of fields', () => {
const fields: string[] = ['LastModifiedBy.Account.LastModifiedBy.Foo', 'Parent.Name', 'Account.Owner', 'Contact.Account.Owner'];
const result = findRequiredRelationships(fields);
expect(result).toEqual([
'LastModifiedBy',
'LastModifiedBy.Account',
'LastModifiedBy.Account.LastModifiedBy',
'Parent',
'Account',
'Contact',
'Contact.Account',
]);
});
});

describe('getLowercaseFieldMapWithFullPath', () => {
it('should combine all lowercaseFieldMaps into one object with the full field path', () => {
const metadata: Record<string, SoqlMetadataTree> = {
Account: {
key: 'Account',
parentField: { name: 'Account', relationshipName: null },
fieldKey: 'Account',
level: 0,
metadata: { name: 'Account', fields: [{ name: 'Id' }, { name: 'Name' }] },
lowercaseFieldMap: { id: { name: 'Id' }, name: { name: 'Name' } },
children: [
{
key: 'Account.Owner',
parentField: { name: 'Owner', relationshipName: 'Owner' },
fieldKey: 'Account.Owner',
level: 1,
metadata: { name: 'User', fields: [{ name: 'Id' }, { name: 'Name' }] },
lowercaseFieldMap: { id: { name: 'Id' }, name: { name: 'Name' } },
children: [],
},
],
},
};

const output = getLowercaseFieldMapWithFullPath(metadata);

expect(output).toEqual({
'Account.id': { name: 'Id' },
'Account.name': { name: 'Name' },
'Account.Owner.id': { name: 'Id' },
'Account.Owner.name': { name: 'Name' },
});
});
});
13 changes: 12 additions & 1 deletion libs/features/query/src/utils/query-soql-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ function getParsableFieldsFromFilter(where: Maybe<WhereClause>, fields: string[]
if (isWhereOrHavingClauseWithRightCondition(where)) {
getParsableFieldsFromFilter(where.right, fields);
}
return fields;
return Array.from(new Set(fields));
}

/**
Expand Down Expand Up @@ -385,3 +385,14 @@ function getLowercaseFieldMapWithFullPath(metadata: Record<string, SoqlMetadataT

return output;
}

export const __TEST_EXPORTS__ = {
getFieldsFromAllPartsOfQuery,
getParsableFields,
getParsableFieldsFromFilter,
fetchAllMetadata,
findRequiredRelationships,
fetchRecursiveMetadata,
getLowercaseFieldMap,
getLowercaseFieldMapWithFullPath,
};
4 changes: 2 additions & 2 deletions libs/features/query/src/utils/useQueryRestore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fireToast } from '@jetstream/ui';
import { fromQueryState, selectedOrgState } from '@jetstream/ui-core';
import isString from 'lodash/isString';
import { useEffect, useRef, useState } from 'react';
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil';
import { useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil';
import { Query, parseQuery } from 'soql-parser-js';
import { QueryRestoreErrors, UserFacingRestoreError, restoreQuery } from './query-restore-utils';

Expand Down Expand Up @@ -39,7 +39,7 @@ export const useQueryRestore = (
const org = useRecoilValue<SalesforceOrgUi>(selectedOrgState);
// we should compare setting here vs in a selector - any difference in performance?

const [isRestore, setIsRestore] = useRecoilState(fromQueryState.isRestore);
const setIsRestore = useSetRecoilState(fromQueryState.isRestore);
const setIsTooling = useSetRecoilState(fromQueryState.isTooling);
const setSObjectsState = useSetRecoilState(fromQueryState.sObjectsState);
const setSelectedSObjectState = useSetRecoilState(fromQueryState.selectedSObjectState);
Expand Down
3 changes: 3 additions & 0 deletions libs/features/query/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"module": "commonjs",
"noImplicitAny": false,
"strictNullChecks": false,
"strict": false,
"types": ["jest", "node"]
},
"include": [
Expand Down

0 comments on commit f5f8378

Please sign in to comment.