Initial commit
This commit is contained in:
536
skills/gtm-custom-templates/SKILL.md
Normal file
536
skills/gtm-custom-templates/SKILL.md
Normal file
@@ -0,0 +1,536 @@
|
||||
---
|
||||
name: gtm-custom-templates
|
||||
description: Expert guidance for building Google Tag Manager custom templates using sandboxed JavaScript. Use when creating custom tag templates, custom variable templates, server-side client templates, converting regular JavaScript to sandboxed JS, debugging template code, writing template tests, publishing to the Community Template Gallery, working with .tpl template files, or using sandboxed JavaScript APIs like require(), sendPixel, injectScript, and other GTM template APIs.
|
||||
---
|
||||
|
||||
# GTM Custom Templates Builder
|
||||
|
||||
## Overview
|
||||
|
||||
This skill provides comprehensive expertise for building Google Tag Manager custom templates using sandboxed JavaScript. Master template structure, sandboxed JavaScript APIs, permissions, testing, and publishing to the Community Template Gallery.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Invoke this skill when:
|
||||
- Building a custom tag template for a third-party service
|
||||
- Creating a custom variable template for complex data manipulation
|
||||
- Developing server-side client templates
|
||||
- Converting regular JavaScript to sandboxed JavaScript
|
||||
- Debugging sandboxed JavaScript API errors
|
||||
- Understanding template permissions and security
|
||||
- Writing tests for custom templates
|
||||
- Publishing templates to the Community Template Gallery
|
||||
- Troubleshooting "undefined is not a function" errors in templates
|
||||
- Implementing HTTP requests, pixel firing, or script injection in templates
|
||||
|
||||
## Sandboxed JavaScript Fundamentals
|
||||
|
||||
### What is Sandboxed JavaScript?
|
||||
|
||||
Sandboxed JavaScript is a restricted subset of JavaScript used in GTM custom templates. It provides security by limiting what code can execute while offering safe APIs for common operations.
|
||||
|
||||
**Key Differences from Regular JavaScript:**
|
||||
- No direct DOM access
|
||||
- No window/document object
|
||||
- No eval() or Function()
|
||||
- No arbitrary HTTP requests
|
||||
- Must use require() to import APIs
|
||||
- All operations require explicit permissions
|
||||
|
||||
### Basic Template Structure
|
||||
|
||||
```javascript
|
||||
// Import required APIs
|
||||
const sendPixel = require('sendPixel');
|
||||
const logToConsole = require('logToConsole');
|
||||
const encodeUriComponent = require('encodeUriComponent');
|
||||
|
||||
// Access template configuration
|
||||
const endpoint = data.endpoint;
|
||||
const eventName = data.eventName;
|
||||
|
||||
// Execute template logic
|
||||
const url = endpoint + '?event=' + encodeUriComponent(eventName);
|
||||
sendPixel(url, data.gtmOnSuccess, data.gtmOnFailure);
|
||||
```
|
||||
|
||||
## Building Tag Templates
|
||||
|
||||
### Tag Template Workflow
|
||||
|
||||
1. **Define Template Info**
|
||||
- Template type (TAG)
|
||||
- Display name and description
|
||||
- Categories and branding
|
||||
- Container contexts (WEB, SERVER, etc.)
|
||||
|
||||
2. **Configure Template Parameters**
|
||||
- Add fields for user configuration
|
||||
- Set field types (TEXT, SELECT, CHECKBOX, etc.)
|
||||
- Define validation rules
|
||||
- Set default values
|
||||
|
||||
3. **Write Sandboxed JavaScript Code**
|
||||
- Require necessary APIs
|
||||
- Access data from template fields
|
||||
- Implement tag logic
|
||||
- Call gtmOnSuccess/gtmOnFailure
|
||||
|
||||
4. **Set Permissions**
|
||||
- Configure required permissions
|
||||
- Specify allowed URLs, cookies, etc.
|
||||
- Minimize permission scope
|
||||
|
||||
5. **Write Tests**
|
||||
- Test successful execution
|
||||
- Test error handling
|
||||
- Test edge cases
|
||||
|
||||
### Common Tag Template Patterns
|
||||
|
||||
**Simple Pixel Tag:**
|
||||
```javascript
|
||||
const sendPixel = require('sendPixel');
|
||||
const encodeUriComponent = require('encodeUriComponent');
|
||||
|
||||
const pixelUrl = data.pixelUrl +
|
||||
'?id=' + encodeUriComponent(data.pixelId) +
|
||||
'&event=' + encodeUriComponent(data.eventName);
|
||||
|
||||
sendPixel(pixelUrl, data.gtmOnSuccess, data.gtmOnFailure);
|
||||
```
|
||||
|
||||
**HTTP Request Tag:**
|
||||
```javascript
|
||||
const sendHttpRequest = require('sendHttpRequest');
|
||||
const JSON = require('JSON');
|
||||
|
||||
const postBody = JSON.stringify({
|
||||
event: data.eventName,
|
||||
userId: data.userId
|
||||
});
|
||||
|
||||
const options = {
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
method: 'POST'
|
||||
};
|
||||
|
||||
sendHttpRequest(data.endpoint, options, postBody)
|
||||
.then(data.gtmOnSuccess)
|
||||
.catch(data.gtmOnFailure);
|
||||
```
|
||||
|
||||
**Script Injection Tag:**
|
||||
```javascript
|
||||
const injectScript = require('injectScript');
|
||||
const queryPermission = require('queryPermission');
|
||||
|
||||
const url = 'https://example.com/script.js';
|
||||
|
||||
if (queryPermission('inject_script', url)) {
|
||||
injectScript(url, data.gtmOnSuccess, data.gtmOnFailure);
|
||||
} else {
|
||||
data.gtmOnFailure();
|
||||
}
|
||||
```
|
||||
|
||||
## Building Variable Templates
|
||||
|
||||
### Variable Template Basics
|
||||
|
||||
Variable templates return a value that can be used in other GTM configurations.
|
||||
|
||||
**Cookie Variable:**
|
||||
```javascript
|
||||
const getCookieValues = require('getCookieValues');
|
||||
|
||||
const cookieName = data.cookieName;
|
||||
const cookies = getCookieValues(cookieName);
|
||||
|
||||
if (cookies && cookies.length > 0) {
|
||||
return cookies[0];
|
||||
}
|
||||
|
||||
return data.defaultValue || '';
|
||||
```
|
||||
|
||||
**LocalStorage Variable:**
|
||||
```javascript
|
||||
const localStorage = require('localStorage');
|
||||
|
||||
const key = data.storageKey;
|
||||
const value = localStorage.getItem(key);
|
||||
|
||||
return value || data.defaultValue;
|
||||
```
|
||||
|
||||
**Custom JavaScript Variable:**
|
||||
```javascript
|
||||
const makeTableMap = require('makeTableMap');
|
||||
const makeNumber = require('makeNumber');
|
||||
|
||||
// Convert table to lookup map
|
||||
const lookupTable = makeTableMap(data.table, 'key', 'value');
|
||||
|
||||
// Perform lookup
|
||||
const inputValue = data.inputVariable;
|
||||
return lookupTable[inputValue] || data.defaultValue;
|
||||
```
|
||||
|
||||
## Sandboxed JavaScript API Reference
|
||||
|
||||
### Commonly Used APIs
|
||||
|
||||
**Data Type Conversion:**
|
||||
```javascript
|
||||
const makeInteger = require('makeInteger');
|
||||
const makeNumber = require('makeNumber');
|
||||
const makeString = require('makeString');
|
||||
|
||||
const num = makeNumber('123.45'); // 123.45
|
||||
const int = makeInteger('123.45'); // 123
|
||||
const str = makeString(123); // '123'
|
||||
```
|
||||
|
||||
**Network APIs:**
|
||||
```javascript
|
||||
const sendPixel = require('sendPixel');
|
||||
const sendHttpRequest = require('sendHttpRequest');
|
||||
const injectScript = require('injectScript');
|
||||
|
||||
// Pixel
|
||||
sendPixel(url, onSuccess, onFailure);
|
||||
|
||||
// HTTP Request
|
||||
sendHttpRequest(url, options, body)
|
||||
.then(onSuccess)
|
||||
.catch(onFailure);
|
||||
|
||||
// Script injection
|
||||
injectScript(url, onSuccess, onFailure);
|
||||
```
|
||||
|
||||
**Storage APIs:**
|
||||
```javascript
|
||||
const getCookieValues = require('getCookieValues');
|
||||
const setCookie = require('setCookie');
|
||||
const localStorage = require('localStorage');
|
||||
|
||||
// Cookies
|
||||
const cookies = getCookieValues('cookieName');
|
||||
setCookie('name', 'value', {
|
||||
domain: 'example.com',
|
||||
path: '/',
|
||||
'max-age': 3600
|
||||
});
|
||||
|
||||
// LocalStorage
|
||||
const value = localStorage.getItem('key');
|
||||
localStorage.setItem('key', 'value');
|
||||
```
|
||||
|
||||
**Utility APIs:**
|
||||
```javascript
|
||||
const encodeUri = require('encodeUri');
|
||||
const encodeUriComponent = require('encodeUriComponent');
|
||||
const decodeUri = require('decodeUri');
|
||||
const decodeUriComponent = require('decodeUriComponent');
|
||||
const JSON = require('JSON');
|
||||
const Math = require('Math');
|
||||
const Object = require('Object');
|
||||
|
||||
// URL encoding
|
||||
const encoded = encodeUriComponent('hello world');
|
||||
|
||||
// JSON
|
||||
const obj = JSON.parse('{"key":"value"}');
|
||||
const str = JSON.stringify({key: 'value'});
|
||||
|
||||
// Math
|
||||
const random = Math.random();
|
||||
const rounded = Math.round(3.7);
|
||||
```
|
||||
|
||||
**DOM APIs (Limited):**
|
||||
```javascript
|
||||
const callInWindow = require('callInWindow');
|
||||
const copyFromWindow = require('copyFromWindow');
|
||||
|
||||
// Call window function
|
||||
callInWindow('functionName', arg1, arg2);
|
||||
|
||||
// Copy from window
|
||||
const gaData = copyFromWindow('ga');
|
||||
```
|
||||
|
||||
## Permissions System
|
||||
|
||||
### Understanding Permissions
|
||||
|
||||
Every API requires explicit permission configuration. Permissions define what URLs, cookies, or data the template can access.
|
||||
|
||||
**sendPixel Permission:**
|
||||
```json
|
||||
{
|
||||
"instance": {
|
||||
"key": {"publicId": "send_pixel", "versionId": "1"},
|
||||
"param": [{
|
||||
"key": "allowedUrls",
|
||||
"value": {"type": 1, "string": "specific"},
|
||||
"list": [
|
||||
{"type": 1, "string": "https://example.com/*"}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**get_cookies Permission:**
|
||||
```json
|
||||
{
|
||||
"instance": {
|
||||
"key": {"publicId": "get_cookies", "versionId": "1"},
|
||||
"param": [{
|
||||
"key": "cookieAccess",
|
||||
"value": {"type": 1, "string": "specific"},
|
||||
"list": [
|
||||
{"type": 1, "string": "session_id"},
|
||||
{"type": 1, "string": "user_*"}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Best Practice:** Use the most restrictive permissions possible. Avoid "any" when you can specify exact URLs or cookie names.
|
||||
|
||||
## Template Testing
|
||||
|
||||
### Writing Tests
|
||||
|
||||
```javascript
|
||||
// Test successful execution
|
||||
scenarios:
|
||||
- name: Tag fires successfully
|
||||
code: |-
|
||||
const mockData = {
|
||||
endpoint: 'https://example.com/api',
|
||||
eventName: 'test_event'
|
||||
};
|
||||
|
||||
runCode(mockData);
|
||||
|
||||
assertApi('gtmOnSuccess').wasCalled();
|
||||
assertApi('sendHttpRequest').wasCalledWith(
|
||||
'https://example.com/api',
|
||||
assertThat.objectContaining({method: 'POST'})
|
||||
);
|
||||
```
|
||||
|
||||
### Test Mocking
|
||||
|
||||
```javascript
|
||||
// Mock API returns
|
||||
mock('getCookieValues', (name) => {
|
||||
if (name === 'session_id') {
|
||||
return ['abc123'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
// Test with mock
|
||||
const mockData = {cookieName: 'session_id'};
|
||||
let result = runCode(mockData);
|
||||
|
||||
assertThat(result).isEqualTo('abc123');
|
||||
```
|
||||
|
||||
## Common Patterns and Solutions
|
||||
|
||||
### Converting Regular JS to Sandboxed JS
|
||||
|
||||
❌ **Regular JavaScript (won't work):**
|
||||
```javascript
|
||||
// Direct DOM access
|
||||
document.getElementById('element');
|
||||
|
||||
// Direct window access
|
||||
window.dataLayer.push({});
|
||||
|
||||
// XMLHttpRequest
|
||||
const xhr = new XMLHttpRequest();
|
||||
```
|
||||
|
||||
✅ **Sandboxed JavaScript (will work):**
|
||||
```javascript
|
||||
// Use callInWindow
|
||||
const callInWindow = require('callInWindow');
|
||||
callInWindow('dataLayer.push', {event: 'custom'});
|
||||
|
||||
// Use sendHttpRequest
|
||||
const sendHttpRequest = require('sendHttpRequest');
|
||||
sendHttpRequest(url, {method: 'GET'})
|
||||
.then(data.gtmOnSuccess)
|
||||
.catch(data.gtmOnFailure);
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```javascript
|
||||
const sendHttpRequest = require('sendHttpRequest');
|
||||
const logToConsole = require('logToConsole');
|
||||
|
||||
sendHttpRequest(data.endpoint)
|
||||
.then(response => {
|
||||
logToConsole('Success:', response);
|
||||
data.gtmOnSuccess();
|
||||
})
|
||||
.catch(error => {
|
||||
logToConsole('Error:', error);
|
||||
data.gtmOnFailure();
|
||||
});
|
||||
```
|
||||
|
||||
### Data Validation
|
||||
|
||||
```javascript
|
||||
const makeNumber = require('makeNumber');
|
||||
const getType = require('getType');
|
||||
|
||||
// Validate input
|
||||
if (getType(data.value) === 'undefined') {
|
||||
data.gtmOnFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert and validate
|
||||
const numValue = makeNumber(data.value);
|
||||
if (numValue === undefined) {
|
||||
logToConsole('Invalid number');
|
||||
data.gtmOnFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue with valid data
|
||||
data.gtmOnSuccess();
|
||||
```
|
||||
|
||||
## Template Field Configuration
|
||||
|
||||
### Common Field Types
|
||||
|
||||
```javascript
|
||||
// Text input
|
||||
{
|
||||
"type": "TEXT",
|
||||
"name": "apiKey",
|
||||
"displayName": "API Key",
|
||||
"simpleValueType": true,
|
||||
"valueValidators": [
|
||||
{"type": "NON_EMPTY"},
|
||||
{"type": "REGEX", "args": ["^[a-zA-Z0-9]{32}$"]}
|
||||
]
|
||||
}
|
||||
|
||||
// Select dropdown
|
||||
{
|
||||
"type": "SELECT",
|
||||
"name": "eventType",
|
||||
"displayName": "Event Type",
|
||||
"selectItems": [
|
||||
{"value": "pageview", "displayValue": "Pageview"},
|
||||
{"value": "event", "displayValue": "Event"}
|
||||
],
|
||||
"simpleValueType": true
|
||||
}
|
||||
|
||||
// Checkbox
|
||||
{
|
||||
"type": "CHECKBOX",
|
||||
"name": "enableDebug",
|
||||
"checkboxText": "Enable Debug Logging",
|
||||
"simpleValueType": true,
|
||||
"defaultValue": false
|
||||
}
|
||||
|
||||
// Group
|
||||
{
|
||||
"type": "GROUP",
|
||||
"name": "advancedSettings",
|
||||
"displayName": "Advanced Settings",
|
||||
"groupStyle": "ZIPPY_CLOSED",
|
||||
"subParams": [
|
||||
// Nested fields here
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Publishing to Community Template Gallery
|
||||
|
||||
1. **Complete Template Info**
|
||||
- Descriptive name and description
|
||||
- Appropriate categories
|
||||
- Brand information
|
||||
- Thumbnail image (optional)
|
||||
|
||||
2. **Add Comprehensive Tests**
|
||||
- Test all major code paths
|
||||
- Test error handling
|
||||
- Test edge cases
|
||||
|
||||
3. **Document Template**
|
||||
- Clear field descriptions
|
||||
- Help text for complex fields
|
||||
- Notes section with usage instructions
|
||||
|
||||
4. **Submit for Review**
|
||||
- Export template from GTM
|
||||
- Submit to Community Template Gallery
|
||||
- Address reviewer feedback
|
||||
|
||||
## Template Boilerplates
|
||||
|
||||
This skill includes ready-to-use template boilerplates:
|
||||
|
||||
- **assets/tag-template-boilerplate.tpl** - Complete tag template structure
|
||||
- **assets/variable-template-boilerplate.tpl** - Complete variable template structure
|
||||
|
||||
Copy these files and customize for your specific use case.
|
||||
|
||||
## References
|
||||
|
||||
This skill includes comprehensive reference documentation:
|
||||
|
||||
- **references/sandboxed-javascript-api.md** - Complete API reference
|
||||
- **references/custom-templates-guide.md** - Simo Ahava's comprehensive guide
|
||||
- **references/template-testing.md** - Testing documentation and patterns
|
||||
- **references/template-examples.md** - Real-world template examples
|
||||
|
||||
Search references for specific APIs:
|
||||
```bash
|
||||
grep -r "sendHttpRequest" references/
|
||||
grep -r "permissions" references/
|
||||
grep -r "testing" references/
|
||||
```
|
||||
|
||||
## Integration with Other Skills
|
||||
|
||||
- **gtm-general** - Understanding GTM concepts and architecture
|
||||
- **gtm-setup** - Testing templates in GTM containers
|
||||
- **gtm-tags** - Understanding tag structure for tag templates
|
||||
- **gtm-variables** - Understanding variable structure for variable templates
|
||||
- **gtm-datalayer** - Templates that interact with data layer
|
||||
- **gtm-debugging** - Testing and debugging custom templates
|
||||
- **gtm-api** - Programmatic template management
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Import API:** `const apiName = require('apiName');`
|
||||
|
||||
**Success/Failure:** Always call `data.gtmOnSuccess()` or `data.gtmOnFailure()`
|
||||
|
||||
**Permissions:** Every API requires explicit permission configuration
|
||||
|
||||
**Testing:** Use `runCode(mockData)` and assertions
|
||||
|
||||
**Debugging:** Use `logToConsole()` with debug environment permission
|
||||
24
skills/gtm-custom-templates/assets/example_asset.txt
Normal file
24
skills/gtm-custom-templates/assets/example_asset.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
# Example Asset File
|
||||
|
||||
This placeholder represents where asset files would be stored.
|
||||
Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed.
|
||||
|
||||
Asset files are NOT intended to be loaded into context, but rather used within
|
||||
the output Claude produces.
|
||||
|
||||
Example asset files from other skills:
|
||||
- Brand guidelines: logo.png, slides_template.pptx
|
||||
- Frontend builder: hello-world/ directory with HTML/React boilerplate
|
||||
- Typography: custom-font.ttf, font-family.woff2
|
||||
- Data: sample_data.csv, test_dataset.json
|
||||
|
||||
## Common Asset Types
|
||||
|
||||
- Templates: .pptx, .docx, boilerplate directories
|
||||
- Images: .png, .jpg, .svg, .gif
|
||||
- Fonts: .ttf, .otf, .woff, .woff2
|
||||
- Boilerplate code: Project directories, starter files
|
||||
- Icons: .ico, .svg
|
||||
- Data files: .csv, .json, .xml, .yaml
|
||||
|
||||
Note: This is a text placeholder. Actual assets can be any file type.
|
||||
168
skills/gtm-custom-templates/assets/tag-template-boilerplate.tpl
Normal file
168
skills/gtm-custom-templates/assets/tag-template-boilerplate.tpl
Normal file
@@ -0,0 +1,168 @@
|
||||
___INFO___
|
||||
|
||||
{
|
||||
"type": "TAG",
|
||||
"id": "cvt_temp_public_id",
|
||||
"version": 1,
|
||||
"securityGroups": [],
|
||||
"displayName": "My Custom Tag",
|
||||
"categories": ["ANALYTICS", "MARKETING"],
|
||||
"brand": {
|
||||
"id": "brand_id",
|
||||
"displayName": "My Brand",
|
||||
"thumbnail": ""
|
||||
},
|
||||
"description": "A custom tag template for...",
|
||||
"containerContexts": [
|
||||
"WEB"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
___TEMPLATE_PARAMETERS___
|
||||
|
||||
[
|
||||
{
|
||||
"type": "TEXT",
|
||||
"name": "endpoint",
|
||||
"displayName": "API Endpoint",
|
||||
"simpleValueType": true,
|
||||
"help": "Enter the API endpoint URL",
|
||||
"valueValidators": [
|
||||
{
|
||||
"type": "NON_EMPTY"
|
||||
},
|
||||
{
|
||||
"type": "REGEX",
|
||||
"args": ["^https?://.*"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "TEXT",
|
||||
"name": "eventName",
|
||||
"displayName": "Event Name",
|
||||
"simpleValueType": true,
|
||||
"defaultValue": "custom_event"
|
||||
},
|
||||
{
|
||||
"type": "CHECKBOX",
|
||||
"name": "debug",
|
||||
"checkboxText": "Enable Debug Mode",
|
||||
"simpleValueType": true,
|
||||
"defaultValue": false
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
___SANDBOXED_JS_FOR_WEB_TEMPLATE___
|
||||
|
||||
// Require necessary APIs
|
||||
const sendPixel = require('sendPixel');
|
||||
const logToConsole = require('logToConsole');
|
||||
const encodeUriComponent = require('encodeUriComponent');
|
||||
|
||||
// Access template data
|
||||
const endpoint = data.endpoint;
|
||||
const eventName = data.eventName;
|
||||
const debug = data.debug;
|
||||
|
||||
// Debug logging
|
||||
if (debug) {
|
||||
logToConsole('Custom Tag Firing', {
|
||||
endpoint: endpoint,
|
||||
eventName: eventName
|
||||
});
|
||||
}
|
||||
|
||||
// Build pixel URL
|
||||
const pixelUrl = endpoint + '?event=' + encodeUriComponent(eventName);
|
||||
|
||||
// Fire pixel
|
||||
sendPixel(pixelUrl, data.gtmOnSuccess, data.gtmOnFailure);
|
||||
|
||||
|
||||
___WEB_PERMISSIONS___
|
||||
|
||||
[
|
||||
{
|
||||
"instance": {
|
||||
"key": {
|
||||
"publicId": "logging",
|
||||
"versionId": "1"
|
||||
},
|
||||
"param": [
|
||||
{
|
||||
"key": "environments",
|
||||
"value": {
|
||||
"type": 1,
|
||||
"string": "debug"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"clientAnnotations": {
|
||||
"isEditedByUser": true
|
||||
},
|
||||
"isRequired": true
|
||||
},
|
||||
{
|
||||
"instance": {
|
||||
"key": {
|
||||
"publicId": "send_pixel",
|
||||
"versionId": "1"
|
||||
},
|
||||
"param": [
|
||||
{
|
||||
"key": "allowedUrls",
|
||||
"value": {
|
||||
"type": 1,
|
||||
"string": "any"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"clientAnnotations": {
|
||||
"isEditedByUser": true
|
||||
},
|
||||
"isRequired": true
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
___TESTS___
|
||||
|
||||
scenarios:
|
||||
- name: Tag fires successfully
|
||||
code: |-
|
||||
const mockData = {
|
||||
endpoint: 'https://example.com/pixel',
|
||||
eventName: 'test_event',
|
||||
debug: false
|
||||
};
|
||||
|
||||
// Call runCode to run the template's code.
|
||||
runCode(mockData);
|
||||
|
||||
// Verify that the tag finished successfully.
|
||||
assertApi('gtmOnSuccess').wasCalled();
|
||||
- name: Debug mode logs to console
|
||||
code: |-
|
||||
const mockData = {
|
||||
endpoint: 'https://example.com/pixel',
|
||||
eventName: 'test_event',
|
||||
debug: true
|
||||
};
|
||||
|
||||
runCode(mockData);
|
||||
|
||||
// Verify logging occurred
|
||||
assertApi('logToConsole').wasCalledWith('Custom Tag Firing', {
|
||||
endpoint: 'https://example.com/pixel',
|
||||
eventName: 'test_event'
|
||||
});
|
||||
|
||||
|
||||
___NOTES___
|
||||
|
||||
Created using Custom Template Boilerplate
|
||||
@@ -0,0 +1,133 @@
|
||||
___INFO___
|
||||
|
||||
{
|
||||
"type": "MACRO",
|
||||
"id": "cvt_temp_public_id",
|
||||
"version": 1,
|
||||
"securityGroups": [],
|
||||
"displayName": "My Custom Variable",
|
||||
"categories": ["UTILITY"],
|
||||
"brand": {
|
||||
"id": "brand_id",
|
||||
"displayName": "My Brand"
|
||||
},
|
||||
"description": "A custom variable template for...",
|
||||
"containerContexts": [
|
||||
"WEB"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
___TEMPLATE_PARAMETERS___
|
||||
|
||||
[
|
||||
{
|
||||
"type": "TEXT",
|
||||
"name": "cookieName",
|
||||
"displayName": "Cookie Name",
|
||||
"simpleValueType": true,
|
||||
"help": "Name of the cookie to read",
|
||||
"valueValidators": [
|
||||
{
|
||||
"type": "NON_EMPTY"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "TEXT",
|
||||
"name": "defaultValue",
|
||||
"displayName": "Default Value",
|
||||
"simpleValueType": true,
|
||||
"help": "Value to return if cookie is not found"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
___SANDBOXED_JS_FOR_WEB_TEMPLATE___
|
||||
|
||||
// Require necessary APIs
|
||||
const getCookieValues = require('getCookieValues');
|
||||
const logToConsole = require('logToConsole');
|
||||
|
||||
// Access template data
|
||||
const cookieName = data.cookieName;
|
||||
const defaultValue = data.defaultValue;
|
||||
|
||||
// Get cookie values
|
||||
const cookies = getCookieValues(cookieName);
|
||||
|
||||
// Return first cookie value or default
|
||||
if (cookies && cookies.length > 0) {
|
||||
return cookies[0];
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
|
||||
|
||||
___WEB_PERMISSIONS___
|
||||
|
||||
[
|
||||
{
|
||||
"instance": {
|
||||
"key": {
|
||||
"publicId": "get_cookies",
|
||||
"versionId": "1"
|
||||
},
|
||||
"param": [
|
||||
{
|
||||
"key": "cookieAccess",
|
||||
"value": {
|
||||
"type": 1,
|
||||
"string": "any"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"clientAnnotations": {
|
||||
"isEditedByUser": true
|
||||
},
|
||||
"isRequired": true
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
___TESTS___
|
||||
|
||||
scenarios:
|
||||
- name: Returns cookie value when exists
|
||||
code: |-
|
||||
mock('getCookieValues', (name) => {
|
||||
if (name === 'testCookie') {
|
||||
return ['cookieValue'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const mockData = {
|
||||
cookieName: 'testCookie',
|
||||
defaultValue: 'default'
|
||||
};
|
||||
|
||||
let result = runCode(mockData);
|
||||
|
||||
assertThat(result).isEqualTo('cookieValue');
|
||||
|
||||
- name: Returns default value when cookie does not exist
|
||||
code: |-
|
||||
mock('getCookieValues', (name) => {
|
||||
return [];
|
||||
});
|
||||
|
||||
const mockData = {
|
||||
cookieName: 'nonExistentCookie',
|
||||
defaultValue: 'defaultValue'
|
||||
};
|
||||
|
||||
let result = runCode(mockData);
|
||||
|
||||
assertThat(result).isEqualTo('defaultValue');
|
||||
|
||||
|
||||
___NOTES___
|
||||
|
||||
Created using Custom Variable Template Boilerplate
|
||||
4051
skills/gtm-custom-templates/references/custom-templates-guide.md
Normal file
4051
skills/gtm-custom-templates/references/custom-templates-guide.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,357 @@
|
||||
# Sandboxed JavaScript API Reference
|
||||
|
||||
This is the complete Sandboxed JavaScript API reference for Google Tag Manager custom templates. These APIs enable you to build powerful custom templates while maintaining security and performance standards.
|
||||
|
||||
Source: https://developers.google.com/tag-platform/tag-manager/templates/api
|
||||
|
||||
## API Categories
|
||||
|
||||
### Core APIs
|
||||
|
||||
These APIs work with sandboxed JavaScript to build custom templates in Google Tag Manager.
|
||||
|
||||
#### Consent Management
|
||||
|
||||
- **addConsentListener(consentType, listener)** → void
|
||||
- Registers a listener function to execute when the state of the specified consent type changes.
|
||||
|
||||
- **isConsentGranted(consentType)** → boolean
|
||||
- Returns true if the specified consent type is granted.
|
||||
|
||||
- **setDefaultConsentState(consentSettings)** → void
|
||||
- Pushes a default consent update to the data layer.
|
||||
|
||||
- **updateConsentState(consentSettings)** → void
|
||||
- Pushes a consent update to the data layer.
|
||||
|
||||
#### Event Handling
|
||||
|
||||
- **addEventCallback(callback)** → void
|
||||
- Allows you to register a callback function that will be invoked at the end of an event.
|
||||
|
||||
- **callLater(function)** → void
|
||||
- Schedules a call to a function to occur asynchronously.
|
||||
|
||||
#### Window and Global Object Access
|
||||
|
||||
- **aliasInWindow(toPath, fromPath)** → boolean
|
||||
- Lets you create an alias (e.g. window.foo = window.bar).
|
||||
|
||||
- **callInWindow(pathToFunction, args)** → *
|
||||
- Allows you to call functions from a path off the window object, in a policy-controlled way.
|
||||
|
||||
- **copyFromWindow(key)** → *
|
||||
- Copies a variable from window object.
|
||||
|
||||
- **setInWindow(key, value, overrideExisting)** → boolean
|
||||
- Sets the given value in window at the given key.
|
||||
|
||||
#### Queue and Array Creation
|
||||
|
||||
- **createArgumentsQueue(fnKey, arrayKey)** → function
|
||||
- Creates a queue that is populated with argument objects, in support of tag solutions that require it.
|
||||
|
||||
- **createQueue(arrayKey)** → function
|
||||
- Creates an array in window and returns a function that will push values onto that array.
|
||||
|
||||
#### URI and Encoding
|
||||
|
||||
- **decodeUri(encoded_uri)** → string
|
||||
- Decodes any encoded characters in the provided URI.
|
||||
|
||||
- **decodeUriComponent(encoded_uri_component)** → string
|
||||
- Decodes any encoded characters in the provided URI component.
|
||||
|
||||
- **encodeUri(uri)** → string
|
||||
- Returns an encoded Uniform Resource Identifier (URI) by escaping special characters.
|
||||
|
||||
- **encodeUriComponent(str)** → string
|
||||
- Returns an encoded Uniform Resource Identifier (URI) by escaping special characters.
|
||||
|
||||
- **fromBase64(base64EncodedString)** → string
|
||||
- Lets you decode strings from their base64 representation.
|
||||
|
||||
- **toBase64(input)** → string
|
||||
- Lets you encode a string into a base64 representation.
|
||||
|
||||
#### Utility Functions
|
||||
|
||||
- **generateRandom(min, max)** → number
|
||||
- Returns a random number (integer) within the given range.
|
||||
|
||||
- **getContainerVersion()** → object
|
||||
- Returns an object containing data about the current container.
|
||||
|
||||
- **getType(value)** → string
|
||||
- Returns a string describing the given value's type.
|
||||
|
||||
- **logToConsole(obj1, obj2, ...)** → void
|
||||
- Logs arguments to the browser console.
|
||||
|
||||
- **makeInteger(value)** → number
|
||||
- Converts the given value to a number (integer).
|
||||
|
||||
- **makeNumber(value)** → number
|
||||
- Converts the given value to a number.
|
||||
|
||||
- **makeString(value)** → string
|
||||
- Returns the given value as a string.
|
||||
|
||||
- **makeTableMap(tableObj, keyColumnName, valueColumnName)** → object
|
||||
- Converts a simple table object with two columns to a Map.
|
||||
|
||||
- **queryPermission(permission, functionArgs)** → boolean
|
||||
- Query the allowed and narrowed permissions.
|
||||
|
||||
- **require(name)** → function
|
||||
- Imports a built-in function by name.
|
||||
|
||||
#### Data Layer Access
|
||||
|
||||
- **copyFromDataLayer(key, dataLayerVersion)** → *
|
||||
- Returns the value currently assigned to the given key in the data layer.
|
||||
|
||||
- **gtagSet(object)** → void
|
||||
- Pushes a gtag set command to the data layer.
|
||||
|
||||
#### Cookie Management
|
||||
|
||||
- **getCookieValues(name, decode)** → array
|
||||
- Returns the values of all cookies with the given name.
|
||||
|
||||
- **setCookie(name, value, options, encode)** → void
|
||||
- Sets or deletes the cookie with the specified name, value, and options.
|
||||
|
||||
#### URL and Query Parameters
|
||||
|
||||
- **getQueryParameters(queryKey, retrieveAll)** → *
|
||||
- Returns the first or all of the parameters for the current URL's queryKey.
|
||||
|
||||
- **getUrl(component)** → string
|
||||
- Returns a string that represents all or a portion of the current URL.
|
||||
|
||||
- **parseUrl(url)** → object
|
||||
- Returns an object that contains all of a given URL's component parts.
|
||||
|
||||
- **getReferrerUrl(component)** → string
|
||||
- Reads the document object for the referrer and returns a string that represents a portion of the referrer.
|
||||
|
||||
- **getReferrerQueryParameters(queryKey, retrieveAll)** → *
|
||||
- Acts the same way as getQueryParameters, except it acts on the referrer instead of the current URL.
|
||||
|
||||
#### Time Management
|
||||
|
||||
- **getTimestamp()** → number
|
||||
- Returns a number that represents the current time in milliseconds since Unix epoch.
|
||||
|
||||
- **getTimestampMillis()** → number
|
||||
- Returns a number that represents the current time in milliseconds since Unix epoch.
|
||||
|
||||
#### Network and Injection
|
||||
|
||||
- **injectHiddenIframe(url, onSuccess)** → void
|
||||
- Adds an invisible iframe to the page.
|
||||
|
||||
- **injectScript(url, onSuccess, onFailure, cacheToken)** → void
|
||||
- Adds a script tag to the page to load the given URL asynchronously.
|
||||
|
||||
- **sendPixel(url, onSuccess, onFailure)** → void
|
||||
- Makes a GET request to a specified URL endpoint.
|
||||
|
||||
- **sha256(input, onSuccess, onFailure, options)** → void
|
||||
- Calculates the SHA-256 digest of the input.
|
||||
|
||||
#### Document Reading
|
||||
|
||||
- **readCharacterSet()** → string
|
||||
- Returns the value of document.characterSet.
|
||||
|
||||
- **readTitle()** → string
|
||||
- Returns the value of document.title.
|
||||
|
||||
- **readAnalyticsStorage(cookieOptions)** → object
|
||||
- Retrieves the data stored for analytics and returns an object with client_id and sessions.
|
||||
|
||||
#### Built-in Objects
|
||||
|
||||
- **JSON** → object
|
||||
- Returns an object that provides JSON functions.
|
||||
|
||||
- **Math** → object
|
||||
- An object providing Math functions.
|
||||
|
||||
- **Object** → object
|
||||
- Returns an object that provides Object methods.
|
||||
|
||||
- **localStorage** → object
|
||||
- Returns an object with methods for accessing local storage.
|
||||
|
||||
- **templateStorage** → object
|
||||
- Returns an object with methods for accessing template storage.
|
||||
|
||||
---
|
||||
|
||||
### Test APIs
|
||||
|
||||
These APIs work with sandboxed JavaScript tests to build tests for custom templates in Google Tag Manager.
|
||||
|
||||
#### Assertions and Validation
|
||||
|
||||
- **assertApi(apiName)** → object
|
||||
- Returns a matcher object that can be used to fluently make assertions about the given API.
|
||||
|
||||
- **assertThat(actual, opt_message)** → object
|
||||
- Returns an object that can be used to fluently make assertions about a subject's value.
|
||||
|
||||
- **fail(opt_message)** → void
|
||||
- Immediately fails the current test and prints the given message, if supplied.
|
||||
|
||||
#### Mocking
|
||||
|
||||
- **mock(apiName, returnValue)** → void
|
||||
- Allows you to override the behavior of Sandboxed APIs.
|
||||
|
||||
- **mockObject(apiName, objectMock)** → void
|
||||
- Lets you override the behavior of Sandboxed APIs that return an object.
|
||||
|
||||
#### Test Execution
|
||||
|
||||
- **runCode(data)** → *
|
||||
- Runs the code for the template in the current test environment.
|
||||
|
||||
---
|
||||
|
||||
## API Summary
|
||||
|
||||
**Total Core APIs**: 49 functions across 13 categories
|
||||
**Total Test APIs**: 6 functions across 3 categories
|
||||
**Total APIs**: 55 functions
|
||||
|
||||
### Core API Categories
|
||||
1. Consent Management (4 functions)
|
||||
2. Event Handling (2 functions)
|
||||
3. Window and Global Object Access (4 functions)
|
||||
4. Queue and Array Creation (2 functions)
|
||||
5. URI and Encoding (6 functions)
|
||||
6. Utility Functions (8 functions)
|
||||
7. Data Layer Access (2 functions)
|
||||
8. Cookie Management (2 functions)
|
||||
9. URL and Query Parameters (5 functions)
|
||||
10. Time Management (2 functions)
|
||||
11. Network and Injection (4 functions)
|
||||
12. Document Reading (3 functions)
|
||||
13. Built-in Objects (5 functions)
|
||||
|
||||
### Test API Categories
|
||||
1. Assertions and Validation (3 functions)
|
||||
2. Mocking (2 functions)
|
||||
3. Test Execution (1 function)
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Working with the Data Layer
|
||||
```javascript
|
||||
// Get a value from the data layer
|
||||
var userId = copyFromDataLayer('userId');
|
||||
|
||||
// Push a gtag set command
|
||||
gtagSet({
|
||||
'event': 'page_view',
|
||||
'page_title': readTitle()
|
||||
});
|
||||
```
|
||||
|
||||
### Managing Cookies
|
||||
```javascript
|
||||
// Get cookie values
|
||||
var cookieValues = getCookieValues('tracking_id', true);
|
||||
|
||||
// Set a cookie
|
||||
setCookie('my_cookie', 'value123', {
|
||||
'domain': 'example.com',
|
||||
'path': '/',
|
||||
'max-age': 86400
|
||||
});
|
||||
```
|
||||
|
||||
### Working with URLs
|
||||
```javascript
|
||||
// Get URL components
|
||||
var hostname = getUrl('hostname');
|
||||
var pathname = getUrl('pathname');
|
||||
var queryString = getUrl('query');
|
||||
|
||||
// Parse a URL
|
||||
var urlParts = parseUrl('https://example.com/page?param=value');
|
||||
|
||||
// Get query parameters
|
||||
var userParam = getQueryParameters('user_id', false);
|
||||
```
|
||||
|
||||
### Async Operations
|
||||
```javascript
|
||||
// Inject a script
|
||||
injectScript('https://example.com/tracker.js', function() {
|
||||
logToConsole('Script loaded successfully');
|
||||
}, function() {
|
||||
logToConsole('Script failed to load');
|
||||
});
|
||||
|
||||
// Send a pixel
|
||||
sendPixel('https://example.com/track?event=purchase', function() {
|
||||
logToConsole('Pixel sent');
|
||||
}, function() {
|
||||
logToConsole('Pixel failed');
|
||||
});
|
||||
|
||||
// Calculate SHA-256
|
||||
sha256('input_string', function(result) {
|
||||
logToConsole('SHA-256: ' + result);
|
||||
}, function() {
|
||||
logToConsole('Hash calculation failed');
|
||||
});
|
||||
```
|
||||
|
||||
### Consent Management
|
||||
```javascript
|
||||
// Check if consent is granted
|
||||
if (isConsentGranted('analytics_storage')) {
|
||||
// Proceed with analytics
|
||||
}
|
||||
|
||||
// Add a consent listener
|
||||
addConsentListener('analytics_storage', function() {
|
||||
logToConsole('Consent state changed');
|
||||
});
|
||||
|
||||
// Update consent
|
||||
updateConsentState({
|
||||
'analytics_storage': 'granted',
|
||||
'ad_storage': 'denied'
|
||||
});
|
||||
```
|
||||
|
||||
### Testing Custom Templates
|
||||
```javascript
|
||||
// Mock an API
|
||||
mock('sendPixel', undefined);
|
||||
|
||||
// Assert API behavior
|
||||
assertApi('sendPixel').wasCalled();
|
||||
|
||||
// Run template code with test data
|
||||
var result = runCode({
|
||||
'event': 'test_event',
|
||||
'user_id': '12345'
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
- **Official Documentation**: https://developers.google.com/tag-platform/tag-manager/templates/api
|
||||
- **Google Tag Manager**: https://tagmanager.google.com
|
||||
- **GTM Custom Templates Guide**: https://developers.google.com/tag-platform/tag-manager/templates/guide
|
||||
149
skills/gtm-custom-templates/references/template-examples.md
Normal file
149
skills/gtm-custom-templates/references/template-examples.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Custom Template Examples
|
||||
|
||||
<!-- To be populated with practical examples -->
|
||||
|
||||
## Tag Template Examples
|
||||
|
||||
### Simple Pixel Tag Template
|
||||
|
||||
```javascript
|
||||
// Code section
|
||||
const sendPixel = require('sendPixel');
|
||||
const encodeUriComponent = require('encodeUriComponent');
|
||||
const getUrl = require('getUrl');
|
||||
|
||||
const pixelUrl = 'https://example.com/pixel?'
|
||||
+ 'page=' + encodeUriComponent(getUrl())
|
||||
+ '&event=' + encodeUriComponent(data.eventName);
|
||||
|
||||
sendPixel(pixelUrl, data.gtmOnSuccess, data.gtmOnFailure);
|
||||
```
|
||||
|
||||
### HTTP Request Tag Template
|
||||
|
||||
```javascript
|
||||
// Code section
|
||||
const sendHttpRequest = require('sendHttpRequest');
|
||||
const JSON = require('JSON');
|
||||
|
||||
const postBody = JSON.stringify({
|
||||
event: data.eventName,
|
||||
userId: data.userId,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
const options = {
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
method: 'POST',
|
||||
};
|
||||
|
||||
sendHttpRequest(data.endpoint, options, postBody)
|
||||
.then(data.gtmOnSuccess)
|
||||
.catch(data.gtmOnFailure);
|
||||
```
|
||||
|
||||
## Variable Template Examples
|
||||
|
||||
### Cookie Variable Template
|
||||
|
||||
```javascript
|
||||
// Code section
|
||||
const getCookieValues = require('getCookieValues');
|
||||
const cookieName = data.cookieName;
|
||||
const cookies = getCookieValues(cookieName);
|
||||
|
||||
if (cookies.length > 0) {
|
||||
return cookies[0];
|
||||
}
|
||||
|
||||
return data.defaultValue;
|
||||
```
|
||||
|
||||
### LocalStorage Variable Template
|
||||
|
||||
```javascript
|
||||
// Code section
|
||||
const localStorage = require('localStorage');
|
||||
const key = data.localStorageKey;
|
||||
|
||||
return localStorage.getItem(key) || data.defaultValue;
|
||||
```
|
||||
|
||||
### Custom JavaScript Function Variable
|
||||
|
||||
```javascript
|
||||
// Code section
|
||||
const makeTableMap = require('makeTableMap');
|
||||
const lookupTable = makeTableMap(data.tableInput, 'input', 'output');
|
||||
|
||||
const inputValue = data.inputVariable;
|
||||
return lookupTable[inputValue] || data.defaultValue;
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Error Handling
|
||||
|
||||
```javascript
|
||||
const sendHttpRequest = require('sendHttpRequest');
|
||||
const logToConsole = require('logToConsole');
|
||||
|
||||
sendHttpRequest(url, options)
|
||||
.then(response => {
|
||||
logToConsole('Success:', response);
|
||||
data.gtmOnSuccess();
|
||||
})
|
||||
.catch(error => {
|
||||
logToConsole('Error:', error);
|
||||
data.gtmOnFailure();
|
||||
});
|
||||
```
|
||||
|
||||
### Conditional Logic
|
||||
|
||||
```javascript
|
||||
const getType = require('getType');
|
||||
|
||||
if (getType(data.value) === 'undefined') {
|
||||
return data.defaultValue;
|
||||
}
|
||||
|
||||
if (data.value > 0) {
|
||||
return 'positive';
|
||||
} else if (data.value < 0) {
|
||||
return 'negative';
|
||||
} else {
|
||||
return 'zero';
|
||||
}
|
||||
```
|
||||
|
||||
### Data Validation
|
||||
|
||||
```javascript
|
||||
const makeNumber = require('makeNumber');
|
||||
const makeString = require('makeString');
|
||||
|
||||
// Validate and convert input
|
||||
const numericValue = makeNumber(data.inputValue);
|
||||
|
||||
if (numericValue === undefined) {
|
||||
logToConsole('Invalid numeric input');
|
||||
data.gtmOnFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue with validated value
|
||||
const result = numericValue * 2;
|
||||
data.gtmOnSuccess();
|
||||
```
|
||||
|
||||
## Placeholder for Additional Examples
|
||||
|
||||
This file will be enhanced with:
|
||||
- Server-side client template examples
|
||||
- Complex permission configurations
|
||||
- Field validation patterns
|
||||
- Real-world use cases
|
||||
- Community template patterns
|
||||
|
||||
**Status**: ⚠️ To be enhanced with documentation extraction
|
||||
96
skills/gtm-custom-templates/references/template-testing.md
Normal file
96
skills/gtm-custom-templates/references/template-testing.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Tests
|
||||
|
||||
**Source**: https://developers.google.com/tag-platform/tag-manager/templates/tests
|
||||
**Extracted**: 2025-01-09
|
||||
|
||||
## Overview
|
||||
|
||||
Unit tests for Google Tag Manager custom templates help you validate the functionality of your templates. You can create a set of tests for each template that can be run without needing to deploy your tag, which allows you to continuously test your template's behavior during development. Each test can provide sample input values, mock function calls, and assert code behavior.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Unit tests do not check validation rules but you can manually check validation using the **Run Code** button.
|
||||
- Permission checks do not happen on mocked APIs in unit tests.
|
||||
|
||||
## Step-by-Step Guide: Creating a Variable Template with Tests
|
||||
|
||||
This guide creates a variable template that takes an input string and returns the uppercase version of that string.
|
||||
|
||||
### Steps 1-3: Create Template and Add Field
|
||||
1. Create a new variable template. Click **Templates** in the left navigation and click **New** under the **Variable Templates** section.
|
||||
2. Click **Fields**.
|
||||
3. Click **Add Field** and select **Text input**. Name the field \`text1\` and set the display name to _"Text 1"_.
|
||||
|
||||
### Step 4: Add Template Code
|
||||
|
||||
In the **Code** tab, replace the default code with this sandboxed JavaScript:
|
||||
|
||||
\`\`\`javascript
|
||||
let input = data.text1;
|
||||
return input.toUpperCase();
|
||||
\`\`\`
|
||||
|
||||
### Steps 5-7: Create First Test
|
||||
|
||||
5. Click **Tests** to open the testing tab.
|
||||
6. Click **Add Test** and change the test's name from _"Untitled test 1"_ to _"Handles strings"_.
|
||||
7. Click on the expand icon to reveal the test's sandboxed JavaScript editor. Replace the code with:
|
||||
|
||||
\`\`\`javascript
|
||||
// Call runCode to run the template's code with a lowercase string
|
||||
let variableResult = runCode({text1: 'this is a test'});
|
||||
// Validate that the result of runCode is an uppercase string.
|
||||
assertThat(variableResult).isEqualTo('THIS IS A TEST');
|
||||
\`\`\`
|
||||
|
||||
This test passes the string \`'this is a test'\` to the variable and verifies that the variable returns the expected value of \`'THIS IS A TEST'\`. The \`runCode\` API is used to run the template code in the **Code** tab. The argument to \`runCode\` is an object that is used as the data global. The \`assertThat\` API returns an object that can be used to fluently make assertions about a subject's value.
|
||||
|
||||
### Step 8: Run Tests
|
||||
|
||||
Click **▶ Run Tests** to run the test. The output of the test will appear in the Console.
|
||||
|
||||
The **▶ Run Tests** button runs all of the enabled tests in the template, in the order shown. To change the order, use the drag icon. A test can be temporarily enabled or disabled by clicking on the circle to the left of the test name. To run a single test, click the ▶ button that appears when you move the mouse over the test.
|
||||
|
||||
The console should print the total number of tests run and the number of tests that failed, if any. In this case, only one test was run and it should pass.
|
||||
|
||||
### Steps 9-11: Create Second Test for Edge Cases
|
||||
|
||||
9. Click **Add Test** again to add a second test. Change the test's name from _"Untitled test 2"_ to _"Handles undefined"_.
|
||||
10. Click on the test to expand it and reveal the sandboxed JavaScript editor. Enter:
|
||||
|
||||
\`\`\`javascript
|
||||
let variableResult = runCode({});
|
||||
assertThat(variableResult).isEqualTo(undefined);
|
||||
\`\`\`
|
||||
|
||||
11. Click **▶ Run Tests** to run all of the tests at once. The output of the test will appear in the console.
|
||||
|
||||
The _Handles undefined_ test should fail. Congratulations, you found a bug!
|
||||
|
||||
### Steps 12-14: Fix Code and Re-test
|
||||
|
||||
12. Click **Code** to go back and edit the template's sandboxed JavaScript code. Update the code as follows:
|
||||
|
||||
\`\`\`javascript
|
||||
const getType = require('getType');
|
||||
|
||||
let input = data.text1;
|
||||
if (getType(input) !== 'string') {
|
||||
return input;
|
||||
}
|
||||
return input.toUpperCase();
|
||||
\`\`\`
|
||||
|
||||
The updated code follows the best practice of validating the \`input\` variable before using it.
|
||||
|
||||
13. Click **Tests** to go back to the list of test cases.
|
||||
14. Click **▶ Run Tests** to run all of the test cases again. This time the _Handles undefined_ test should pass.
|
||||
15. Click **Save**, and close the Template Editor.
|
||||
|
||||
## Core APIs
|
||||
|
||||
### runCode
|
||||
Executes the template's code with provided sample data object. Arguments are merged into the data global variable used in the template code.
|
||||
|
||||
### assertThat
|
||||
Returns an object that can be used to fluently make assertions about a subject's value. Used for validation in tests.
|
||||
Reference in New Issue
Block a user