├── Apex_Integrations_Custom_Authentications ├── CustomAuthentication.cls ├── README.md └── SF_Custom_Integration__mdt │ ├── SF_Custom_Integration__mdt.object-meta.xml │ └── fields │ ├── Auth_URL__c.field-meta.xml │ ├── Client_Id__c.field-meta.xml │ ├── Client_Secret__c.field-meta.xml │ ├── Content_Type__c.field-meta.xml │ ├── Password__c.field-meta.xml │ ├── Security_Token__c.field-meta.xml │ ├── Username__c.field-meta.xml │ └── grant_type__c.field-meta.xml ├── Apex_Interfaces_Tutorial ├── AccountCloneLogic.cls ├── CloneRecordsController.cls ├── CloneRecords_Service.cls ├── Clone_Interface.cls ├── ContactCloneLogic.cls ├── README.md └── cloneRecords │ ├── cloneRecords.html │ ├── cloneRecords.js │ └── cloneRecords.js-meta.xml ├── Asynchronous_Triggers ├── AccountChangeEvent_Trigger.trigger └── README.md ├── Auth_Flows ├── IdP Initiated SAML Flow.pdf ├── JWT Bearer Flow.pdf ├── README.md ├── Refresh Token Flow.pdf ├── SP-Initiated SAML Flow.pdf ├── User Agent Flow.pdf └── Web Server Flow.pdf ├── DML_Mocking ├── Case_Service.cls ├── Case_Service_DML_Test.cls ├── Case_Service_Test.cls ├── DML_Cases.cls ├── MockIdBuilder.cls ├── MockSObjectBuilder.cls ├── MockSObjectBuilder_Test.cls ├── Query_Cases.cls ├── README.md └── UniversalMocker.cls ├── LWC_Data_Attributes ├── README.md └── lwcDataAttributes │ ├── lwcDataAttributes.html │ ├── lwcDataAttributes.js │ └── lwcDataAttributes.js-meta.xml ├── LWC_List_View_Button ├── Contact.object ├── List_View_App_Page.flexipage ├── List_View_App_Page.tab ├── List_View_Button_Flow.flow ├── List_View_Button_Flow.flowDefinition ├── List_View_Tab.tab ├── README.md └── listViewButton │ ├── listViewButton.html │ ├── listViewButton.js │ └── listViewButton.js-meta.xml ├── Nebula_Logger ├── Log_Page.flexipage ├── NebulaComponent_Controller.cls ├── README.md └── nebulaComponent │ ├── nebulaComponent.css │ ├── nebulaComponent.html │ ├── nebulaComponent.js │ └── nebulaComponent.js-meta.xml ├── POST_Request ├── AccountWrapper.cls ├── POST_Example.cls ├── README.md └── REST_Service.cls ├── Platform_Cache ├── ContactPlatformCacheData.cls ├── PartitionCacheExampleController.cls ├── README.md └── partitionCacheExample │ ├── partitionCacheExample.html │ ├── partitionCacheExample.js │ └── partitionCacheExample.js-meta.xml ├── README.md ├── REST_Resource ├── README.md └── Users_Rest_Resource.cls ├── The_Complete_Guide_To_Apex_Triggers ├── Accounts.cls ├── Accounts.trigger ├── Accounts_Marketing.cls ├── Case_Trigger.trigger ├── Case_Trigger_Handler.cls ├── Cases.trigger ├── ContactTrigger.trigger ├── DomainObject.cls └── README.md ├── The_Complete_Guide_To_SOQL_And_SOSL ├── ApexSOQLExample.cls ├── ApexSOQLExample.cls-meta.xml ├── ApexSOSLExample.cls └── ApexSOSLExample.cls-meta.xml ├── Wrapper_Classes ├── README.md ├── Reddit_Post_Retriever.cls └── Salesforce_Reddit.cls ├── lwc_pdf_generation ├── PdfGenerator.cls ├── README.md ├── jspdf.resource └── jspdfDemo │ ├── jspdfDemo.html │ ├── jspdfDemo.js │ └── jspdfDemo.js-meta.xml ├── lwc_utility_modules ├── README.md ├── demo_component │ ├── demo_component.html │ ├── demo_component.js │ └── demo_component.js-meta.xml └── util_module │ ├── util_module.js │ └── util_module.js-meta.xml ├── lwc_word_document_generation ├── ContactGrabber.cls ├── README.md ├── contact_list_generator │ ├── contact_list_generator.css │ ├── contact_list_generator.html │ ├── contact_list_generator.js │ └── contact_list_generator.js-meta.xml └── docx.js ├── named_credentials ├── GitHubAuthCallout_NoCredential.cls ├── GitHubAuthCallout_NoCredential.cls-meta.xml ├── GitHubPassTokenCallout.cls ├── GitHubPassTokenCallout.cls-meta.xml ├── GithubOAuthCallout.cls ├── GithubOAuthCallout.cls-meta.xml ├── GoogleMapsCallout.cls ├── GoogleMapsCallout.cls-meta.xml └── README.md ├── safe_navigation_examples ├── README.md ├── safeNavExample.cls └── safeNavExample.cls-meta.xml └── trigger_framework ├── Case_Trigger_Handler.cls ├── Case_Trigger_Handler.cls-meta.xml ├── Case_Utility.cls ├── Case_Utility.cls-meta.xml ├── README.md ├── TriggerHandler.cls ├── TriggerHandler.cls-meta.xml ├── TriggerHandler_Test.cls └── TriggerHandler_Test.cls-meta.xml /Apex_Integrations_Custom_Authentications/CustomAuthentication.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 3/3/2021. 3 | */ 4 | 5 | public with sharing class CustomAuthentication 6 | { 7 | private final String ACCOUNT_CREATION_URL = 'https://codingwiththeforce-dev-ed.my.salesforce.com/services/apexrest/AccountDML/'; 8 | private final String JSON_CONTENT_TYPE = 'application/json'; 9 | private final String POST_METHOD = 'POST'; 10 | 11 | public Id createAccountCallout(){ 12 | SF_Custom_Integration__mdt connectionData = getConnectionMetadata(); 13 | SF_Auth authBody = getSFAccessToken(connectionData); 14 | Id acctId = createAccount(authBody); 15 | return acctId; 16 | } 17 | 18 | private SF_Custom_Integration__mdt getConnectionMetadata(){ 19 | List connectionData = [SELECT Auth_URL__c, Client_Id__c, Client_Secret__c, Content_Type__c, grant_type__c, Password__c, Security_Token__c, Username__c FROM SF_Custom_Integration__mdt LIMIT 1]; 20 | return connectionData[0]; 21 | } 22 | 23 | private SF_Auth getSFAccessToken(SF_Custom_Integration__mdt connectionData){ 24 | HttpRequest authReq = new HttpRequest(); 25 | authReq.setMethod(POST_METHOD); 26 | authReq.setEndpoint(generateEndpointURL(connectionData)); 27 | authReq.setHeader('Content-Type', connectionData.Content_Type__c); 28 | Http http = new Http(); 29 | HttpResponse response = http.send(authReq); 30 | SF_Auth authBody = SF_Auth.parse(response.getBody()); 31 | return authBody; 32 | } 33 | 34 | private String generateEndpointURL(SF_Custom_Integration__mdt connectionData){ 35 | String authURL = connectionData.Auth_URL__c; 36 | authURL += '?grant_type=' + connectionData.grant_type__c; 37 | authURL += '&client_secret=' + connectionData.Client_Secret__c; 38 | authURL += '&client_id=' + connectionData.Client_Id__c; 39 | authURL += '&username=' + connectionData.Username__c; 40 | authURL += '&password=' + connectionData.Password__c; 41 | authURL += '&security_token=' + connectionData.Security_Token__c; 42 | return authURL; 43 | } 44 | 45 | private Id createAccount(SF_Auth authBody){ 46 | HttpRequest dataReq = new HttpRequest(); 47 | Id accountId = null; 48 | dataReq.setMethod(POST_METHOD); 49 | dataReq.setEndpoint(ACCOUNT_CREATION_URL); 50 | dataReq.setHeader('Authorization', authBody.token_type + ' ' + authBody.access_token); 51 | dataReq.setHeader('Content-Type', JSON_CONTENT_TYPE); 52 | dataReq.setBody(JSON.serialize(wrapAccount(generateAccount()))); 53 | Http http = new Http(); 54 | HttpResponse response = http.send(dataReq); 55 | accountId = Id.valueOf(response.getBody().substringBetween('"', '"')); 56 | return accountId; 57 | } 58 | 59 | private Account generateAccount(){ 60 | Account acct = new Account(); 61 | acct.Name = 'Hi'; 62 | acct.Phone = '8162221111'; 63 | return acct; 64 | } 65 | 66 | private AccountWrapper wrapAccount(Account acct){ 67 | AccountWrapper wrapper = new AccountWrapper(); 68 | wrapper.acct = acct; 69 | return wrapper; 70 | } 71 | } -------------------------------------------------------------------------------- /Apex_Integrations_Custom_Authentications/README.md: -------------------------------------------------------------------------------- 1 | Video Tutorial for Custom Authentications: https://youtu.be/wOkvqAobA1M 2 | -------------------------------------------------------------------------------- /Apex_Integrations_Custom_Authentications/SF_Custom_Integration__mdt/SF_Custom_Integration__mdt.object-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SF_Custom_Integrations 5 | Public 6 | 7 | -------------------------------------------------------------------------------- /Apex_Integrations_Custom_Authentications/SF_Custom_Integration__mdt/fields/Auth_URL__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Auth_URL__c 4 | false 5 | DeveloperControlled 6 | 7 | 255 8 | false 9 | Text 10 | false 11 | 12 | -------------------------------------------------------------------------------- /Apex_Integrations_Custom_Authentications/SF_Custom_Integration__mdt/fields/Client_Id__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Client_Id__c 4 | false 5 | DeveloperControlled 6 | 7 | 255 8 | false 9 | Text 10 | false 11 | 12 | -------------------------------------------------------------------------------- /Apex_Integrations_Custom_Authentications/SF_Custom_Integration__mdt/fields/Client_Secret__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Client_Secret__c 4 | false 5 | DeveloperControlled 6 | 7 | 255 8 | false 9 | Text 10 | false 11 | 12 | -------------------------------------------------------------------------------- /Apex_Integrations_Custom_Authentications/SF_Custom_Integration__mdt/fields/Content_Type__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Content_Type__c 4 | false 5 | DeveloperControlled 6 | 7 | 255 8 | false 9 | Text 10 | false 11 | 12 | -------------------------------------------------------------------------------- /Apex_Integrations_Custom_Authentications/SF_Custom_Integration__mdt/fields/Password__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Password__c 4 | false 5 | DeveloperControlled 6 | 7 | 255 8 | false 9 | Text 10 | false 11 | 12 | -------------------------------------------------------------------------------- /Apex_Integrations_Custom_Authentications/SF_Custom_Integration__mdt/fields/Security_Token__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Security_Token__c 4 | false 5 | DeveloperControlled 6 | 7 | 255 8 | false 9 | Text 10 | false 11 | 12 | -------------------------------------------------------------------------------- /Apex_Integrations_Custom_Authentications/SF_Custom_Integration__mdt/fields/Username__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Username__c 4 | false 5 | DeveloperControlled 6 | 7 | 255 8 | false 9 | Text 10 | false 11 | 12 | -------------------------------------------------------------------------------- /Apex_Integrations_Custom_Authentications/SF_Custom_Integration__mdt/fields/grant_type__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | grant_type__c 4 | false 5 | DeveloperControlled 6 | 7 | 255 8 | false 9 | Text 10 | false 11 | 12 | -------------------------------------------------------------------------------- /Apex_Interfaces_Tutorial/AccountCloneLogic.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 9/28/2021. 3 | */ 4 | 5 | public with sharing class AccountCloneLogic implements Clone_Interface 6 | { 7 | public void cloneRecord(Id recordId){ 8 | System.debug('This is the account clone logic running'); 9 | } 10 | 11 | public void cloneRelatedRecords(Id recordId){ 12 | System.debug('This is the account clone related records logic running'); 13 | } 14 | } -------------------------------------------------------------------------------- /Apex_Interfaces_Tutorial/CloneRecordsController.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 9/28/2021. 3 | */ 4 | 5 | public with sharing class CloneRecordsController 6 | { 7 | @AuraEnabled 8 | public static void cloneRecords(Id recordId, String className){ 9 | Type cloneInterfaceType = Type.forName(className); 10 | Clone_Interface cloneInterfaceInstance = (Clone_Interface)cloneInterfaceType.newInstance(); 11 | CloneRecords_Service cloneRecords = new CloneRecords_Service(cloneInterfaceInstance); 12 | cloneRecords.cloneRecord(recordId); 13 | } 14 | 15 | @AuraEnabled 16 | public static void cloneRelatedRecords(Id recordId, String className){ 17 | Type cloneInterfaceType = Type.forName(className); 18 | Clone_Interface cloneInterfaceInstance = (Clone_Interface)cloneInterfaceType.newInstance(); 19 | CloneRecords_Service cloneRecords = new CloneRecords_Service(cloneInterfaceInstance); 20 | cloneRecords.cloneRelatedRecords(recordId); 21 | } 22 | } -------------------------------------------------------------------------------- /Apex_Interfaces_Tutorial/CloneRecords_Service.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 9/28/2021. 3 | */ 4 | 5 | public with sharing class CloneRecords_Service 6 | { 7 | private Clone_Interface cloneLogic; 8 | public CloneRecords_Service(Clone_Interface cloneClass){ 9 | this.cloneLogic = cloneClass; 10 | } 11 | 12 | public void cloneRecord(Id recordId){ 13 | cloneLogic.cloneRecord(recordId); 14 | } 15 | 16 | public void cloneRelatedRecords(Id recordId){ 17 | cloneLogic.cloneRelatedRecords(recordId); 18 | } 19 | } -------------------------------------------------------------------------------- /Apex_Interfaces_Tutorial/Clone_Interface.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 9/28/2021. 3 | */ 4 | 5 | public interface Clone_Interface 6 | { 7 | void cloneRecord(Id recordId); 8 | void cloneRelatedRecords(Id recordId); 9 | } -------------------------------------------------------------------------------- /Apex_Interfaces_Tutorial/ContactCloneLogic.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 9/28/2021. 3 | */ 4 | 5 | public with sharing class ContactCloneLogic implements Clone_Interface 6 | { 7 | public void cloneRecord(Id recordId){ 8 | System.debug('This is the contact clone logic running'); 9 | } 10 | 11 | public void cloneRelatedRecords(Id recordId){ 12 | System.debug('This is the contact clone related records logic running'); 13 | } 14 | } -------------------------------------------------------------------------------- /Apex_Interfaces_Tutorial/README.md: -------------------------------------------------------------------------------- 1 | Link to Tutorial Video: https://youtu.be/d3xOEg1Rs88 2 | -------------------------------------------------------------------------------- /Apex_Interfaces_Tutorial/cloneRecords/cloneRecords.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Apex_Interfaces_Tutorial/cloneRecords/cloneRecords.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 9/28/2021. 3 | */ 4 | 5 | import {LightningElement, api} from 'lwc'; 6 | import cloneRecordsController from '@salesforce/apex/CloneRecordsController.cloneRecords'; 7 | import cloneRelatedRecordsController from '@salesforce/apex/CloneRecordsController.cloneRelatedRecords'; 8 | 9 | export default class CloneRecords extends LightningElement { 10 | @api recordId; 11 | @api className; 12 | 13 | cloneRecord(){ 14 | cloneRecordsController({"recordId": this.recordId, "className": this.className}).then(()=>{ 15 | console.log('Success'); 16 | this.cloneRelatedRecords(); 17 | }); 18 | } 19 | 20 | cloneRelatedRecords(){ 21 | cloneRelatedRecordsController({"recordId": this.recordId, "className": this.className}).then(()=>{ 22 | console.log('Success'); 23 | }); 24 | } 25 | } -------------------------------------------------------------------------------- /Apex_Interfaces_Tutorial/cloneRecords/cloneRecords.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51.0 4 | This clones records for any object in the org 5 | true 6 | Clone Records 7 | 8 | lightning__RecordPage 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Asynchronous_Triggers/AccountChangeEvent_Trigger.trigger: -------------------------------------------------------------------------------- 1 | //If you want to debug this you need to put a log on the Automated System User. It is the only way to debug these triggers. 2 | trigger AccountChangeEvent_Trigger on AccountChangeEvent (after insert) { 3 | System.debug('This is the Account Change Event Trigger Size ::: ' + trigger.new.size()); 4 | 5 | //Everything you really care about in the ChangeEvent objects are in the ChangeEventHeader. Make sure to 6 | //pull your data from the ChangeEvent header to do what you need to do. 7 | for(AccountChangeEvent ace: trigger.new){ 8 | EventBus.ChangeEventHeader aceHeader = ace.ChangeEventHeader; 9 | List acctIds = aceHeader.getRecordIds(); 10 | String userId = aceHeader.getCommitUser(); 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /Asynchronous_Triggers/README.md: -------------------------------------------------------------------------------- 1 | Video Tutorial for Asynchronous Change Event Triggers: https://youtu.be/6QU96Xhnsqw 2 | -------------------------------------------------------------------------------- /Auth_Flows/IdP Initiated SAML Flow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coding-With-The-Force/Salesforce-Development-Tutorials/4c8e5787e82340e2be58784a45f35332e590b903/Auth_Flows/IdP Initiated SAML Flow.pdf -------------------------------------------------------------------------------- /Auth_Flows/JWT Bearer Flow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coding-With-The-Force/Salesforce-Development-Tutorials/4c8e5787e82340e2be58784a45f35332e590b903/Auth_Flows/JWT Bearer Flow.pdf -------------------------------------------------------------------------------- /Auth_Flows/README.md: -------------------------------------------------------------------------------- 1 | Tutorial Video Here: https://youtu.be/5AbxOGDEgmQ 2 | -------------------------------------------------------------------------------- /Auth_Flows/Refresh Token Flow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coding-With-The-Force/Salesforce-Development-Tutorials/4c8e5787e82340e2be58784a45f35332e590b903/Auth_Flows/Refresh Token Flow.pdf -------------------------------------------------------------------------------- /Auth_Flows/SP-Initiated SAML Flow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coding-With-The-Force/Salesforce-Development-Tutorials/4c8e5787e82340e2be58784a45f35332e590b903/Auth_Flows/SP-Initiated SAML Flow.pdf -------------------------------------------------------------------------------- /Auth_Flows/User Agent Flow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coding-With-The-Force/Salesforce-Development-Tutorials/4c8e5787e82340e2be58784a45f35332e590b903/Auth_Flows/User Agent Flow.pdf -------------------------------------------------------------------------------- /Auth_Flows/Web Server Flow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coding-With-The-Force/Salesforce-Development-Tutorials/4c8e5787e82340e2be58784a45f35332e590b903/Auth_Flows/Web Server Flow.pdf -------------------------------------------------------------------------------- /DML_Mocking/Case_Service.cls: -------------------------------------------------------------------------------- 1 | public with sharing class Case_Service { 2 | private Query_Cases caseQuery; 3 | private DML_Cases caseDML; 4 | 5 | public Case_Service() { 6 | this(new Query_Cases(), new DML_Cases()); 7 | } 8 | 9 | @testVisible 10 | private Case_Service(Query_Cases caseQuery, DML_Cases caseDML){ 11 | this.caseQuery = caseQuery; 12 | this.caseDML = caseDML; 13 | } 14 | 15 | public void doCaseUpdates(Set caseIds){ 16 | List caseList = caseQuery.selectCases(caseIds); 17 | caseDML.updateCases(caseList); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /DML_Mocking/Case_Service_DML_Test.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | public with sharing class Case_Service_DML_Test { 3 | @isTest 4 | private static void doCaseUpdates_NoMocking_Test(){ 5 | Case newCase = new Case(Subject = 'Taco Bell'); 6 | insert newCase; 7 | 8 | Set caseIds = new Set{newCase.Id}; 9 | 10 | Test.startTest(); 11 | Case_Service caseUpdater = new Case_Service(); 12 | caseUpdater.doCaseUpdates(caseIds); 13 | Test.stopTest(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DML_Mocking/Case_Service_Test.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | public with sharing class Case_Service_Test { 3 | @isTest 4 | private static void doCaseUpdates_Test(){ 5 | MockSObjectBuilder mockCase = new MockSObjectBuilder(Case.getSObjectType()); 6 | Case caseRecord = (Case)mockCase.setId().setField('Subject', 'Hey Gurl').build(); 7 | List returnedCases = new List{caseRecord}; 8 | Set caseIds = new Set{caseRecord.Id}; 9 | 10 | final String CASE_QUERY_METHOD = 'selectCases'; 11 | final String CASE_DML_METHOD = 'updateCases'; 12 | 13 | UniversalMocker caseDML_Mock = UniversalMocker.mock(DML_Cases.class); 14 | caseDML_Mock.when(CASE_DML_METHOD).withParamTypes(new List{List.class}).thenReturn(returnedCases); 15 | DML_Cases caseDML = (DML_Cases)caseDML_Mock.createStub(); 16 | 17 | UniversalMocker caseQuery_Mock = UniversalMocker.mock(Query_Cases.class); 18 | caseQuery_Mock.when(CASE_QUERY_METHOD).withParamTypes(new List{Set.class}).thenReturn(returnedCases); 19 | Query_Cases caseQueries = (Query_Cases)caseQuery_Mock.createStub(); 20 | 21 | Test.startTest(); 22 | Case_Service caseService = new Case_Service(caseQueries, caseDML); 23 | caseService.doCaseUpdates(caseIds); 24 | caseDML_Mock.assertThat().method(CASE_DML_METHOD).withParamTypes(new List{List.class}).wasCalled(1, UniversalMocker.Times.EXACTLY); 25 | caseQuery_Mock.assertThat().method(CASE_QUERY_METHOD).withParamTypes(new List{Set.class}).wasCalled(1, UniversalMocker.Times.EXACTLY); 26 | Test.stopTest(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DML_Mocking/DML_Cases.cls: -------------------------------------------------------------------------------- 1 | public with sharing class DML_Cases { 2 | public List updateCases(List caseList){ 3 | database.update(caseList); 4 | return caseList; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DML_Mocking/MockIdBuilder.cls: -------------------------------------------------------------------------------- 1 | /* 2 | * Builds fake ids for unit testing 3 | * Created by Caleb Weaver --- Acumen Solutions --- 07/01/2019. 4 | */ 5 | 6 | @isTest 7 | public with sharing class MockIdBuilder { 8 | 9 | public static Integer idCount = 0; 10 | 11 | //Get a fake id for a given SObjectType 12 | public static String getMockId(SObjectType objectType) { 13 | 14 | String nextIdCount = String.valueOf(idCount++); 15 | 16 | return objectType.getDescribe().getKeyPrefix() 17 | + getFillerZeros(nextIdCount) 18 | + nextIdCount; 19 | } 20 | 21 | //Gets how many 0's the id needs for correct length 22 | private static String getFillerZeros(String nextIdCount) { 23 | return '0'.repeat(12-nextIdCount.length()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DML_Mocking/MockSObjectBuilder.cls: -------------------------------------------------------------------------------- 1 | /* 2 | * Utility for building mock sobjects 3 | * Utilizes json deserialization to allow for building of otherwise unwritable fields 4 | * The test class should provide examples of how to use this 5 | * Created by Caleb Weaver --- Acumen Solutions --- 07/01/2019. 6 | */ 7 | 8 | public with sharing class MockSObjectBuilder { 9 | 10 | public SObjectType mockType {get; private set;} 11 | 12 | private String starterSObject; 13 | 14 | private static Integer idCount = 1; 15 | 16 | private Map fieldToValue = new Map(); 17 | private Map> relationshipToChild = new Map>(); 18 | private Map parentsByLabels = new Map(); 19 | 20 | //Initialize a builder with an sobject type 21 | public MockSObjectBuilder(SObjectType type) { 22 | mockType = type; 23 | } 24 | 25 | //Initialize a builder with an existing sobject 26 | public MockSObjectBuilder(SObject starterObject) { 27 | mockType = starterObject.getSObjectType(); 28 | starterSObject = JSON.serialize(starterObject); 29 | } 30 | 31 | //Set a normal field on the sobject using the schema field name 32 | public MockSObjectBuilder setField(SObjectField field, String value) { 33 | fieldToValue.put(field, value); 34 | return this; 35 | } 36 | 37 | //Set a normal field on the sobject using the string field name 38 | public MockSObjectBuilder setField(String field, String value) { 39 | SObjectField describeField = getDescribeFieldFromString(field); 40 | setField(describeField, value); 41 | return this; 42 | } 43 | 44 | //Set a date field on the sobject using the string field name 45 | public MockSObjectBuilder setField(String field, Date value) { 46 | SObjectField describeField = getDescribeFieldFromString(field); 47 | String stringifiedValue = trimQuotes(JSON.serialize(value)); 48 | setField(describeField, stringifiedValue); 49 | return this; 50 | } 51 | 52 | //Set a datetime field on the sobject using the string field name 53 | public MockSObjectBuilder setField(String field, Datetime value) { 54 | SObjectField describeField = getDescribeFieldFromString(field); 55 | String stringifiedValue = trimQuotes(JSON.serialize(value)); 56 | setField(describeField, stringifiedValue); 57 | return this; 58 | } 59 | 60 | //Trim the last quote from json values when building strings 61 | private String trimQuotes(String longValue) { 62 | return longValue.substring(1, longValue.length() - 1); 63 | } 64 | 65 | //Parse the json string for a fields value using schema field name 66 | public String getField(SObjectField field) { 67 | 68 | String value = ''; 69 | 70 | if (fieldToValue.containsKey(field)) { 71 | value = fieldToValue.get(field); 72 | } 73 | return value; 74 | } 75 | 76 | //Parse the json string for a fields value using string field name 77 | public String getField(String field) { 78 | SObjectField describeField = getDescribeFieldFromString(field); 79 | return getField(describeField); 80 | } 81 | 82 | //Get the schema value from a string field name 83 | private SObjectField getDescribeFieldFromString(String field) { 84 | return mockType.getDescribe().fields.getMap().get(field); 85 | } 86 | 87 | //Convenience function to set a new id on an sobject being built 88 | public MockSObjectBuilder setId() { 89 | setField('Id', getMockId()); 90 | return this; 91 | } 92 | 93 | //Get a mock id of the instantiated type 94 | public String getMockId() { 95 | return getMockId(mockType); 96 | } 97 | 98 | //Utility method to get an id of a given type 99 | public static String getMockId(SObjectType objectType) { 100 | 101 | String nextIdCount = String.valueOf(idCount++); 102 | 103 | return objectType.getDescribe().getKeyPrefix() 104 | + getFillerZeros(nextIdCount) 105 | + nextIdCount; 106 | } 107 | 108 | //Determine how many 0s are needed to pad the id for the correct length of id 109 | private static String getFillerZeros(String nextIdCount) { 110 | return '0'.repeat(12-nextIdCount.length()); 111 | } 112 | 113 | //Gets a schema child relationship given its string name 114 | public ChildRelationship getChildRelationship(String childLabel) { 115 | 116 | ChildRelationship relationship; 117 | 118 | for (ChildRelationship childRel : mockType.getDescribe().getChildRelationships()) { 119 | if (childRel.getRelationshipName() == childLabel) { 120 | relationship = childRel; 121 | } 122 | } 123 | 124 | return relationship; 125 | } 126 | 127 | //Sets a parent object and id for the current build object 128 | public MockSObjectBuilder setParent(String parentLabel, SObject parent) { 129 | 130 | if (parent != null) { 131 | 132 | String idKey = getIdFieldNameFromRelationshipName(parentLabel); 133 | if (getDescribeFieldFromString(idKey) != null) { 134 | parentsByLabels.put(parentLabel, JSON.serialize(parent)); 135 | setParentId(parentLabel, parent); 136 | } 137 | } 138 | 139 | return this; 140 | } 141 | 142 | //Sets up the parent id from a parent object label 143 | private void setParentId(String parentLabel, SObject parent) { 144 | if (parent != null && parent.Id != null) { 145 | String idKey = getIdFieldNameFromRelationshipName(parentLabel); 146 | setField(idKey, parent.Id); 147 | } 148 | } 149 | 150 | //Determines methodology for naming parent id field: '__c' for custom, 'Id' for standard 151 | private String getIdFieldNameFromRelationshipName(String parentLabel) { 152 | 153 | String idKey = ''; 154 | if (parentLabel.contains('__r')) { 155 | idKey = parentLabel.substring(0, parentLabel.length() - 1) + 'c'; 156 | } else { 157 | idKey = parentLabel + 'Id'; 158 | } 159 | return idKey; 160 | } 161 | 162 | //Sets a child object for a string child relationship name 163 | public MockSObjectBuilder setChild(String childLabel, SObject child) { 164 | return setChild(getChildRelationship(childLabel), JSON.serialize(child)); 165 | } 166 | 167 | //Sets multiple children for a string child relationship name 168 | public MockSObjectBuilder setChildren(String childLabel, List children) { 169 | List serializedChildren = new List(); 170 | for (SObject child : children) { 171 | serializedChildren.add(JSON.serialize(child)); 172 | } 173 | return setChildren(getChildRelationship(childLabel), serializedChildren); 174 | } 175 | 176 | //Sets a serialized (string-form) child from a child relationship string name 177 | public MockSObjectBuilder setChild(String childLabel, String serializedChild) { 178 | return setChild(getChildRelationship(childLabel), serializedChild); 179 | } 180 | 181 | //Sets multiple serialized (string-form) children from a child relationship string name 182 | public MockSObjectBuilder setChildren(String childLabel, List serializedChildren) { 183 | return setChildren(getChildRelationship(childLabel), serializedChildren); 184 | } 185 | 186 | //Sets multiple children for a schema child relationship name 187 | public MockSObjectBuilder setChild(ChildRelationship childRel, String serializedChild) { 188 | setChildren(childRel, new List{serializedChild}); 189 | return this; 190 | } 191 | 192 | //Sets multiple children for a schema child relationship name 193 | public MockSObjectBuilder setChildren(ChildRelationship childRel, List serializedChildren) { 194 | if (!relationshipToChild.containsKey(childRel)) { 195 | relationshipToChild.put(childRel, new List()); 196 | } 197 | for (String child : serializedChildren) { 198 | relationshipToChild.get(childRel).add(child); 199 | } 200 | return this; 201 | } 202 | 203 | //Builds the currently building sobject 204 | public SObject build() { 205 | 206 | String jsonSObject = getSerializedSObject(); 207 | SObject mockObject = (SObject) JSON.deserialize(jsonSObject, SObject.class); 208 | 209 | return mockObject; 210 | } 211 | 212 | //Returns the string-form of the currently uilding object 213 | public String getSerializedSObject() { 214 | 215 | String jsonSObject = ''; 216 | 217 | if (starterSObject == null) { 218 | jsonSObject = JSON.serialize(mockType.newSObject()); 219 | } else { 220 | jsonSObject = starterSObject; 221 | } 222 | 223 | jsonSObject = openJson(jsonSObject) 224 | + appendJsonFields() 225 | + appendParents() 226 | + appendChildRelationships() 227 | + closeJson(); 228 | 229 | return jsonSObject; 230 | } 231 | 232 | //JSON parsing function 233 | //Opens the json object 234 | private String openJson(String jsonSObject) { 235 | return jsonSObject.substring(0, jsonSObject.length() - 1); 236 | } 237 | 238 | //JSON parsing function 239 | //Appends a field to the JSON object 240 | private String appendJsonFields() { 241 | String fieldsToAppend = ''; 242 | for (SObjectField field : fieldToValue.keySet()) { 243 | fieldsToAppend += ',"' + field + '":"' + fieldToValue.get(field) + '"'; 244 | } 245 | return fieldsToAppend; 246 | } 247 | 248 | //JSON parsing function 249 | //Appends a parent object to the JSON object 250 | private String appendParents() { 251 | String parentsToAppend = ''; 252 | for (String parentLabel : parentsByLabels.keySet()) { 253 | parentsToAppend += ',"'; 254 | parentsToAppend += parentLabel; 255 | parentsToAppend += '":'; 256 | parentsToAppend += parentsByLabels.get(parentLabel); 257 | } 258 | return parentsToAppend; 259 | } 260 | 261 | //JSON parsing function 262 | //Appends children objects to the JSON object 263 | private String appendChildRelationships() { 264 | String childrenToAppend = ''; 265 | for (ChildRelationship relationship : relationshipToChild.keySet()) { 266 | List serializedChildren = relationshipToChild.get(relationship); 267 | childrenToAppend += getRelationshipName(relationship) 268 | + getRelationshipHeaderInfo(serializedChildren.size()) 269 | + getRecordStart() 270 | + getChildRecords(serializedChildren) 271 | + closeChildList(); 272 | } 273 | return childrenToAppend; 274 | } 275 | 276 | //JSON parsing function 277 | //Appends a parent object to the JSON object 278 | private String getRelationshipName(ChildRelationship relationship) { 279 | return ',"' + relationship.getRelationshipName() + '":{'; 280 | } 281 | 282 | //JSON parsing function 283 | //Appends a child relationship header 284 | private String getRelationshipHeaderInfo(Integer childCount) { 285 | return'"totalSize":' + childCount + ',"done":true,'; 286 | } 287 | 288 | //JSON parsing function 289 | //Appends the beginning of the child list 290 | private String getRecordStart() { 291 | return '"records":['; 292 | } 293 | 294 | //JSON parsing function 295 | //AAppends the list of child records 296 | private String getChildRecords(List serializedChildren) { 297 | String childRecords = ''; 298 | for (String child : serializedChildren) { 299 | childRecords += child + ','; 300 | } 301 | childRecords = childRecords.substring(0, childRecords.length() - 1); 302 | return childRecords; 303 | } 304 | 305 | //JSON parsing function 306 | //Appends the JSON child list closing 307 | private String closeChildList() { 308 | return ']}'; 309 | } 310 | 311 | //JSON parsing function 312 | //Appends the JSON object closing 313 | private String closeJson() { 314 | return '}'; 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /DML_Mocking/MockSObjectBuilder_Test.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | private class MockSObjectBuilder_Test { 3 | 4 | private static String TEST_NAME = 'Raymond Powell'; 5 | private static String TEST_NAME_2 = 'Henery Kennsington'; 6 | private static String TEST_PHONE = '1234567890'; 7 | private static String CHILD_LABEL_OPPORTUNITIES = 'Opportunities'; 8 | private static String FIELD_LABEL_NAME = 'Name'; 9 | private static String FIELD_LABEL_OWNER = 'Owner'; 10 | 11 | @isTest 12 | static void build_hasStarterSObject_shouldMaintainPreviousFields() { 13 | Account acc = new Account(Owner = new User(), Phone = TEST_PHONE); 14 | MockSObjectBuilder mockAccountBuilder = new MockSObjectBuilder(acc); 15 | 16 | mockAccountBuilder.setField('Name', TEST_NAME); 17 | 18 | Test.startTest(); 19 | Account mockAccount = (Account) mockAccountBuilder.build(); 20 | Test.stopTest(); 21 | 22 | System.assertEquals(TEST_NAME, mockAccount.Name, 'Account should have correct mock set name.'); 23 | System.assertEquals(TEST_PHONE, mockAccount.Phone, 'Account should have correct previously set phone number.'); 24 | System.assertEquals(new User(), mockAccount.Owner, 'Account should have previously set Owner.'); 25 | } 26 | 27 | @isTest 28 | static void build_hasSObjectType_shouldBuildEmptySObject() { 29 | 30 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 31 | 32 | Test.startTest(); 33 | Account mockAccount = (Account) mockAccountBuilder.build(); 34 | Test.stopTest(); 35 | 36 | System.assertEquals(new Account(), mockAccount, 'Does not have default implementation of class'); 37 | } 38 | 39 | @isTest 40 | static void build_hasFieldSet_objectShouldHaveFieldSet() { 41 | 42 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 43 | 44 | SObjectField nameField = Account.Name.getDescribe().getSObjectField(); 45 | mockAccountBuilder.setField(nameField, TEST_NAME); 46 | 47 | Test.startTest(); 48 | Account mockAccount = (Account) mockAccountBuilder.build(); 49 | Test.stopTest(); 50 | 51 | System.assertEquals(TEST_NAME, mockAccount.Name, 'Object should have correct name.'); 52 | } 53 | 54 | @isTest 55 | static void build_hasReadOnlyFieldSet_objectShouldHaveFieldSet() { 56 | 57 | MockSObjectBuilder mockUserBuilder = getTestMockUserBuilder(); 58 | 59 | SObjectField readOnlyField = User.Name.getDescribe().getSObjectField(); 60 | mockUserBuilder.setField(readOnlyField, TEST_NAME); 61 | 62 | Test.startTest(); 63 | User mockUser = (User) mockUserBuilder.build(); 64 | Test.stopTest(); 65 | 66 | System.assertEquals(TEST_NAME, mockUser.Name, 'Object should have correct Name.'); 67 | } 68 | 69 | @isTest 70 | static void build_hasDateAndDatetimeFieldSet_objectShouldHaveFieldSet() { 71 | 72 | MockSObjectBuilder mockOpportunityBuilder = getTestMockOpportunityBuilder(); 73 | 74 | Datetime createdDate = Datetime.now(); 75 | Date closeDate = Date.today(); 76 | 77 | mockOpportunityBuilder.setField('CreatedDate', createdDate); 78 | mockOpportunityBuilder.setField('CloseDate', closeDate); 79 | 80 | Test.startTest(); 81 | Opportunity mockOpportunity = (Opportunity) mockOpportunityBuilder.build(); 82 | Test.stopTest(); 83 | 84 | System.assertEquals(createdDate, mockOpportunity.CreatedDate, 'Object should have correct Close Date.'); 85 | System.assertEquals(closeDate, mockOpportunity.CloseDate, 'Object should have correct Created Date.'); 86 | } 87 | 88 | @isTest 89 | static void build_hasMultipleFieldsSet_objectShouldHaveAllFieldsSet() { 90 | 91 | MockSObjectBuilder mockUserBuilder = getTestMockUserBuilder() 92 | .setField(FIELD_LABEL_NAME, TEST_NAME) 93 | .setId(); 94 | 95 | Test.startTest(); 96 | User mockUser = (User) mockUserBuilder.build(); 97 | Test.stopTest(); 98 | 99 | System.assertEquals(TEST_NAME, mockUser.Name, 'Object should have correct Name.'); 100 | System.assertEquals(mockUserBuilder.getField('Id'), mockUser.Id, 'Object should have correct Id.'); 101 | } 102 | 103 | @isTest 104 | static void build_givenUnserializedChild_objectShouldHaveChild() { 105 | 106 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 107 | 108 | mockAccountBuilder.setChild(CHILD_LABEL_OPPORTUNITIES, getTestMockOpportunityBuilder().setField(FIELD_LABEL_NAME, TEST_NAME).build()); 109 | 110 | Test.startTest(); 111 | Account mockAccount = (Account) mockAccountBuilder.build(); 112 | Test.stopTest(); 113 | 114 | System.assertEquals(TEST_NAME, mockAccount.Opportunities[0].Name, 'Object should have child'); 115 | } 116 | 117 | @isTest 118 | static void build_givenChild_objectShouldHaveChild() { 119 | 120 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 121 | 122 | mockAccountBuilder.setChild(CHILD_LABEL_OPPORTUNITIES, getSerializedOpp(TEST_NAME)); 123 | 124 | Test.startTest(); 125 | Account mockAccount = (Account) mockAccountBuilder.build(); 126 | Test.stopTest(); 127 | 128 | System.assertEquals(TEST_NAME, mockAccount.Opportunities[0].Name, 'Object should have child'); 129 | } 130 | 131 | @isTest 132 | static void build_given2UnserializedChildren_objectShouldHave2Children() { 133 | 134 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 135 | Opportunity opp = (Opportunity) getTestMockOpportunityBuilder() 136 | .setField(FIELD_LABEL_NAME, TEST_NAME) 137 | .build(); 138 | Opportunity opp2 = (Opportunity) getTestMockOpportunityBuilder() 139 | .setField(FIELD_LABEL_NAME, TEST_NAME_2) 140 | .build(); 141 | 142 | List serializedChildren = new List{opp, opp2}; 143 | 144 | mockAccountBuilder.setChildren(CHILD_LABEL_OPPORTUNITIES, serializedChildren); 145 | 146 | Test.startTest(); 147 | Account mockAccount = (Account) mockAccountBuilder.build(); 148 | Test.stopTest(); 149 | 150 | System.assertEquals(TEST_NAME, mockAccount.Opportunities[0].Name, 'Object should have first child'); 151 | System.assertEquals(TEST_NAME_2, mockAccount.Opportunities[1].Name, 'Object should have second child'); 152 | } 153 | 154 | @isTest 155 | static void build_given2Children_objectShouldHave2Children() { 156 | 157 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 158 | 159 | List serializedChildren = new List{getSerializedOpp(TEST_NAME), getSerializedOpp(TEST_NAME_2)}; 160 | 161 | mockAccountBuilder.setChildren(CHILD_LABEL_OPPORTUNITIES, serializedChildren); 162 | 163 | Test.startTest(); 164 | Account mockAccount = (Account) mockAccountBuilder.build(); 165 | Test.stopTest(); 166 | 167 | System.assertEquals(TEST_NAME, mockAccount.Opportunities[0].Name, 'Object should have first child'); 168 | System.assertEquals(TEST_NAME_2, mockAccount.Opportunities[1].Name, 'Object should have second child'); 169 | } 170 | 171 | @isTest 172 | static void setParent_givenParentIsNull_shouldNotSetParent() { 173 | 174 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 175 | String ownerField = FIELD_LABEL_OWNER; 176 | 177 | Test.startTest(); 178 | Account mockAccount = (Account) mockAccountBuilder.setParent(ownerField, null).build(); 179 | Test.stopTest(); 180 | 181 | System.assertEquals(null, mockAccount.Owner, 'Object should not have parent owner'); 182 | System.assertEquals(null, mockAccount.OwnerId, 'Object should not have parent ownerId'); 183 | } 184 | 185 | @isTest 186 | static void setParent_givenInvalidParent_shouldNotSetParent() { 187 | 188 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 189 | String invalidField = 'Invalid Field'; 190 | User testUser = new User(Id = MockSObjectBuilder.getMockId(User.getSObjectType()), LastName = TEST_NAME); 191 | 192 | Test.startTest(); 193 | Account mockAccount = (Account) mockAccountBuilder.setParent(invalidField, testUser).build(); 194 | Test.stopTest(); 195 | } 196 | 197 | @isTest 198 | static void build_givenParentField_shouldHaveParent() { 199 | 200 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 201 | String ownerField = FIELD_LABEL_OWNER; 202 | User testUser = new User(Id = MockSObjectBuilder.getMockId(User.getSObjectType()), LastName = TEST_NAME); 203 | 204 | Test.startTest(); 205 | Account mockAccount = (Account) mockAccountBuilder.setParent(ownerField, testUser).build(); 206 | Test.stopTest(); 207 | 208 | System.assertEquals(testUser.LastName, mockAccount.Owner.LastName, 'Object should have parent owner'); 209 | System.assertEquals(testUser.Id, mockAccount.OwnerId, 'Object should have owner Id'); 210 | } 211 | 212 | @isTest 213 | static void getSerializedSObject_givenEmptyObject_shouldReturnEmptyObject() { 214 | 215 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 216 | 217 | Test.startTest(); 218 | String accountJson = mockAccountBuilder.getSerializedSObject(); 219 | Test.stopTest(); 220 | 221 | System.assertEquals(JSON.serialize(new Account()), accountJson); 222 | } 223 | 224 | @isTest 225 | static void setField_givenField_shouldHaveField() { 226 | 227 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 228 | SObjectField nameField = Account.Name.getDescribe().getSObjectField(); 229 | 230 | Test.startTest(); 231 | mockAccountBuilder.setField(nameField, TEST_NAME); 232 | Test.stopTest(); 233 | 234 | System.assertEquals(TEST_NAME, mockAccountBuilder.getField(nameField), 'Field not found in builder.'); 235 | } 236 | 237 | @isTest 238 | static void setField_givenFieldString_shouldHaveField() { 239 | 240 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 241 | String nameField = FIELD_LABEL_NAME; 242 | 243 | Test.startTest(); 244 | mockAccountBuilder.setField(nameField, TEST_NAME); 245 | Test.stopTest(); 246 | 247 | System.assertEquals(TEST_NAME, mockAccountBuilder.getField(nameField), 'Field not found in builder.'); 248 | } 249 | 250 | @isTest 251 | static void getMockId_givenNoPreviousIds_shouldReturnFirstId() { 252 | 253 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 254 | 255 | Test.startTest(); 256 | Id mockId = mockAccountBuilder.getMockId(); 257 | Test.stopTest(); 258 | 259 | System.assertEquals(mockAccountBuilder.mockType.getDescribe().getKeyPrefix() + '000000000001', mockId); 260 | } 261 | 262 | @isTest 263 | static void getMockId_givenOnePreviousId_shouldReturnSecondId() { 264 | 265 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 266 | mockAccountBuilder.getMockId(); 267 | 268 | Test.startTest(); 269 | Id mockId = mockAccountBuilder.getMockId(); 270 | Test.stopTest(); 271 | 272 | System.assertEquals(mockAccountBuilder.mockType.getDescribe().getKeyPrefix() + '000000000002', mockId); 273 | } 274 | 275 | @isTest 276 | static void getChildRelationship_givenChildRelationshipLabel_shouldReturnChildRelationship() { 277 | 278 | MockSObjectBuilder mockAccountBuilder = getTestMockAccountBuilder(); 279 | 280 | Test.startTest(); 281 | ChildRelationship relationship = mockAccountBuilder.getChildRelationship(CHILD_LABEL_OPPORTUNITIES); 282 | Test.stopTest(); 283 | 284 | System.assertEquals(CHILD_LABEL_OPPORTUNITIES, relationship.getRelationshipName()); 285 | } 286 | 287 | private static MockSObjectBuilder getTestMockAccountBuilder() { 288 | return new MockSObjectBuilder(Account.getSObjectType()); 289 | } 290 | 291 | private static MockSObjectBuilder getTestMockUserBuilder() { 292 | return new MockSObjectBuilder(User.getSObjectType()); 293 | } 294 | 295 | private static MockSObjectBuilder getTestMockOpportunityBuilder() { 296 | return new MockSObjectBuilder(Opportunity.getSObjectType()); 297 | } 298 | 299 | private static String getSerializedOpp(String name) { 300 | MockSObjectBuilder mockOpportunityBuilder = getTestMockOpportunityBuilder(); 301 | mockOpportunityBuilder.setField(FIELD_LABEL_NAME, name); 302 | return mockOpportunityBuilder.getSerializedSObject(); 303 | } 304 | } -------------------------------------------------------------------------------- /DML_Mocking/Query_Cases.cls: -------------------------------------------------------------------------------- 1 | public with sharing class Query_Cases { 2 | 3 | public List selectCases(set caseIds){ 4 | return [SELECT Id, Subject FROM Case WHERE Id IN: caseIds]; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DML_Mocking/README.md: -------------------------------------------------------------------------------- 1 | Video Tutorial Covering DML Mocking here: https://youtu.be/-esf8Q_Vp7U 2 | 3 | Universal Mocking Framework: https://github.com/surajp/universalmock 4 | -------------------------------------------------------------------------------- /DML_Mocking/UniversalMocker.cls: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | 3 | *** @author: Suraj Pillai 4 | *** @group: Test Class 5 | *** @date: 01/2020 6 | *** @description: A universal class for mocking in tests. Contains a method for setting the return value for any method. Another method returns the number of times a method was called 7 | 8 | */ 9 | @isTest 10 | public with sharing class UniversalMocker implements System.StubProvider { 11 | private final Map>> argumentsMap = new Map>>(); 12 | private final Type mockedClass; 13 | private final Map mocksMap = new Map(); 14 | private final Map callCountsMap = new Map(); 15 | 16 | private Boolean isInSetupMode = false; 17 | private Boolean isInAssertMode = false; 18 | private Boolean isInGetArgumentMode = false; 19 | 20 | private String currentMethodName; 21 | private String currentParamTypesString; 22 | private Integer expectedCallCount; 23 | private Integer forInvocationNumber = 0; 24 | 25 | private String INVALID_STATE_ERROR_MSG = 'Mocker object state is invalid for this operation. Please refer to the Readme'; 26 | private String KEY_DELIMITER = '||'; 27 | 28 | public enum Times { 29 | OR_LESS, 30 | OR_MORE, 31 | EXACTLY 32 | } 33 | 34 | private UniversalMocker(Type mockedClass) { 35 | this.mockedClass = mockedClass; 36 | } 37 | 38 | public static UniversalMocker mock(Type mockedClass) { 39 | return new UniversalMocker(mockedClass); 40 | } 41 | 42 | public Object createStub() { 43 | return Test.createStub(this.mockedClass, this); 44 | } 45 | 46 | private String getClassNameFromStubbedObjectName(Object stubbedObject) { 47 | String result = 'DateTime'; 48 | try { 49 | DateTime typeCheck = (DateTime) stubbedObject; 50 | } catch (System.TypeException te) { 51 | String message = te.getMessage().substringAfter('Invalid conversion from runtime type '); 52 | result = message.substringBefore(' to Datetime'); 53 | } 54 | return result; 55 | } 56 | 57 | private String getCurrentKey() { 58 | String className = this.mockedClass.getName(); 59 | String retVal = className + KEY_DELIMITER + this.currentMethodName; 60 | if (this.currentParamTypesString != null) { 61 | retVal += KEY_DELIMITER + this.currentParamTypesString; 62 | } 63 | return retVal.toLowerCase(); 64 | } 65 | 66 | private String getKey(String className, String methodName) { 67 | return (className + KEY_DELIMITER + methodName).toLowerCase(); 68 | } 69 | 70 | private String getKey(String className, String methodName, List paramTypes) { 71 | return (className + KEY_DELIMITER + methodName + KEY_DELIMITER + this.getParamTypesString(paramTypes)).toLowerCase(); 72 | } 73 | 74 | private String getParamTypesString(List paramTypes) { 75 | String[] classNames = new List{}; 76 | for (Type paramType : paramTypes) { 77 | classNames.add(paramType.getName()); 78 | } 79 | return String.join(classNames, '-'); 80 | } 81 | 82 | private void resetState() { 83 | this.currentParamTypesString = null; 84 | this.currentMethodName = null; 85 | this.isInAssertMode = false; 86 | this.isInSetupMode = false; 87 | this.isInGetArgumentMode = false; 88 | this.forInvocationNumber = 0; 89 | } 90 | 91 | private boolean isAnyModeActive() { 92 | return this.isInSetupMode || this.isInAssertMode || this.isInGetArgumentMode; 93 | } 94 | 95 | public void setMock(String stubbedMethodName, Object returnValue) { 96 | String key = this.getKey(this.mockedClass.getName(), stubbedMethodName); 97 | this.mocksMap.put(key, returnValue); 98 | this.callCountsMap.put(key, 0); 99 | } 100 | 101 | public UniversalMocker when(String stubbedMethodName) { 102 | if (this.isAnyModeActive()) { 103 | throw new InvalidOperationException(INVALID_STATE_ERROR_MSG); 104 | } 105 | this.isInSetupMode = true; 106 | this.currentMethodName = stubbedMethodName; 107 | return this; 108 | } 109 | 110 | public UniversalMocker withParamTypes(List paramTypes) { 111 | if (!this.isAnyModeActive()) { 112 | throw new InvalidOperationException('Invalid order of operations. Must specify method name to mock/assert first'); 113 | } 114 | this.currentParamTypesString = this.getParamTypesString(paramTypes); 115 | return this; 116 | } 117 | 118 | public UniversalMocker thenReturn(Object returnObject) { 119 | if (!this.isInSetupMode) { 120 | throw new InvalidOperationException('Invalid order of operations. Must specify method name to mock/assert first'); 121 | } 122 | String key = this.getCurrentKey(); 123 | this.mocksMap.put(key, returnObject); 124 | if (!this.callCountsMap.containsKey(key)) { 125 | this.callCountsMap.put(key, 0); 126 | } 127 | this.resetState(); 128 | return this; 129 | } 130 | 131 | public UniversalMocker thenThrow(Exception exceptionToThrow) { 132 | return this.thenReturn(exceptionToThrow); 133 | } 134 | 135 | private String determineKeyToUseForCurrentStubbedMethod(Object stubbedObject, String stubbedMethodName, List listOfParamTypes) { 136 | String mockedClass = this.getClassNameFromStubbedObjectName(stubbedObject); 137 | String keyWithoutParamTypes = this.getKey(mockedClass, stubbedMethodName); 138 | String keyWithParamTypes = this.getKey(mockedClass, stubbedMethodName, listOfParamTypes); 139 | return this.callCountsMap.containsKey(keyWithParamTypes) ? keyWithParamTypes : keyWithoutParamTypes; 140 | } 141 | 142 | private void incrementCallCount(String key) { 143 | Integer count = this.callCountsMap.containsKey(key) ? this.callCountsMap.get(key) : 0; 144 | this.callCountsMap.put(key, count + 1); 145 | } 146 | 147 | private void saveArguments(List listOfParamNames, List listOfArgs, String key) { 148 | Map currentArgsMap = new Map(); 149 | if (!this.argumentsMap.containsKey(key)) { 150 | this.argumentsMap.put(key, new List>{ currentArgsMap }); 151 | } else { 152 | this.argumentsMap.get(key).add(currentArgsMap); 153 | } 154 | 155 | for (Integer i = 0; i < listOfParamNames.size(); i++) { 156 | currentArgsMap.put(listOfParamNames[i].toLowerCase(), listOfArgs[i]); 157 | } 158 | } 159 | 160 | public Object handleMethodCall( 161 | Object stubbedObject, 162 | String stubbedMethodName, 163 | Type returnType, //currently unused 164 | List listOfParamTypes, 165 | List listOfParamNames, 166 | List listOfArgs 167 | ) { 168 | if (this.isAnyModeActive()) { 169 | throw new InvalidOperationException(INVALID_STATE_ERROR_MSG); 170 | } 171 | String keyInUse = this.determineKeyToUseForCurrentStubbedMethod(stubbedObject, stubbedMethodName, listOfParamTypes); 172 | this.incrementCallCount(keyInUse); 173 | this.saveArguments(listOfParamNames, listOfArgs, keyInUse); 174 | 175 | Object returnValue = this.mocksMap.get(keyInUse); 176 | if (returnValue instanceof Exception) { 177 | throw (Exception) returnValue; 178 | } 179 | return returnValue; 180 | } 181 | 182 | public UniversalMocker assertThat() { 183 | if (this.isAnyModeActive()) { 184 | throw new InvalidOperationException(INVALID_STATE_ERROR_MSG); 185 | } 186 | this.isInAssertMode = true; 187 | return this; 188 | } 189 | 190 | public UniversalMocker method(String methodName) { 191 | if (!this.isInAssertMode) { 192 | throw new InvalidOperationException('Invalid order of operations. Method called without calling assertThat first'); 193 | } 194 | this.currentMethodName = methodName; 195 | return this; 196 | } 197 | 198 | public void wasCalled(Integer expectedCallCount, Times assertTypeValue) { 199 | if (!this.isInAssertMode) { 200 | throw new InvalidOperationException('Invalid order of operations. Method called without calling assertThat first'); 201 | } 202 | this.expectedCallCount = expectedCallCount; 203 | String currentKey = this.getCurrentKey(); 204 | Integer actualCallCount = this.callCountsMap.get(currentKey); 205 | String methodName = this.currentMethodName; 206 | this.resetState(); 207 | switch on assertTypeValue { 208 | when OR_LESS { 209 | system.assert(this.expectedCallCount >= actualCallCount, this.getMethodCallCountAssertMessage(methodName, 'less than or equal')); 210 | } 211 | when OR_MORE { 212 | system.assert(this.expectedCallCount <= actualCallCount, this.getMethodCallCountAssertMessage(methodName, 'more than or equal')); 213 | } 214 | when else { 215 | system.assertEquals(this.expectedCallCount, actualCallCount, this.getMethodCallCountAssertMessage(methodName, 'equal')); 216 | } 217 | } 218 | } 219 | 220 | public void wasNeverCalled() { 221 | if (!this.isInAssertMode) { 222 | throw new InvalidOperationException('Invalid order of operations. Method called without calling assertThat first'); 223 | } 224 | String currentKey = this.getCurrentKey(); 225 | Integer actualCallCount = this.callCountsMap.get(currentKey); 226 | String methodName = this.currentMethodName; 227 | this.resetState(); 228 | if (actualCallCount != null) { 229 | this.expectedCallCount = 0; 230 | system.assertEquals(this.expectedCallCount, actualCallCount, String.format('Method {0} was called 1 or more times', new List{ methodName })); 231 | } 232 | } 233 | 234 | private String getMethodCallCountAssertMessage(String methodName, String comparison) { 235 | return String.format('Expected call count for method {0} is not {1} to the actual count', new List{ methodName, comparison }); 236 | } 237 | 238 | public UniversalMocker forMethod(String stubbedMethodName) { 239 | if (this.isAnyModeActive()) { 240 | throw new InvalidOperationException(INVALID_STATE_ERROR_MSG); 241 | } 242 | this.isInGetArgumentMode = true; 243 | this.currentMethodName = stubbedMethodName; 244 | return this; 245 | } 246 | 247 | public UniversalMocker andInvocationNumber(Integer invocation) { 248 | if (!this.isInGetArgumentMode) { 249 | throw new InvalidOperationException('Invalid order of operations. Method called without calling \'forMethod\' first'); 250 | } 251 | this.forInvocationNumber = invocation; 252 | return this; 253 | } 254 | 255 | public Object getValueOf(String paramName) { 256 | if (!this.isInGetArgumentMode) { 257 | throw new InvalidOperationException('Invalid order of operations. Method called without calling \'forMethod\' first'); 258 | } 259 | String theKey = this.getCurrentKey(); 260 | Map paramsMap = argumentsMap.get(theKey).get(this.forInvocationNumber); 261 | if (!paramsMap.containsKey(paramName.toLowerCase())) { 262 | throw new IllegalArgumentException(String.format('Param name {0} not found for the method {1}', new List{ paramName, this.currentMethodName })); 263 | } 264 | Object returnValue = paramsMap.get(paramName.toLowerCase()); 265 | this.resetState(); 266 | return returnValue; 267 | } 268 | 269 | public Object getArgumentsMap() { 270 | if (!this.isInGetArgumentMode) { 271 | throw new InvalidOperationException('Invalid order of operations. Method called without calling \'forMethod\' first'); 272 | } 273 | String theKey = this.getCurrentKey(); 274 | Map returnValue = this.argumentsMap.get(theKey).get(this.forInvocationNumber); 275 | this.resetState(); 276 | return returnValue; 277 | } 278 | 279 | public class InvalidOperationException extends Exception { 280 | } 281 | } -------------------------------------------------------------------------------- /LWC_Data_Attributes/README.md: -------------------------------------------------------------------------------- 1 | Tutorial video covering how to use data attributes for LWC's here: https://youtu.be/UoHRO-HHnG4 2 | -------------------------------------------------------------------------------- /LWC_Data_Attributes/lwcDataAttributes/lwcDataAttributes.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LWC_Data_Attributes/lwcDataAttributes/lwcDataAttributes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 7/11/2021. 3 | */ 4 | 5 | import {LightningElement} from 'lwc'; 6 | 7 | export default class LwcDataAttributes extends LightningElement { 8 | getDataAttributes(event){ 9 | console.log('This is the data set ::: ' + JSON.stringify(event.target.dataset)); 10 | console.log('This is the data set turtle ::: ' + JSON.stringify(event.target.dataset.turtle)); 11 | } 12 | } -------------------------------------------------------------------------------- /LWC_Data_Attributes/lwcDataAttributes/lwcDataAttributes.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51.0 4 | Lwc Data Attributes 5 | true 6 | Lwc Data Attributes 7 | 8 | lightning__AppPage 9 | 10 | 11 | -------------------------------------------------------------------------------- /LWC_List_View_Button/Contact.object: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | List_View_App_Page 5 | online 6 | massActionButton 7 | UTF-8 8 | url 9 | List View App Page 10 | replace 11 | false 12 | true 13 | /lightning/n/List_View_App_Page 14 | 15 | 16 | List_View_Button_Flow 17 | online 18 | massActionButton 19 | UTF-8 20 | url 21 | List View Button Flow 22 | replace 23 | false 24 | true 25 | /flow/List_View_Button_Flow 26 | 27 | 28 | List_View_Tab_Button 29 | online 30 | massActionButton 31 | UTF-8 32 | url 33 | List View Tab Button 34 | replace 35 | false 36 | true 37 | /lightning/n/List_View_Button 38 | 39 | 40 | -------------------------------------------------------------------------------- /LWC_List_View_Button/List_View_App_Page.flexipage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | listViewButton 7 | 8 | 9 | main 10 | Region 11 | 12 | List View App Page 13 | 16 | AppPage 17 | 18 | -------------------------------------------------------------------------------- /LWC_List_View_Button/List_View_App_Page.tab: -------------------------------------------------------------------------------- 1 | 2 | 3 | Created by Lightning App Builder 4 | List_View_App_Page 5 | 6 | Custom49: CD/DVD 7 | 8 | -------------------------------------------------------------------------------- /LWC_List_View_Button/List_View_Button_Flow.flow: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | List View Button Flow {!$Flow.CurrentDateTime} 5 | 6 | 7 | BuilderType 8 | 9 | LightningFlowBuilder 10 | 11 | 12 | 13 | CanvasMode 14 | 15 | FREE_FORM_CANVAS 16 | 17 | 18 | 19 | OriginBuilderType 20 | 21 | LightningFlowBuilder 22 | 23 | 24 | Flow 25 | 26 | ListViewButtonScreen 27 | 28 | 548 29 | 68 30 | true 31 | true 32 | true 33 | 34 | ListViewButton 35 | c:listViewButton 36 | ComponentInstance 37 | 38 | listViewIds 39 | 40 | ids 41 | 42 | 43 | UseStoredValues 44 | true 45 | true 46 | 47 | false 48 | false 49 | 50 | 51 | 50 52 | 50 53 | 54 | ListViewButtonScreen 55 | 56 | 57 | Active 58 | 59 | ids 60 | String 61 | true 62 | true 63 | false 64 | 65 | 66 | -------------------------------------------------------------------------------- /LWC_List_View_Button/List_View_Button_Flow.flowDefinition: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | -------------------------------------------------------------------------------- /LWC_List_View_Button/List_View_Tab.tab: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | listViewButtonWithIds 5 | Custom51: Apple 6 | 7 | -------------------------------------------------------------------------------- /LWC_List_View_Button/README.md: -------------------------------------------------------------------------------- 1 | LWC List View Button Tutorial Video: https://youtu.be/oPTQ-Il1eFE 2 | -------------------------------------------------------------------------------- /LWC_List_View_Button/listViewButton/listViewButton.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LWC_List_View_Button/listViewButton/listViewButton.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 7/20/2021. 3 | */ 4 | 5 | import {LightningElement, api} from 'lwc'; 6 | 7 | export default class ListViewButton extends LightningElement { 8 | @api listViewIds; 9 | 10 | close(){ 11 | setTimeout( 12 | function() { 13 | window.history.back(); 14 | }, 15 | 1000 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /LWC_List_View_Button/listViewButton/listViewButton.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51.0 4 | List View Button 5 | true 6 | List View Button 7 | 8 | lightning__AppPage 9 | lightning__Tab 10 | lightning__FlowScreen 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Nebula_Logger/Log_Page.flexipage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | collapsed 8 | false 9 | 10 | 11 | enableActionsConfiguration 12 | false 13 | 14 | 15 | enableActionsInNative 16 | false 17 | 18 | 19 | hideChatterActions 20 | false 21 | 22 | 23 | numVisibleActions 24 | 3 25 | 26 | force:highlightsPanel 27 | 28 | 29 | header 30 | Region 31 | 32 | 33 | 34 | 35 | force:detailPanel 36 | 37 | 38 | Facet-d8d1464e-42b5-4917-81d0-2faff9eba298 39 | Facet 40 | 41 | 42 | 43 | 44 | 45 | parentFieldApiName 46 | Log__c.Id 47 | 48 | 49 | relatedListApiName 50 | LogEntries__r 51 | 52 | 53 | relatedListComponentOverride 54 | NONE 55 | 56 | 57 | rowsToDisplay 58 | 10 59 | 60 | 61 | showActionBar 62 | true 63 | 64 | force:relatedListSingleContainer 65 | 66 | 67 | Facet-58de58f4-3f74-4422-adce-06cb8700d73a 68 | Facet 69 | 70 | 71 | 72 | 73 | 74 | parentFieldApiName 75 | Log__c.Id 76 | 77 | 78 | relatedListApiName 79 | ChildLogs__r 80 | 81 | 82 | relatedListComponentOverride 83 | NONE 84 | 85 | 86 | rowsToDisplay 87 | 10 88 | 89 | 90 | showActionBar 91 | true 92 | 93 | force:relatedListSingleContainer 94 | 95 | 96 | Facet-5838cfea-198c-4531-b1a4-ff5500bca6ae 97 | Facet 98 | 99 | 100 | 101 | 102 | 103 | numberOfTopicsToDisplay 104 | 10 105 | 106 | 107 | searchHint 108 | Type a topic name and press Enter. 109 | 110 | 111 | title 112 | Topics 113 | 114 | forceChatter:topicsOnRecordWrapper 115 | 116 | 117 | Facet-x6q5qdiuef 118 | Facet 119 | 120 | 121 | 122 | 123 | 124 | body 125 | Facet-d8d1464e-42b5-4917-81d0-2faff9eba298 126 | 127 | 128 | title 129 | Standard.Tab.detail 130 | 131 | flexipage:tab 132 | 133 | 134 | 135 | 136 | 137 | active 138 | true 139 | 140 | 141 | body 142 | Facet-58de58f4-3f74-4422-adce-06cb8700d73a 143 | 144 | 145 | title 146 | Log Entries 147 | 148 | flexipage:tab 149 | 150 | 151 | 152 | 153 | 154 | body 155 | Facet-5838cfea-198c-4531-b1a4-ff5500bca6ae 156 | 157 | 158 | title 159 | Child Logs 160 | 161 | flexipage:tab 162 | 163 | 164 | 165 | 166 | 167 | body 168 | Facet-x6q5qdiuef 169 | 170 | 171 | title 172 | Topics 173 | 174 | flexipage:tab 175 | 176 | 177 | Facet-f73c09c4-f2d6-4176-beed-9357d93a6bc5 178 | Facet 179 | 180 | 181 | 182 | 183 | 184 | tabs 185 | Facet-f73c09c4-f2d6-4176-beed-9357d93a6bc5 186 | 187 | flexipage:tabset 188 | 189 | 190 | main 191 | Region 192 | 193 | Log Page 194 | Log__c 195 | 202 | RecordPage 203 | 204 | -------------------------------------------------------------------------------- /Nebula_Logger/NebulaComponent_Controller.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 7/24/2021. 3 | */ 4 | 5 | public with sharing class NebulaComponent_Controller 6 | { 7 | private static String nebulaId; 8 | 9 | @AuraEnabled 10 | public static void setupCache(){ 11 | Cache.Session.put('IsNewLoad', true); 12 | } 13 | 14 | @AuraEnabled 15 | public static void logWarning(Id recordId){ 16 | System.debug('This is a warning debug log'); 17 | Logger.warn('This is a warning debug log'); 18 | LogEntryEventBuilder logEvent = Logger.warn('This is a wanrning debug log for a record', recordId); 19 | Logger.error('This is an error', recordId); 20 | logEvent.setTopics(new List{'WARNING'}); 21 | Logger.saveLog(); 22 | //saveNebulaLog(); 23 | } 24 | 25 | @AuraEnabled 26 | public static void logDebug(Id recordId){ 27 | Logger.debug('This is a regular debug log'); 28 | LogEntryEventBuilder logEvent = Logger.debug('This is a regular debug log for a record'); 29 | logEvent.setRecord(recordId); 30 | logEvent.setTopics(new List{'DANGER'}); 31 | Logger.saveLog(); 32 | //saveNebulaLog(); 33 | } 34 | 35 | private static void saveNebulaLog(){ 36 | Boolean isNewLoad = (Boolean)Cache.Session.get('IsNewLoad'); 37 | if(isNewLoad){ 38 | nebulaId = Logger.getTransactionId(); 39 | Cache.Session.put('nebulaId', Logger.getTransactionId()); 40 | Cache.Session.put('IsNewLoad', false); 41 | Logger.saveLog(); 42 | saveAccount(); 43 | } 44 | else{ 45 | String parentNebulaId = (String)Cache.Session.get('nebulaId'); 46 | Logger.debug('This is the static id : ' + nebulaId + ' : This is the cached Id : ' + parentNebulaId); 47 | Logger.setParentLogTransactionId(parentNebulaId); 48 | Logger.saveLog(); 49 | saveAccount(); 50 | } 51 | } 52 | 53 | private static void saveAccount(){ 54 | Account acct = new Account(Name='Kewl Account'); 55 | insert acct; 56 | } 57 | } -------------------------------------------------------------------------------- /Nebula_Logger/README.md: -------------------------------------------------------------------------------- 1 | Link to Video Tutorial: https://youtu.be/AB4RhfRVvyk 2 | -------------------------------------------------------------------------------- /Nebula_Logger/nebulaComponent/nebulaComponent.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 7/24/2021. 3 | */ 4 | -------------------------------------------------------------------------------- /Nebula_Logger/nebulaComponent/nebulaComponent.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Nebula_Logger/nebulaComponent/nebulaComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 7/24/2021. 3 | */ 4 | 5 | import {LightningElement, api} from 'lwc'; 6 | import setNewSession from '@salesforce/apex/NebulaComponent_Controller.setupCache'; 7 | import debugLog from '@salesforce/apex/NebulaComponent_Controller.logDebug'; 8 | import warnLog from '@salesforce/apex/NebulaComponent_Controller.logWarning'; 9 | 10 | export default class NebulaComponent extends LightningElement { 11 | 12 | @api recordId 13 | 14 | connectedCallback() { 15 | setNewSession().then(returnVal =>{ 16 | console.log('Success'); 17 | }).catch(error =>{ 18 | console.log('Error'); 19 | }); 20 | } 21 | 22 | logDebug(){ 23 | debugLog({"recordId": this.recordId}).then(returnVal =>{ 24 | console.log('Success'); 25 | }).catch(error =>{ 26 | console.log('Error'); 27 | }); 28 | } 29 | 30 | logWarning(){ 31 | warnLog({"recordId": this.recordId}).then(returnVal =>{ 32 | console.log('Success'); 33 | }).catch(error =>{ 34 | console.log('Error'); 35 | }); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /Nebula_Logger/nebulaComponent/nebulaComponent.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51.0 4 | NebulaLogger Example 5 | true 6 | Nebula Component 7 | 8 | lightning__RecordPage 9 | 10 | 11 | -------------------------------------------------------------------------------- /POST_Request/AccountWrapper.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 2/16/2021. 3 | */ 4 | 5 | public with sharing class AccountWrapper 6 | { 7 | public Account acct; 8 | } -------------------------------------------------------------------------------- /POST_Request/POST_Example.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 2/16/2021. 3 | */ 4 | 5 | public with sharing class POST_Example 6 | { 7 | public void sendPOSTReq(){ 8 | HttpRequest req = new HttpRequest(); 9 | req.setEndpoint('callout:SalesforcePOST'); 10 | req.setMethod('POST'); 11 | req.setHeader('Content-Type', 'application/json'); 12 | String acctToPass = JSON.serialize(wrapAccount(generateAccount())); 13 | System.debug('This is the account JSON ::: ' + acctToPass); 14 | req.setBody(acctToPass); 15 | Http http = new Http(); 16 | HttpResponse res = http.send(req); 17 | System.debug(res.getBody()); 18 | } 19 | 20 | private Account generateAccount(){ 21 | Account acct = new Account(); 22 | acct.Name = 'Hi'; 23 | acct.Phone = '8162221111'; 24 | return acct; 25 | } 26 | 27 | private AccountWrapper wrapAccount(Account acct){ 28 | AccountWrapper wrapper = new AccountWrapper(); 29 | wrapper.acct = acct; 30 | return wrapper; 31 | } 32 | } -------------------------------------------------------------------------------- /POST_Request/README.md: -------------------------------------------------------------------------------- 1 | Post Requests Video Tutorial: https://youtu.be/MYk0ir_tIDA 2 | -------------------------------------------------------------------------------- /POST_Request/REST_Service.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 2/16/2021. 3 | */ 4 | @RestResource(UrlMapping='/AccountDML/*') 5 | global with sharing class REST_Service 6 | { 7 | @HttpPost 8 | global static String insertAccount(Account acct){ 9 | insert acct; 10 | return acct.Id; 11 | } 12 | } -------------------------------------------------------------------------------- /Platform_Cache/ContactPlatformCacheData.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 9/21/2021. 3 | */ 4 | 5 | public with sharing class ContactPlatformCacheData implements Cache.CacheBuilder 6 | { 7 | public Object doLoad(String contactId){ 8 | Contact cont = [SELECT Id, FirstName, LastName, AccountId FROM Contact WHERE Id = :contactId LIMIT 1]; 9 | return cont; 10 | } 11 | } -------------------------------------------------------------------------------- /Platform_Cache/PartitionCacheExampleController.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 9/21/2021. 3 | */ 4 | 5 | public with sharing class PartitionCacheExampleController 6 | { 7 | @AuraEnabled 8 | public static void storeDataController(){ 9 | Contact cont = [SELECT Id, FirstName, LastName, AccountId FROM Contact WHERE Id = '0034R00003oGP8PQAW' LIMIT 1]; 10 | Cache.Session.put('ContactFound', cont); 11 | Cache.Session.put('local.TacoCat.ContactFound', cont); 12 | } 13 | 14 | @AuraEnabled 15 | public static Contact retrieveDataCacheController(){ 16 | return (Contact) Cache.Session.get('ContactFound'); 17 | } 18 | 19 | @AuraEnabled 20 | public static Contact retrieveDataSOQLController(){ 21 | return [SELECT Id, FirstName, LastName, AccountId FROM Contact WHERE Id = '0034R00003oGP8PQAW' LIMIT 1]; 22 | } 23 | } -------------------------------------------------------------------------------- /Platform_Cache/README.md: -------------------------------------------------------------------------------- 1 | Tutorial Video Link: https://youtu.be/IGlPBBFJUwQ 2 | -------------------------------------------------------------------------------- /Platform_Cache/partitionCacheExample/partitionCacheExample.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Platform_Cache/partitionCacheExample/partitionCacheExample.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 9/21/2021. 3 | */ 4 | 5 | import {LightningElement} from 'lwc'; 6 | import retrieveDataCacheController from '@salesforce/apex/PartitionCacheExampleController.retrieveDataCacheController'; 7 | import retrieveDataSOQLController from '@salesforce/apex/PartitionCacheExampleController.retrieveDataSOQLController'; 8 | import storeDataController from '@salesforce/apex/PartitionCacheExampleController.storeDataController'; 9 | 10 | export default class PartitionCacheExample extends LightningElement { 11 | 12 | connectedCallback() { 13 | this.storeData() 14 | } 15 | 16 | storeData(){ 17 | storeDataController().then(()=>{ 18 | console.log('We stored the data'); 19 | }) 20 | } 21 | 22 | 23 | retrieveDataCache(){ 24 | retrieveDataCacheController().then(data =>{ 25 | console.log('This is the data ::: ' + data); 26 | }) 27 | } 28 | 29 | retrieveDataSOQL(){ 30 | retrieveDataSOQLController().then(data =>{ 31 | console.log('This is the data ::: ' + data); 32 | }) 33 | } 34 | } -------------------------------------------------------------------------------- /Platform_Cache/partitionCacheExample/partitionCacheExample.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51.0 4 | Partition Cache Example 5 | true 6 | Partition Cache Example 7 | 8 | lightning__RecordPage 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutorials 2 | This houses all of my youtube video tutorials 3 | 4 | *** 5 | 6 | ### How to Say Thanks 7 | 8 | If you enjoy this repo and would like to say thank you, feel free to [send a donation here](https://www.paypal.com/donate?business=RNHEF8ZWKKLDG¤cy_code=USD)! But no pressure, I really just do this for fun! 9 | -------------------------------------------------------------------------------- /REST_Resource/README.md: -------------------------------------------------------------------------------- 1 | Video Tutorial: https://youtu.be/u3EjK2OmuXI 2 | -------------------------------------------------------------------------------- /REST_Resource/Users_Rest_Resource.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 8/31/2021. 3 | */ 4 | @RestResource(UrlMapping='/Users/*') 5 | global with sharing class Users_Rest_Resource 6 | { 7 | @HttpGet 8 | global static List getUsers(){ 9 | RestRequest req = RestContext.request; 10 | System.debug(req.params); 11 | List users = [SELECT Id, FirstName, LastName FROM User LIMIT 10]; 12 | return users; 13 | } 14 | 15 | @HttpPost 16 | global static String insertUser(User newUser){ 17 | System.debug('This is a user ::: ' + newUser); 18 | insert newUser; 19 | return newUser.Id; 20 | } 21 | } -------------------------------------------------------------------------------- /The_Complete_Guide_To_Apex_Triggers/Accounts.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 5/1/2021. 3 | */ 4 | 5 | public inherited sharing class Accounts extends DomainObject 6 | { 7 | List accounts = new List(); 8 | Map oldAccountMap = new Map(); 9 | 10 | public Accounts(List acctRecords){ 11 | super(acctRecords); 12 | super.setFilter(new Set{'Kewl_Record_Type'}, accounts, oldAccountMap); 13 | } 14 | 15 | public class Constructor implements fflib_SObjectDomain.IConstructable{ 16 | public fflib_SObjectDomain construct(List sObjectList){ 17 | return new Accounts(sObjectList); 18 | } 19 | } 20 | 21 | public override void onBeforeUpdate(Map oldRecords){ 22 | if(accounts.isEmpty() ){ 23 | System.debug('You had no matching records'); 24 | return; 25 | } 26 | System.debug('You had matching records'); 27 | } 28 | } -------------------------------------------------------------------------------- /The_Complete_Guide_To_Apex_Triggers/Accounts.trigger: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 5/1/2021. 3 | */ 4 | 5 | trigger Accounts on Account (before insert, after insert, before update) 6 | { 7 | fflib_SObjectDomain.triggerHandler(Accounts.class); 8 | fflib_SObjectDomain.triggerHandler(Accounts_Marketing.class); 9 | } -------------------------------------------------------------------------------- /The_Complete_Guide_To_Apex_Triggers/Accounts_Marketing.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 9/6/2022. 3 | */ 4 | 5 | public with sharing class Accounts_Marketing extends DomainObject 6 | { 7 | List accounts = new List(); 8 | Map oldAccountMap = new Map(); 9 | 10 | public Accounts_Marketing(List acctRecords){ 11 | super(acctRecords); 12 | super.setFilter(new Set{'NonKewl_Record_Type'}, accounts, oldAccountMap); 13 | } 14 | 15 | public class Constructor implements fflib_SObjectDomain.IConstructable{ 16 | public fflib_SObjectDomain construct(List sObjectList){ 17 | return new Accounts_Marketing(sObjectList); 18 | } 19 | } 20 | 21 | public override void onBeforeUpdate(Map oldRecords){ 22 | if(accounts.isEmpty() ){ 23 | System.debug('You had no matching records non kewl'); 24 | return; 25 | } 26 | System.debug('You had matching records non kewl'); 27 | } 28 | } -------------------------------------------------------------------------------- /The_Complete_Guide_To_Apex_Triggers/Case_Trigger.trigger: -------------------------------------------------------------------------------- 1 | trigger Case_Trigger on Case (before insert) 2 | { 3 | new Case_Trigger_Handler().run(); 4 | } -------------------------------------------------------------------------------- /The_Complete_Guide_To_Apex_Triggers/Case_Trigger_Handler.cls: -------------------------------------------------------------------------------- 1 | public with sharing class Case_Trigger_Handler extends TriggerHandler 2 | { 3 | public override void beforeInsert() 4 | { 5 | Case_Utility.updateCaseSubject(trigger.new); 6 | Case_Utility.createCase(); 7 | } 8 | } -------------------------------------------------------------------------------- /The_Complete_Guide_To_Apex_Triggers/Cases.trigger: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 4/6/2021. 3 | */ 4 | 5 | /** 6 | * Created by gerry on 3/21/2021. 7 | */ 8 | 9 | trigger Cases on Case (before insert, before update, after insert, after update) 10 | { 11 | fflib_SObjectDomain.triggerHandler(Cases.class); 12 | } -------------------------------------------------------------------------------- /The_Complete_Guide_To_Apex_Triggers/ContactTrigger.trigger: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 9/6/2022. 3 | */ 4 | 5 | trigger ContactTrigger on Contact (before update, after update) 6 | { 7 | if(Trigger.isAfter && Trigger.isInsert){ 8 | System.debug('This ran yea!!!'); 9 | } 10 | 11 | if(Trigger.isBefore && Trigger.isUpdate){ 12 | kewlMethod(); 13 | } 14 | 15 | public static void kewlMethod(){ 16 | for(Contact cont: trigger.new){ 17 | Contact oldContact = Trigger.oldMap.get(cont.Id); 18 | if(cont.Birthdate != oldContact.Birthdate) 19 | { 20 | cont.addError('You can\'t change the birthday sorry bro'); 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /The_Complete_Guide_To_Apex_Triggers/DomainObject.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 9/6/2022. 3 | */ 4 | 5 | public with sharing virtual class DomainObject extends fflib_SObjectDomain 6 | { 7 | public DomainObject(List sObjectList) { 8 | super(sObjectList); 9 | } 10 | 11 | public class Constructor implements fflib_SObjectDomain.IConstructable { 12 | public fflib_SObjectDomain construct(List sObjectList) { 13 | return new DomainObject(sObjectList); 14 | } 15 | } 16 | 17 | public void setFilter(Set recordTypeDevNames, List returnListOverride, Map returnMapOverride) { 18 | if (records.isEmpty()) return; 19 | Set recordTypeIdFilters = getFilteredRecordTypes(recordTypeDevNames); 20 | if (records != null) { 21 | for (sObject o : records) { 22 | Object obj = o.get('RecordTypeId'); 23 | if(obj ==null) continue; 24 | String sObjRecType = obj.ToString(); 25 | if (recordTypeIdFilters.contains(sObjRecType)) { 26 | returnListOverride.add(o); 27 | } 28 | } 29 | } 30 | if (existingRecords != null) { 31 | for (sObject o : existingRecords.values()) { 32 | Object obj = o.get('RecordTypeId'); 33 | if(obj ==null) continue; 34 | String sObjRecType = obj.ToString(); 35 | if (recordTypeIdFilters.contains(sObjRecType)) { 36 | returnMapOverride.put(o.Id, o); 37 | } 38 | } 39 | } 40 | } 41 | 42 | private Set getFilteredRecordTypes(Set recordTypeDevNames) 43 | { 44 | Set recordTypeIdFilters = new Set(); 45 | new Set(); 46 | if (records != null && records.size() > 0) { 47 | Map recordTypeInfoMap = (records[0].getSObjectType().getDescribe()).getRecordTypeInfosByDeveloperName(); 48 | for (String recordTypeDevName : recordTypeDevNames) { 49 | Id recordTypeId = recordTypeInfoMap.get(recordTypeDevName).getRecordTypeId(); 50 | if (!recordTypeIdFilters.contains(recordTypeId)) 51 | recordTypeIdFilters.add(recordTypeId); 52 | } 53 | } 54 | if (existingRecords != null) { 55 | List oldValuesList = new List(existingRecords.values()); // gotta get first one and cant from map without a key or iterating 56 | Map recordTypeInfoMap = (oldValuesList[0].getSObjectType().getDescribe()).getRecordTypeInfosByDeveloperName(); 57 | for (String recordTypeDevName : recordTypeDevNames) { 58 | Id recordTypeId = recordTypeInfoMap.get(recordTypeDevName).getRecordTypeId(); 59 | if (!recordTypeIdFilters.contains(recordTypeId)) 60 | recordTypeIdFilters.add(recordTypeId); 61 | } 62 | } 63 | return recordTypeIdFilters; 64 | } 65 | } -------------------------------------------------------------------------------- /The_Complete_Guide_To_Apex_Triggers/README.md: -------------------------------------------------------------------------------- 1 | ### The Complete Guide to Apex Triggers Tutorial Video 2 | 3 | These are the code files for the complete guide to apex triggers tutorial video located here: https://youtu.be/ebbj8RG5_bk 4 | -------------------------------------------------------------------------------- /The_Complete_Guide_To_SOQL_And_SOSL/ApexSOQLExample.cls: -------------------------------------------------------------------------------- 1 | public class ApexSOQLExample { 2 | public static void queryForAccounts(){ 3 | Integer minimumAnnualRevenue = 5000; 4 | Set accountTypes = new Set{'Customer - Direct', 'Customer - Channel'}; 5 | List acctList = [SELECT Id, Name, Type FROM Account WHERE Type IN :accountTypes]; 6 | Map acctMap = 7 | new Map([SELECT Id, Name, Type FROM Account WHERE Type IN :accountTypes]); 8 | System.debug('This is my list ::: ' + acctMap.values()); 9 | } 10 | 11 | public static void dynamicAccountQuery(){ 12 | Set accountTypes = new Set{'Customer - Direct', 'Customer - Channel'}; 13 | String queryString = 'SELECT Id, Name FROM Account WHERE Type IN :accountTypes'; 14 | List acctList = Database.query(queryString); 15 | System.debug('This is my list ::: ' + acctList); 16 | } 17 | 18 | public static void queryBenchmarkingExample(){ 19 | System.debug('CPU Time 1 ::: ' + Limits.getCpuTime()); 20 | List acctList = [SELECT Id, Name FROM Account WHERE Name = 'Taco']; 21 | System.debug('CPU Time 2 ::: ' + Limits.getCpuTime()); 22 | for(Account acct: acctList){ 23 | acct.Name = 'Bob'; 24 | } 25 | System.debug('CPU Time 3 ::: ' + Limits.getCpuTime()); 26 | } 27 | } -------------------------------------------------------------------------------- /The_Complete_Guide_To_SOQL_And_SOSL/ApexSOQLExample.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 56.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /The_Complete_Guide_To_SOQL_And_SOSL/ApexSOSLExample.cls: -------------------------------------------------------------------------------- 1 | public class ApexSOSLExample { 2 | public static void soslExample(){ 3 | List> soslResults = [FIND 'Taco' IN ALL FIELDS 4 | RETURNING Account(Id, Name), Contact(Id, Name)]; 5 | System.debug('Results ::: ' + soslResults); 6 | for(List objList: soslResults){ 7 | for(SObject obj: objList){ 8 | System.debug('This is our object ::: ' + obj); 9 | } 10 | } 11 | } 12 | 13 | public static void dynamicSoslExample(){ 14 | String queryString = 'FIND \'Taco\' IN ALL FIELDS RETURNING Account(Id, Name), Contact(Id, Name)'; 15 | List> soslResults = search.query(queryString); 16 | for(List objList: soslResults){ 17 | for(SObject obj: objList){ 18 | System.debug('This is our object ::: ' + obj); 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /The_Complete_Guide_To_SOQL_And_SOSL/ApexSOSLExample.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 56.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /Wrapper_Classes/README.md: -------------------------------------------------------------------------------- 1 | Video tutorial for Wrapper Classes: https://youtu.be/PVZOKm6E82Q 2 | -------------------------------------------------------------------------------- /Wrapper_Classes/Reddit_Post_Retriever.cls: -------------------------------------------------------------------------------- 1 | public with sharing class Reddit_Post_Retriever { 2 | private static final String REDDIT_URL = 'https://www.reddit.com/r/salesforce/hot.json?limit=1'; 3 | private static final String GET = 'GET'; 4 | 5 | public static List getRedditData(){ 6 | List redditData = new List(); 7 | 8 | Http httpCallout = new Http(); 9 | HttpRequest req = new HttpRequest(); 10 | req.setEndpoint(REDDIT_URL); 11 | req.setMethod(GET); 12 | 13 | HttpResponse redditResponse = httpCallout.send(req); 14 | Salesforce_Reddit redditPostResponse = Salesforce_Reddit.parse(redditResponse.getBody()); 15 | List redditPosts = redditPostResponse.data.children; 16 | 17 | for(Salesforce_Reddit.Children child: redditPosts){ 18 | redditData.add(child.data); 19 | } 20 | 21 | for(Salesforce_Reddit.Data data: redditData){ 22 | System.debug(data.author); 23 | System.debug(data.title); 24 | System.debug(data.url); 25 | } 26 | 27 | return redditData; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Wrapper_Classes/Salesforce_Reddit.cls: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by JSON2Apex http://json2apex.herokuapp.com/ 3 | // 4 | 5 | public class Salesforce_Reddit { 6 | 7 | @AuraEnabled 8 | public Post data; 9 | 10 | public class Post { 11 | @AuraEnabled 12 | public List children; 13 | 14 | } 15 | 16 | public class Data { 17 | @AuraEnabled 18 | public String author; 19 | @AuraEnabled 20 | public String title; 21 | @AuraEnabled 22 | public String url; 23 | } 24 | 25 | public class Children { 26 | 27 | public Data data; 28 | } 29 | 30 | public static Salesforce_Reddit parse(String json) { 31 | return (Salesforce_Reddit) System.JSON.deserialize(json, Salesforce_Reddit.class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lwc_pdf_generation/PdfGenerator.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 2/23/2021. 3 | */ 4 | 5 | public with sharing class PdfGenerator 6 | { 7 | @AuraEnabled 8 | public static List getContactsController(){ 9 | return [SELECT Id, FirstName, LastName FROM Contact LIMIT 1000]; 10 | } 11 | } -------------------------------------------------------------------------------- /lwc_pdf_generation/README.md: -------------------------------------------------------------------------------- 1 | Video tutorial on how to generate a pdf from an lwc: https://youtu.be/RZ5-AArzZaY 2 | -------------------------------------------------------------------------------- /lwc_pdf_generation/jspdfDemo/jspdfDemo.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /lwc_pdf_generation/jspdfDemo/jspdfDemo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gerry on 2/23/2021. 3 | */ 4 | 5 | import {LightningElement} from 'lwc'; 6 | import {loadScript} from "lightning/platformResourceLoader"; 7 | import JSPDF from '@salesforce/resourceUrl/jspdf'; 8 | import getContacts from '@salesforce/apex/PdfGenerator.getContactsController'; 9 | 10 | export default class JspdfDemo extends LightningElement { 11 | contactList = []; 12 | headers = this.createHeaders([ 13 | "Id", 14 | "FirstName", 15 | "LastName" 16 | ]); 17 | 18 | renderedCallback() { 19 | Promise.all([ 20 | loadScript(this, JSPDF) 21 | ]); 22 | } 23 | 24 | generatePdf(){ 25 | const { jsPDF } = window.jspdf; 26 | const doc = new jsPDF({ 27 | encryption: { 28 | userPassword: "user", 29 | ownerPassword: "owner", 30 | userPermissions: ["print", "modify", "copy", "annot-forms"] 31 | // try changing the user permissions granted 32 | } 33 | }); 34 | 35 | doc.text("Hi I'm Matt", 20, 20); 36 | doc.table(30, 30, this.contactList, this.headers, { autosize:true }); 37 | doc.save("demo.pdf"); 38 | } 39 | 40 | generateData(){ 41 | getContacts().then(result=>{ 42 | this.contactList = result; 43 | this.generatePdf(); 44 | }); 45 | } 46 | 47 | createHeaders(keys) { 48 | var result = []; 49 | for (var i = 0; i < keys.length; i += 1) { 50 | result.push({ 51 | id: keys[i], 52 | name: keys[i], 53 | prompt: keys[i], 54 | width: 65, 55 | align: "center", 56 | padding: 0 57 | }); 58 | } 59 | return result; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /lwc_pdf_generation/jspdfDemo/jspdfDemo.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 49.0 4 | Jspdf Demo 5 | true 6 | Jspdf Demo 7 | 8 | lightning__RecordPage 9 | 10 | 11 | -------------------------------------------------------------------------------- /lwc_utility_modules/README.md: -------------------------------------------------------------------------------- 1 | The video tutorial I made covering lwc module imports and exports can be viewed here: https://youtu.be/FL70NnD0XYI 2 | -------------------------------------------------------------------------------- /lwc_utility_modules/demo_component/demo_component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lwc_utility_modules/demo_component/demo_component.js: -------------------------------------------------------------------------------- 1 | import { LightningElement } from 'lwc'; 2 | 3 | //Importing in our javascript utility module. The import variable names (OPEN_STATUS, showError, util_class) 4 | //are the names of the exported variable, method and class in our util_module component 5 | import { OPEN_STATUS, showError, util_class} from 'c/util_module'; 6 | 7 | export default class Demo_component extends LightningElement 8 | { 9 | openStatus; 10 | returnedError; 11 | newStatus; 12 | 13 | //The connectedCallback method runs on component load 14 | connectedCallback() 15 | { 16 | //assigning our imported OPEN_STATUS value to our local openStatus variable 17 | this.openStatus = OPEN_STATUS; 18 | 19 | //assigning our imported showError methods return value to our local returnedError variable 20 | this.returnedError = showError('Tacos had an error'); 21 | 22 | //assigning our imported class's newStatus variable to our local newStatus variable 23 | this.newStatus = new util_class().newStatus; 24 | 25 | //Calling our imported class's showConsoleLog method 26 | new util_class().showConsoleLog(); 27 | } 28 | } -------------------------------------------------------------------------------- /lwc_utility_modules/demo_component/demo_component.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | false 5 | -------------------------------------------------------------------------------- /lwc_utility_modules/util_module/util_module.js: -------------------------------------------------------------------------------- 1 | 2 | //DISCLAIMER: There are many different ways available in ES6 JS to export variables, functions and classes 3 | //you can find out more about exports here: https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export 4 | 5 | //How to export a variable and make it available when importing your module 6 | //in other components 7 | export const OPEN_STATUS = 'Open'; 8 | 9 | //How to export a method and make it available when importing your module 10 | //in other components 11 | export function showError(error) { 12 | return 'This is an error ' + error; 13 | } 14 | 15 | //How to export a class and make it available when importing your module 16 | //in other components 17 | export class util_class { 18 | 19 | newStatus = 'New'; 20 | 21 | showConsoleLog(){ 22 | console.log('This is the console log ::: '); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /lwc_utility_modules/util_module/util_module.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | false 5 | -------------------------------------------------------------------------------- /lwc_word_document_generation/ContactGrabber.cls: -------------------------------------------------------------------------------- 1 | public with sharing class ContactGrabber { 2 | @AuraEnabled 3 | public static List getAllRelatedContacts(Id acctId){ 4 | return [SELECT Id, FirstName, LastName FROM Contact WHERE AccountId = :acctId]; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /lwc_word_document_generation/README.md: -------------------------------------------------------------------------------- 1 | Video tutorial for how to generate word documents from Lightning Web Components: https://youtu.be/VU0SnB5dC-g 2 | -------------------------------------------------------------------------------- /lwc_word_document_generation/contact_list_generator/contact_list_generator.css: -------------------------------------------------------------------------------- 1 | .hidden{ 2 | display: none; 3 | } 4 | 5 | .not_hidden{ 6 | display: block; 7 | } -------------------------------------------------------------------------------- /lwc_word_document_generation/contact_list_generator/contact_list_generator.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lwc_word_document_generation/contact_list_generator/contact_list_generator.js: -------------------------------------------------------------------------------- 1 | import { LightningElement, api } from 'lwc'; 2 | import {loadScript} from "lightning/platformResourceLoader"; 3 | import docxImport from "@salesforce/resourceUrl/docx"; 4 | import contactGrab from "@salesforce/apex/ContactGrabber.getAllRelatedContacts"; 5 | 6 | export default class Contact_list_generator extends LightningElement { 7 | 8 | @api recordId; 9 | downloadURL; 10 | _no_border = {top: {style: "none", size: 0, color: "FFFFFF"}, 11 | bottom: {style: "none", size: 0, color: "FFFFFF"}, 12 | left: {style: "none", size: 0, color: "FFFFFF"}, 13 | right: {style: "none", size: 0, color: "FFFFFF"}}; 14 | 15 | connectedCallback(){ 16 | Promise.all([loadScript(this, docxImport)]).then(() =>{ 17 | this.renderButtons(); 18 | }); 19 | } 20 | 21 | renderButtons(){ 22 | this.template.querySelector(".hidden").classList.add("not_hidden"); 23 | this.template.querySelector(".hidden").classList.remove("hidden"); 24 | } 25 | 26 | startDocumentGeneration(){ 27 | contactGrab({'acctId': this.recordId}).then(contacts=>{ 28 | this.buildDocument(contacts); 29 | }); 30 | } 31 | 32 | buildDocument(contactsPassed){ 33 | let document = new docx.Document(); 34 | let tableCells = []; 35 | tableCells.push(this.generateHeaderRow()); 36 | 37 | contactsPassed.forEach(contact => { 38 | tableCells.push(this.generateRow(contact)); 39 | }); 40 | 41 | this.generateTable(document, tableCells); 42 | this.generateDownloadLink(document); 43 | } 44 | 45 | generateHeaderRow(){ 46 | let tableHeaderRow = new docx.TableRow({ 47 | children:[ 48 | new docx.TableCell({ 49 | children: [new docx.Paragraph("First Name")], 50 | borders: this._no_border 51 | }), 52 | new docx.TableCell({ 53 | children: [new docx.Paragraph("Last Name")], 54 | borders: this._no_border 55 | }) 56 | ] 57 | }); 58 | 59 | return tableHeaderRow; 60 | } 61 | 62 | generateRow(contactPassed){ 63 | let tableRow = new docx.TableRow({ 64 | children: [ 65 | new docx.TableCell({ 66 | children: [new docx.Paragraph({children: [this.generateTextRun(contactPassed["FirstName"].toString())]})], 67 | borders: this._no_border 68 | }), 69 | new docx.TableCell({ 70 | children: [new docx.Paragraph({children: [this.generateTextRun(contactPassed["LastName"].toString())]})], 71 | borders: this._no_border 72 | }) 73 | ] 74 | }); 75 | 76 | return tableRow; 77 | } 78 | 79 | generateTextRun(cellString){ 80 | let textRun = new docx.TextRun({text: cellString, bold: true, size: 48, font: "Calibri"}); 81 | return textRun; 82 | } 83 | 84 | generateTable(documentPassed, tableCellsPassed){ 85 | let docTable = new docx.Table({ 86 | rows: tableCellsPassed 87 | }); 88 | 89 | documentPassed.addSection({ 90 | children: [docTable] 91 | }); 92 | } 93 | 94 | generateDownloadLink(documentPassed){ 95 | docx.Packer.toBase64String(documentPassed).then(textBlob =>{ 96 | this.downloadURL = 'data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,' + textBlob; 97 | this.template.querySelector(".slds-hide").classList.remove("slds-hide"); 98 | }); 99 | } 100 | } -------------------------------------------------------------------------------- /lwc_word_document_generation/contact_list_generator/contact_list_generator.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | true 5 | Contact List Document Generator 6 | Used to generate word documents 7 | 8 | lightning__RecordPage 9 | 10 | -------------------------------------------------------------------------------- /named_credentials/GitHubAuthCallout_NoCredential.cls: -------------------------------------------------------------------------------- 1 | public class GitHubAuthCallout_NoCredential { 2 | 3 | public static void callGitHub() 4 | { 5 | HttpRequest req = new HttpRequest(); 6 | req.setEndpoint('https://api.github.com/users/Coding-With-The-Force/repos'); 7 | req.setMethod('GET'); 8 | req.setHeader('Authorization', 'token [your_github_token]'); 9 | req.setHeader('Accept', 'application/json'); 10 | req.setHeader('Content-Type', 'application/json'); 11 | Http http = new Http(); 12 | HTTPResponse res = http.send(req); 13 | System.debug(res.getBody()); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /named_credentials/GitHubAuthCallout_NoCredential.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /named_credentials/GitHubPassTokenCallout.cls: -------------------------------------------------------------------------------- 1 | public class GitHubPassTokenCallout { 2 | public static void callGitHub() 3 | { 4 | HttpRequest req = new HttpRequest(); 5 | req.setEndpoint('callout:GitHub_Authorization_Flow/users/Coding-With-The-Force/repos'); 6 | req.setMethod('GET'); 7 | req.setHeader('Accept', 'application/json'); 8 | req.setHeader('Content-Type', 'application/json'); 9 | Http http = new Http(); 10 | HTTPResponse res = http.send(req); 11 | System.debug(res.getBody()); 12 | } 13 | } -------------------------------------------------------------------------------- /named_credentials/GitHubPassTokenCallout.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /named_credentials/GithubOAuthCallout.cls: -------------------------------------------------------------------------------- 1 | public class GithubOAuthCallout { 2 | 3 | public static void callGitHub() 4 | { 5 | HttpRequest req = new HttpRequest(); 6 | req.setEndpoint('callout:GitHub_OAuth' + '/users/Coding-With-The-Force/repos'); 7 | req.setMethod('GET'); 8 | req.setHeader('Accept', 'application/json'); 9 | req.setHeader('Content-Type', 'application/json'); 10 | Http http = new Http(); 11 | HTTPResponse res = http.send(req); 12 | System.debug(res.getBody()); 13 | } 14 | } -------------------------------------------------------------------------------- /named_credentials/GithubOAuthCallout.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /named_credentials/GoogleMapsCallout.cls: -------------------------------------------------------------------------------- 1 | public class GoogleMapsCallout { 2 | 3 | private static final String GEOCODE_PATH = '/maps/api/geocode/json?'; 4 | private static final String ADDRESS_VARIABLE = 'address=1600+Amphitheatre+Parkway+Mountain+View,+CA'; 5 | private static final String KEY_VARIABLE = '&key=[your_google_api_key]'; 6 | 7 | public static void callGoogleMaps() 8 | { 9 | HttpRequest req = new HttpRequest(); 10 | req.setEndpoint('https://maps.googleapis.com' + GEOCODE_PATH + ADDRESS_VARIABLE + KEY_VARIABLE); 11 | req.setMethod('GET'); 12 | Http http = new Http(); 13 | HTTPResponse res = http.send(req); 14 | System.debug(res.getBody()); 15 | } 16 | } -------------------------------------------------------------------------------- /named_credentials/GoogleMapsCallout.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /named_credentials/README.md: -------------------------------------------------------------------------------- 1 | The video tutorial I made covering how and why to leverage named credentials can be viewed here: https://youtu.be/hIOvZZfbrto 2 | -------------------------------------------------------------------------------- /safe_navigation_examples/README.md: -------------------------------------------------------------------------------- 1 | The video tutorial I made covering the safe navigation operator can be viewed here: https://youtu.be/wpJsi3d6QHI 2 | -------------------------------------------------------------------------------- /safe_navigation_examples/safeNavExample.cls: -------------------------------------------------------------------------------- 1 | public with sharing class safeNavExample { 2 | 3 | public static void noSafeNav(){ 4 | List acctList = [SELECT Id, Name FROM Account WHERE Name = 'Kevin' LIMIT 1]; 5 | if(!acctList.isEmpty()){ 6 | String acctName = acctList[0].Name; 7 | } 8 | System.debug('This is the account name ::: ' + acctList.size()); 9 | } 10 | 11 | public static void safeNav(){ 12 | String acctName = [SELECT Id, Name FROM Account WHERE Name = 'Kevin' LIMIT 1]?.Name; 13 | System.debug('This is the account name ::: ' + acctName); 14 | } 15 | 16 | public static void safeNavMap(){ 17 | Map acctMap = new Map([SELECT Id, Name FROM Account WHERE Name = 'sForce' LIMIT 1]); 18 | List acctList = [SELECT Id, Name FROM Account]; 19 | 20 | for(Account acct: acctList){ 21 | String acctFoundName = acctMap.get(acct.Id)?.Name; 22 | System.debug('This is the name of the account ::: ' + acctFoundName); 23 | } 24 | } 25 | 26 | public static void noSafeNavMap(){ 27 | Map acctMap = new Map([SELECT Id, Name FROM Account WHERE Name = 'sForce' LIMIT 1]); 28 | List acctList = [SELECT Id, Name FROM Account]; 29 | 30 | for(Account acct: acctList){ 31 | Account acctFound = acctMap.get(acct.Id); 32 | if(acctFound != null){ 33 | String acctFoundName = acctFound.Name; 34 | System.debug('This is the name of the account ::: ' + acctFoundName); 35 | } 36 | 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /safe_navigation_examples/safeNavExample.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /trigger_framework/Case_Trigger_Handler.cls: -------------------------------------------------------------------------------- 1 | public with sharing class Case_Trigger_Handler extends TriggerHandler 2 | { 3 | public override void beforeInsert() 4 | { 5 | Case_Utility.updateCaseSubject(trigger.new); 6 | Case_Utility.createCase(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /trigger_framework/Case_Trigger_Handler.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /trigger_framework/Case_Utility.cls: -------------------------------------------------------------------------------- 1 | public with sharing class Case_Utility 2 | { 3 | private static final String HANDLER_NAME = 'Case_Trigger_Handler'; 4 | 5 | public static void updateCaseSubject(List caseList) 6 | { 7 | for(Case cs: caseList) 8 | { 9 | cs.Subject = 'Kewl New Subject'; 10 | } 11 | } 12 | 13 | public static void createCase() 14 | { 15 | TriggerHandler.bypass(HANDLER_NAME); 16 | 17 | Case newCase = new Case(); 18 | newCase.Subject = 'Awesome Case'; 19 | insert newCase; 20 | 21 | TriggerHandler.clearBypass(HANDLER_NAME); 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /trigger_framework/Case_Utility.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /trigger_framework/README.md: -------------------------------------------------------------------------------- 1 | The video tutorial I made covering how to implement this trigger framework can be found here: https://youtu.be/nmwX0djwG_4 2 | -------------------------------------------------------------------------------- /trigger_framework/TriggerHandler.cls: -------------------------------------------------------------------------------- 1 | public virtual class TriggerHandler { 2 | 3 | // static map of handlername, times run() was invoked 4 | private static Map loopCountMap; 5 | private static Set bypassedHandlers; 6 | 7 | // the current context of the trigger, overridable in tests 8 | @TestVisible 9 | private TriggerContext context; 10 | 11 | // the current context of the trigger, overridable in tests 12 | @TestVisible 13 | private Boolean isTriggerExecuting; 14 | 15 | // static initialization 16 | static { 17 | loopCountMap = new Map(); 18 | bypassedHandlers = new Set(); 19 | } 20 | 21 | // constructor 22 | public TriggerHandler() { 23 | this.setTriggerContext(); 24 | } 25 | 26 | /*************************************** 27 | * public instance methods 28 | ***************************************/ 29 | 30 | // main method that will be called during execution 31 | public void run() { 32 | 33 | if(!validateRun()) { 34 | return; 35 | } 36 | 37 | addToLoopCount(); 38 | 39 | // dispatch to the correct handler method 40 | switch on this.context { 41 | when BEFORE_INSERT { 42 | this.beforeInsert(); 43 | } 44 | when BEFORE_UPDATE { 45 | this.beforeUpdate(); 46 | } 47 | when BEFORE_DELETE { 48 | this.beforeDelete(); 49 | } 50 | when AFTER_INSERT { 51 | this.afterInsert(); 52 | } 53 | when AFTER_UPDATE { 54 | this.afterUpdate(); 55 | } 56 | when AFTER_DELETE { 57 | this.afterDelete(); 58 | } 59 | when AFTER_UNDELETE { 60 | this.afterUndelete(); 61 | } 62 | } 63 | } 64 | 65 | public void setMaxLoopCount(Integer max) { 66 | String handlerName = getHandlerName(); 67 | if(!TriggerHandler.loopCountMap.containsKey(handlerName)) { 68 | TriggerHandler.loopCountMap.put(handlerName, new LoopCount(max)); 69 | } else { 70 | TriggerHandler.loopCountMap.get(handlerName).setMax(max); 71 | } 72 | } 73 | 74 | public void clearMaxLoopCount() { 75 | this.setMaxLoopCount(-1); 76 | } 77 | 78 | /*************************************** 79 | * public static methods 80 | ***************************************/ 81 | 82 | public static void bypass(String handlerName) { 83 | TriggerHandler.bypassedHandlers.add(handlerName); 84 | } 85 | 86 | public static void clearBypass(String handlerName) { 87 | TriggerHandler.bypassedHandlers.remove(handlerName); 88 | } 89 | 90 | public static Boolean isBypassed(String handlerName) { 91 | return TriggerHandler.bypassedHandlers.contains(handlerName); 92 | } 93 | 94 | public static void clearAllBypasses() { 95 | TriggerHandler.bypassedHandlers.clear(); 96 | } 97 | 98 | /*************************************** 99 | * private instancemethods 100 | ***************************************/ 101 | 102 | @TestVisible 103 | private void setTriggerContext() { 104 | this.setTriggerContext(null, false); 105 | } 106 | 107 | @TestVisible 108 | private void setTriggerContext(String ctx, Boolean testMode) { 109 | if(!Trigger.isExecuting && !testMode) { 110 | this.isTriggerExecuting = false; 111 | return; 112 | } else { 113 | this.isTriggerExecuting = true; 114 | } 115 | 116 | if((Trigger.isExecuting && Trigger.isBefore && Trigger.isInsert) || 117 | (ctx != null && ctx == 'before insert')) { 118 | this.context = TriggerContext.BEFORE_INSERT; 119 | } else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isUpdate) || 120 | (ctx != null && ctx == 'before update')){ 121 | this.context = TriggerContext.BEFORE_UPDATE; 122 | } else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isDelete) || 123 | (ctx != null && ctx == 'before delete')) { 124 | this.context = TriggerContext.BEFORE_DELETE; 125 | } else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isInsert) || 126 | (ctx != null && ctx == 'after insert')) { 127 | this.context = TriggerContext.AFTER_INSERT; 128 | } else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUpdate) || 129 | (ctx != null && ctx == 'after update')) { 130 | this.context = TriggerContext.AFTER_UPDATE; 131 | } else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isDelete) || 132 | (ctx != null && ctx == 'after delete')) { 133 | this.context = TriggerContext.AFTER_DELETE; 134 | } else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUndelete) || 135 | (ctx != null && ctx == 'after undelete')) { 136 | this.context = TriggerContext.AFTER_UNDELETE; 137 | } 138 | } 139 | 140 | // increment the loop count 141 | @TestVisible 142 | private void addToLoopCount() { 143 | String handlerName = getHandlerName(); 144 | if(TriggerHandler.loopCountMap.containsKey(handlerName)) { 145 | Boolean exceeded = TriggerHandler.loopCountMap.get(handlerName).increment(); 146 | if(exceeded) { 147 | Integer max = TriggerHandler.loopCountMap.get(handlerName).max; 148 | throw new TriggerHandlerException('Maximum loop count of ' + String.valueOf(max) + ' reached in ' + handlerName); 149 | } 150 | } 151 | } 152 | 153 | // make sure this trigger should continue to run 154 | @TestVisible 155 | private Boolean validateRun() { 156 | if(!this.isTriggerExecuting || this.context == null) { 157 | throw new TriggerHandlerException('Trigger handler called outside of Trigger execution'); 158 | } 159 | return !TriggerHandler.bypassedHandlers.contains(getHandlerName()); 160 | } 161 | 162 | @TestVisible 163 | private String getHandlerName() { 164 | return String.valueOf(this).substring(0,String.valueOf(this).indexOf(':')); 165 | } 166 | 167 | /*************************************** 168 | * context methods 169 | ***************************************/ 170 | 171 | // context-specific methods for override 172 | @TestVisible 173 | protected virtual void beforeInsert(){} 174 | @TestVisible 175 | protected virtual void beforeUpdate(){} 176 | @TestVisible 177 | protected virtual void beforeDelete(){} 178 | @TestVisible 179 | protected virtual void afterInsert(){} 180 | @TestVisible 181 | protected virtual void afterUpdate(){} 182 | @TestVisible 183 | protected virtual void afterDelete(){} 184 | @TestVisible 185 | protected virtual void afterUndelete(){} 186 | 187 | /*************************************** 188 | * inner classes 189 | ***************************************/ 190 | 191 | // inner class for managing the loop count per handler 192 | @TestVisible 193 | private class LoopCount { 194 | private Integer max; 195 | private Integer count; 196 | 197 | public LoopCount() { 198 | this.max = 5; 199 | this.count = 0; 200 | } 201 | 202 | public LoopCount(Integer max) { 203 | this.max = max; 204 | this.count = 0; 205 | } 206 | 207 | public Boolean increment() { 208 | this.count++; 209 | return this.exceeded(); 210 | } 211 | 212 | public Boolean exceeded() { 213 | return this.max >= 0 && this.count > this.max; 214 | } 215 | 216 | public Integer getMax() { 217 | return this.max; 218 | } 219 | 220 | public Integer getCount() { 221 | return this.count; 222 | } 223 | 224 | public void setMax(Integer max) { 225 | this.max = max; 226 | } 227 | } 228 | 229 | // possible trigger contexts 230 | @TestVisible 231 | private enum TriggerContext { 232 | BEFORE_INSERT, BEFORE_UPDATE, BEFORE_DELETE, 233 | AFTER_INSERT, AFTER_UPDATE, AFTER_DELETE, 234 | AFTER_UNDELETE 235 | } 236 | 237 | // exception class 238 | public class TriggerHandlerException extends Exception {} 239 | 240 | } 241 | -------------------------------------------------------------------------------- /trigger_framework/TriggerHandler.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /trigger_framework/TriggerHandler_Test.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | private class TriggerHandler_Test { 3 | 4 | private static final String TRIGGER_CONTEXT_ERROR = 'Trigger handler called outside of Trigger execution'; 5 | 6 | private static String lastMethodCalled; 7 | 8 | private static TriggerHandler_Test.TestHandler handler; 9 | 10 | static { 11 | handler = new TriggerHandler_Test.TestHandler(); 12 | // override its internal trigger detection 13 | handler.isTriggerExecuting = true; 14 | } 15 | 16 | /*************************************** 17 | * unit tests 18 | ***************************************/ 19 | 20 | // contexts tests 21 | 22 | @isTest 23 | static void testBeforeInsert() { 24 | beforeInsertMode(); 25 | handler.run(); 26 | System.assertEquals('beforeInsert', lastMethodCalled, 'last method should be beforeInsert'); 27 | } 28 | 29 | @isTest 30 | static void testBeforeUpdate() { 31 | beforeUpdateMode(); 32 | handler.run(); 33 | System.assertEquals('beforeUpdate', lastMethodCalled, 'last method should be beforeUpdate'); 34 | } 35 | 36 | @isTest 37 | static void testBeforeDelete() { 38 | beforeDeleteMode(); 39 | handler.run(); 40 | System.assertEquals('beforeDelete', lastMethodCalled, 'last method should be beforeDelete'); 41 | } 42 | 43 | @isTest 44 | static void testAfterInsert() { 45 | afterInsertMode(); 46 | handler.run(); 47 | System.assertEquals('afterInsert', lastMethodCalled, 'last method should be afterInsert'); 48 | } 49 | 50 | @isTest 51 | static void testAfterUpdate() { 52 | afterUpdateMode(); 53 | handler.run(); 54 | System.assertEquals('afterUpdate', lastMethodCalled, 'last method should be afterUpdate'); 55 | } 56 | 57 | @isTest 58 | static void testAfterDelete() { 59 | afterDeleteMode(); 60 | handler.run(); 61 | System.assertEquals('afterDelete', lastMethodCalled, 'last method should be afterDelete'); 62 | } 63 | 64 | @isTest 65 | static void testAfterUndelete() { 66 | afterUndeleteMode(); 67 | handler.run(); 68 | System.assertEquals('afterUndelete', lastMethodCalled, 'last method should be afterUndelete'); 69 | } 70 | 71 | @isTest 72 | static void testNonTriggerContext() { 73 | try{ 74 | handler.run(); 75 | System.assert(false, 'the handler ran but should have thrown'); 76 | } catch(TriggerHandler.TriggerHandlerException te) { 77 | System.assertEquals(TRIGGER_CONTEXT_ERROR, te.getMessage(), 'the exception message should match'); 78 | } catch(Exception e) { 79 | System.assert(false, 'the exception thrown was not expected: ' + e.getTypeName() + ': ' + e.getMessage()); 80 | } 81 | } 82 | 83 | // test bypass api 84 | 85 | @isTest 86 | static void testBypassAPI() { 87 | afterUpdateMode(); 88 | 89 | // test a bypass and run handler 90 | TriggerHandler.bypass('TestHandler'); 91 | handler.run(); 92 | System.assertEquals(null, lastMethodCalled, 'last method should be null when bypassed'); 93 | System.assertEquals(true, TriggerHandler.isBypassed('TestHandler'), 'test handler should be bypassed'); 94 | resetTest(); 95 | 96 | // clear that bypass and run handler 97 | TriggerHandler.clearBypass('TestHandler'); 98 | handler.run(); 99 | System.assertEquals('afterUpdate', lastMethodCalled, 'last method called should be afterUpdate'); 100 | System.assertEquals(false, TriggerHandler.isBypassed('TestHandler'), 'test handler should be bypassed'); 101 | resetTest(); 102 | 103 | // test a re-bypass and run handler 104 | TriggerHandler.bypass('TestHandler'); 105 | handler.run(); 106 | System.assertEquals(null, lastMethodCalled, 'last method should be null when bypassed'); 107 | System.assertEquals(true, TriggerHandler.isBypassed('TestHandler'), 'test handler should be bypassed'); 108 | resetTest(); 109 | 110 | // clear all bypasses and run handler 111 | TriggerHandler.clearAllBypasses(); 112 | handler.run(); 113 | System.assertEquals('afterUpdate', lastMethodCalled, 'last method called should be afterUpdate'); 114 | System.assertEquals(false, TriggerHandler.isBypassed('TestHandler'), 'test handler should be bypassed'); 115 | resetTest(); 116 | } 117 | 118 | // instance method tests 119 | 120 | @isTest 121 | static void testLoopCount() { 122 | beforeInsertMode(); 123 | 124 | // set the max loops to 2 125 | handler.setMaxLoopCount(2); 126 | 127 | // run the handler twice 128 | handler.run(); 129 | handler.run(); 130 | 131 | // clear the tests 132 | resetTest(); 133 | 134 | try { 135 | // try running it. This should exceed the limit. 136 | handler.run(); 137 | System.assert(false, 'the handler should throw on the 3rd run when maxloopcount is 3'); 138 | } catch(TriggerHandler.TriggerHandlerException te) { 139 | // we're expecting to get here 140 | System.assertEquals(null, lastMethodCalled, 'last method should be null'); 141 | } catch(Exception e) { 142 | System.assert(false, 'the exception thrown was not expected: ' + e.getTypeName() + ': ' + e.getMessage()); 143 | } 144 | 145 | // clear the tests 146 | resetTest(); 147 | 148 | // now clear the loop count 149 | handler.clearMaxLoopCount(); 150 | 151 | try { 152 | // re-run the handler. We shouldn't throw now. 153 | handler.run(); 154 | System.assertEquals('beforeInsert', lastMethodCalled, 'last method should be beforeInsert'); 155 | } catch(TriggerHandler.TriggerHandlerException te) { 156 | System.assert(false, 'running the handler after clearing the loop count should not throw'); 157 | } catch(Exception e) { 158 | System.assert(false, 'the exception thrown was not expected: ' + e.getTypeName() + ': ' + e.getMessage()); 159 | } 160 | } 161 | 162 | @isTest 163 | static void testLoopCountClass() { 164 | TriggerHandler.LoopCount lc = new TriggerHandler.LoopCount(); 165 | System.assertEquals(5, lc.getMax(), 'max should be five on init'); 166 | System.assertEquals(0, lc.getCount(), 'count should be zero on init'); 167 | 168 | lc.increment(); 169 | System.assertEquals(1, lc.getCount(), 'count should be 1'); 170 | System.assertEquals(false, lc.exceeded(), 'should not be exceeded with count of 1'); 171 | 172 | lc.increment(); 173 | lc.increment(); 174 | lc.increment(); 175 | lc.increment(); 176 | System.assertEquals(5, lc.getCount(), 'count should be 5'); 177 | System.assertEquals(false, lc.exceeded(), 'should not be exceeded with count of 5'); 178 | 179 | lc.increment(); 180 | System.assertEquals(6, lc.getCount(), 'count should be 6'); 181 | System.assertEquals(true, lc.exceeded(), 'should not be exceeded with count of 6'); 182 | } 183 | 184 | // private method tests 185 | 186 | @isTest 187 | static void testGetHandlerName() { 188 | System.assertEquals('TestHandler', handler.getHandlerName(), 'handler name should match class name'); 189 | } 190 | 191 | // test virtual methods 192 | 193 | @isTest 194 | static void testVirtualMethods() { 195 | TriggerHandler h = new TriggerHandler(); 196 | h.beforeInsert(); 197 | h.beforeUpdate(); 198 | h.beforeDelete(); 199 | h.afterInsert(); 200 | h.afterUpdate(); 201 | h.afterDelete(); 202 | h.afterUndelete(); 203 | } 204 | 205 | /*************************************** 206 | * testing utilities 207 | ***************************************/ 208 | 209 | private static void resetTest() { 210 | lastMethodCalled = null; 211 | } 212 | 213 | // modes for testing 214 | 215 | private static void beforeInsertMode() { 216 | handler.setTriggerContext('before insert', true); 217 | } 218 | 219 | private static void beforeUpdateMode() { 220 | handler.setTriggerContext('before update', true); 221 | } 222 | 223 | private static void beforeDeleteMode() { 224 | handler.setTriggerContext('before delete', true); 225 | } 226 | 227 | private static void afterInsertMode() { 228 | handler.setTriggerContext('after insert', true); 229 | } 230 | 231 | private static void afterUpdateMode() { 232 | handler.setTriggerContext('after update', true); 233 | } 234 | 235 | private static void afterDeleteMode() { 236 | handler.setTriggerContext('after delete', true); 237 | } 238 | 239 | private static void afterUndeleteMode() { 240 | handler.setTriggerContext('after undelete', true); 241 | } 242 | 243 | // test implementation of the TriggerHandler 244 | 245 | private class TestHandler extends TriggerHandler { 246 | 247 | public override void beforeInsert() { 248 | TriggerHandler_Test.lastMethodCalled = 'beforeInsert'; 249 | } 250 | 251 | public override void beforeUpdate() { 252 | TriggerHandler_Test.lastMethodCalled = 'beforeUpdate'; 253 | } 254 | 255 | public override void beforeDelete() { 256 | TriggerHandler_Test.lastMethodCalled = 'beforeDelete'; 257 | } 258 | 259 | public override void afterInsert() { 260 | TriggerHandler_Test.lastMethodCalled = 'afterInsert'; 261 | } 262 | 263 | public override void afterUpdate() { 264 | TriggerHandler_Test.lastMethodCalled = 'afterUpdate'; 265 | } 266 | 267 | public override void afterDelete() { 268 | TriggerHandler_Test.lastMethodCalled = 'afterDelete'; 269 | } 270 | 271 | public override void afterUndelete() { 272 | TriggerHandler_Test.lastMethodCalled = 'afterUndelete'; 273 | } 274 | 275 | } 276 | 277 | } 278 | -------------------------------------------------------------------------------- /trigger_framework/TriggerHandler_Test.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25.0 4 | 5 | --------------------------------------------------------------------------------