diff --git a/.prettierrc b/.prettierrc index 37e7ad4..c84be25 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,31 @@ { "trailingComma": "none", + "singleQuote": true, + "tabWidth": 2, "printWidth": 120, - "tabWidth": 2 -} + "arrowParens": "avoid", + "overrides": [ + { + "files": "**/lwc/**/*.html", + "options": { + "parser": "lwc" + } + }, + { + "files": "*.{cmp,page,component}", + "options": { + "parser": "html" + } + }, + { + "files": "*.{cls, apex}", + "options": { + "parser": "apex" + } + } + ], + "plugins": [ + "prettier-plugin-apex" + ], + "$schema": "https://json.schemastore.org/prettierrc" +} \ No newline at end of file diff --git a/force-app/factory/RepoFactoryMock.cls b/force-app/factory/RepoFactoryMock.cls index addad53..beef07b 100644 --- a/force-app/factory/RepoFactoryMock.cls +++ b/force-app/factory/RepoFactoryMock.cls @@ -130,10 +130,20 @@ public class RepoFactoryMock { return this.results; } - public override List> getSosl(String searchTerm, List queries, List additionalSoslObjects) { + public override List> getSosl( + String searchTerm, + List queries, + List additionalSoslObjects + ) { QueriesMade.addAll(queries); - this.clearState(); - return new List>{ this.results }; + List> results = new List>{ this.results }; + for (AdditionalSoslObject additionalSoslObject : additionalSoslObjects) { + if (additionalSoslObject.objectType != this.repoType) { + results.add(getResults(additionalSoslObject.objectType)); + QueriesMade.addAll(additionalSoslObject.queryFilters); + } + } + return results; } public override Integer count(List queries) { diff --git a/force-app/repository/IRepository.cls b/force-app/repository/IRepository.cls index 4176856..13bd57d 100644 --- a/force-app/repository/IRepository.cls +++ b/force-app/repository/IRepository.cls @@ -7,16 +7,16 @@ public interface IRepository extends IDML { List> getSosl(String searchTerm, Query query); List> getSosl(String searchTerm, List queries); - List> getSosl( - String searchTerm, - List queries, - List additionalSoslObjects - ); + List> getSosl(String searchTerm, List queries, List additionalSoslObjects); IRepository setSearchGroup(SearchGroup searchGroup); + IRepository clearBindVars(); IRepository setAccessLevel(System.AccessLevel accessLevel); IRepository setLimit(Integer limitAmount); IRepository addSortOrder(Schema.SObjectField fieldToken, RepositorySortOrder sortOrder); + IRepository addSortOrder(List fieldChain, RepositorySortOrder sortOrder); + + IRepository addBaseFields(List fields); IRepository addParentFields(List relationshipFields, List parentFields); IRepository addChildFields(Schema.SObjectField childFieldToken, List childFields); IRepository addChildFields( diff --git a/force-app/repository/Query.cls b/force-app/repository/Query.cls index b813ecd..30b7ad3 100644 --- a/force-app/repository/Query.cls +++ b/force-app/repository/Query.cls @@ -1,4 +1,6 @@ public virtual class Query { + private Boolean isSoslEmpty = false; + public enum Operator { EQUALS, NOT_EQUALS, @@ -20,6 +22,10 @@ public virtual class Query { private static final String BIND_VAR_MERGE = 'bindVar{0}'; private static Integer BIND_VAR_NUMBER = 0; + public Boolean isSoslEmpty() { + return this.isSoslEmpty; + } + public Query usingParent(Schema.SObjectField parentField) { return this.usingParent(new List{ parentField }); } @@ -238,6 +244,9 @@ public virtual class Query { startingString = startingString.replace(operatorToReplace, newOperator); } } + if (startingString.endsWith('()')) { + this.isSoslEmpty = true; + } return startingString; } @@ -303,7 +312,7 @@ public virtual class Query { } else if (predicate instanceof Iterable) { Iterable localPredicates = (Iterable) predicate; if (localPredicates.iterator().hasNext() == false) { - return ''; + return '()'; } List innerStrings = new List(); for (Object innerPred : localPredicates) { @@ -311,8 +320,8 @@ public virtual class Query { String innerString = this.getSoslPredicate(innerPred); innerStrings.add(innerString); } - String start = innerStrings.size() > 1 ? '(' : ''; - String ending = innerStrings.size() > 1 ? ')' : ''; + String start = '(' ; + String ending = ')' ; return start + String.join(innerStrings, ',') + ending; } else if (predicate instanceof String) { String input = (String) predicate; diff --git a/force-app/repository/QueryTests.cls b/force-app/repository/QueryTests.cls index 1159761..835f28f 100644 --- a/force-app/repository/QueryTests.cls +++ b/force-app/repository/QueryTests.cls @@ -80,10 +80,7 @@ private class QueryTests { Id nullId = null; String expectedQuery = '(Id = null OR Id != null)'; - Query orQuery = Query.orQuery( - Query.equals(Account.Id, nullId), - Query.notEquals(Account.Id, nullId) - ); + Query orQuery = Query.orQuery(Query.equals(Account.Id, nullId), Query.notEquals(Account.Id, nullId)); System.assertEquals(expectedQuery, orQuery.toString()); } @@ -100,10 +97,7 @@ private class QueryTests { Query.equals(Contact.LastName, 'asb'), Query.andQuery( Query.equals(Contact.FirstName, 'John'), - Query.orQuery( - Query.notEquals(Contact.LastName, 'a'), - Query.notEquals(Contact.LastName, 'b') - ) + Query.orQuery(Query.notEquals(Contact.LastName, 'a'), Query.notEquals(Contact.LastName, 'b')) ) } ) @@ -180,10 +174,7 @@ private class QueryTests { Query.equals(Account.AnnualRevenue, 50), Query.equals(Account.Industry, 'Tech'), Query.orQuery( - new List{ - Query.equals(Account.NumberOfEmployees, 1), - Query.equals(Account.Site, 'web3') - } + new List{ Query.equals(Account.NumberOfEmployees, 1), Query.equals(Account.Site, 'web3') } ) } ) @@ -218,4 +209,13 @@ private class QueryTests { Assert.areEqual('Id IN (\'' + String.join(fakeAccountIds, '\',\'') + '\')', query.toSoslString()); } + + @IsTest + static void it_works_for_singular_value_in_collections() { + List fakeAccountIds = new List{ TestingUtils.generateId(Account.SObjectType) }; + + Query query = Query.equals(Account.Id, fakeAccountIds); + + Assert.areEqual('Id IN (\'' + String.join(fakeAccountIds, '\',\'') + '\')', query.toSoslString()); + } } diff --git a/force-app/repository/Repository.cls b/force-app/repository/Repository.cls index 9a8dc70..5b73f93 100644 --- a/force-app/repository/Repository.cls +++ b/force-app/repository/Repository.cls @@ -16,11 +16,7 @@ public virtual without sharing class Repository implements IRepository { private Integer limitAmount; private SearchGroup soslSearchGroup = SearchGroup.ALL_FIELDS; - public Repository( - Schema.SObjectType repoType, - List queryFields, - RepoFactory repoFactory - ) { + public Repository(Schema.SObjectType repoType, List queryFields, RepoFactory repoFactory) { this.dml = repoFactory.getDml(); this.queryFields = queryFields; this.repoType = repoType; @@ -69,18 +65,20 @@ public virtual without sharing class Repository implements IRepository { return this; } - public Repository addSortOrder( - List parentFieldChain, - RepositorySortOrder sortOrder - ) { + public Repository addSortOrder(List parentFieldChain, RepositorySortOrder sortOrder) { this.fieldToSortOrder.put(Query.getBuiltUpParentFieldName(parentFieldChain), sortOrder); return this; } - public Repository addParentFields( - List parentTypes, - List parentFields - ) { + public Repository addBaseFields(List fields) { + Set uniqueFields = new Set(this.queryFields); + uniqueFields.addAll(fields); + this.queryFields.clear(); + this.queryFields.addAll(uniqueFields); + return this; + } + + public Repository addParentFields(List parentTypes, List parentFields) { String parentBase = ''; for (SObjectField parentId : parentTypes) { parentBase += parentId.getDescribe().getRelationshipName() + '.'; @@ -138,19 +136,16 @@ public virtual without sharing class Repository implements IRepository { return this; } + public Repository clearBindVars() { + this.bindVars.clear(); + return this; + } + protected virtual Set addSelectFields() { this.baseSelectUsed = true; return this.addSelectFields(this.queryFields); } - private Set addSelectFields(List fields) { - Set fieldStrings = new Set{ 'Id' }; - for (SObjectField field : fields) { - fieldStrings.add(field.getDescribe().getName()); - } - return fieldStrings; - } - protected virtual String getFinalQuery(List queries) { return this.getSelectAndFrom() + this.addWheres(queries) + @@ -159,7 +154,6 @@ public virtual without sharing class Repository implements IRepository { } protected virtual void clearState() { - this.bindVars.clear(); this.fieldToSortOrder.clear(); this.limitAmount = null; } @@ -181,11 +175,22 @@ public virtual without sharing class Repository implements IRepository { return 'SELECT ' + String.join(localSelectFields, ', ') + '\nFROM ' + this.repoType; } + private Set addSelectFields(List fields) { + Set fieldStrings = new Set{ 'Id' }; + for (SObjectField field : fields) { + fieldStrings.add(field.getDescribe().getName()); + } + return fieldStrings; + } + private String addWheres(List queries) { List wheres = new List(); for (Query qry : queries) { - wheres.add(this.isSosl ? qry.toSoslString() : qry.toString()); - this.bindVars.putAll(qry.getBindVars()); + String possibleClauseToAdd = this.isSosl ? qry.toSoslString() : qry.toString(); + if (qry.isSoslEmpty() == false) { + wheres.add(possibleClauseToAdd); + this.bindVars.putAll(qry.getBindVars()); + } } return wheres.isEmpty() ? '' : '\nWHERE ' + String.join(wheres, '\nAND '); } @@ -225,7 +230,7 @@ public virtual without sharing class Repository implements IRepository { return this.getSosl(searchTerm, queryFilters, new List()); } - public virtual List> getSosl( + public virtual List> getSosl( String searchTerm, List queryFilters, List additionalSoslObjects @@ -261,11 +266,11 @@ public virtual without sharing class Repository implements IRepository { for (AdditionalSoslObject soslObject : soslObjects) { objectsPreJoin.add( soslObject.objectType + - '(' + - String.join(this.addSelectFields(soslObject.selectFields), ',') + - this.addWheres(soslObject.queryFilters) + - this.getLimitAmount(soslObject.queryLimit) + - ')' + '(' + + String.join(this.addSelectFields(soslObject.selectFields), ',') + + this.addWheres(soslObject.queryFilters) + + this.getLimitAmount(soslObject.queryLimit) + + ')' ); } return String.join(objectsPreJoin, ','); diff --git a/force-app/repository/RepositoryTests.cls b/force-app/repository/RepositoryTests.cls index 8af88cc..3d89717 100644 --- a/force-app/repository/RepositoryTests.cls +++ b/force-app/repository/RepositoryTests.cls @@ -70,10 +70,7 @@ private class RepositoryTests { IRepository repo = new ContactPointAddressRepo(); - Query notLike = Query.notLike( - ContactPointAddress.Name, - new List{ cpa.Name, 'someOtherString' } - ); + Query notLike = Query.notLike(ContactPointAddress.Name, new List{ cpa.Name, 'someOtherString' }); List cpas = repo.get(notLike); @@ -151,10 +148,7 @@ private class RepositoryTests { .addSortOrder(Account.Name, RepositorySortOrder.ASCENDING) .addSortOrder( Account.AnnualRevenue, - new RepositorySortOrder( - RepositorySortOrder.SortOrder.DESCENDING, - RepositorySortOrder.NullSortOrder.LAST - ) + new RepositorySortOrder(RepositorySortOrder.SortOrder.DESCENDING, RepositorySortOrder.NullSortOrder.LAST) ) .setLimit(1) .getAll(); @@ -177,17 +171,15 @@ private class RepositoryTests { accounts[2].ParentId = accounts[3].Id; update accounts; - List returnedAccounts = new AccountRepo() + IRepository accountRepo = new AccountRepo(); + List returnedAccounts = accountRepo .addParentFields( new List{ Account.ParentId }, new List{ Account.Id, Account.Name } ) .addSortOrder( new List{ Account.ParentId, Account.Name }, - new RepositorySortOrder( - RepositorySortOrder.SortOrder.ASCENDING, - RepositorySortOrder.NullSortOrder.LAST - ) + new RepositorySortOrder(RepositorySortOrder.SortOrder.ASCENDING, RepositorySortOrder.NullSortOrder.LAST) ) .setLimit(1) .getAll(); @@ -200,9 +192,7 @@ private class RepositoryTests { @IsTest static void it_should_decorate_dml_methods() { - IRepository repo = new RepoFactory() - .setFacade(new RepoFactoryMock.FacadeMock()) - .getProfileRepo(); + IRepository repo = new RepoFactory().setFacade(new RepoFactoryMock.FacadeMock()).getProfileRepo(); Account acc = new Account(); List accs = new List{ acc }; @@ -277,6 +267,34 @@ private class RepositoryTests { System.assertEquals(record.Id, results.get(1).get(0).Id); } + @IsTest + static void it_handles_empty_sosl_searches() { + ContactPointPhone record = new ContactPointPhone(TelephoneNumber = 'hello universe'); + insert record; + + Test.setFixedSearchResults(new List{ record.Id }); + + List> results = new ContactPointAddressRepo() + .setSearchGroup(SearchGroup.NAME_FIELDS) + .getSosl( + 'hel', + new List(), + new List{ + new AdditionalSoslObject( + ContactPointPhone.SObjectType, + new List(), + new List{ + Query.notEquals(ContactPointPhone.Id, new List()), + Query.equals(ContactPointPhone.TelephoneNumber, 'hello universe') + }, + 1 + ) + } + ); + + System.assertEquals(record.Id, results.get(1).get(0).Id); + } + private class GroupMemberRepo extends Repository { public GroupMemberRepo() { super(GroupMember.SObjectType, new List{ GroupMember.GroupId }, new RepoFactory()); diff --git a/package-lock.json b/package-lock.json index 2a1d605..e44b142 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "apex-dml-mocking", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "apex-dml-mocking", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "devDependencies": { "@salesforce/cli": "latest", - "prettier": "3.2.5", - "prettier-plugin-apex": "2.1.0" + "prettier": "3.3.3", + "prettier-plugin-apex": "2.1.4" } }, "node_modules/@azure/abort-controller": { @@ -16164,10 +16164,11 @@ } }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -16179,10 +16180,11 @@ } }, "node_modules/prettier-plugin-apex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-apex/-/prettier-plugin-apex-2.1.0.tgz", - "integrity": "sha512-km/Aqy/ZtkSkBxe8APN+ue87OA7ApL9q0mmI6ryQrVpGpcj+0ZLTiOFyR5K9t8NpTkTEvXdFuHlYmNzw9IirTw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-apex/-/prettier-plugin-apex-2.1.4.tgz", + "integrity": "sha512-kGImHH2s+RsPtAXwbh5VmqqSTYhts626Zle2ryeUKJ4VY+vDyOQ53ppWOzFPA1XGdRpthh++WliD0ZVP1kdReA==", "dev": true, + "license": "MIT", "dependencies": { "jest-docblock": "^29.0.0", "wait-on": "^7.2.0" diff --git a/package.json b/package.json index a0905b0..dae5998 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "apex-dml-mocking", - "version": "1.0.1", + "version": "1.0.2", "description": "Easy DML / SOQL mocking through the Factory pattern in Apex", "dependencies": {}, "devDependencies": { "@salesforce/cli": "latest", - "prettier-plugin-apex": "2.1.0", - "prettier": "3.2.5" + "prettier-plugin-apex": "2.1.4", + "prettier": "3.3.3" }, "repository": { "type": "git",