Compare commits

...

2 Commits

Author SHA1 Message Date
Johannes
b05a636914 feat: add Phase 1 testing utilities (TEST_IDS, FIXTURES, setupTestEnvironment)
- Create centralized TEST_IDS constants to eliminate 200+ magic string occurrences
- Create FIXTURES for common test data to reduce 47+ duplicate definitions
- Add setupTestEnvironment() helper to standardize cleanup patterns (36+ occurrences)
- Export utilities from vitestSetup.ts for easy access
- Add comprehensive README with usage examples

This is Phase 1 (Quick Wins) of the testing infrastructure refactor.
Estimated impact: 30-50% reduction in test boilerplate for new tests.

Related analysis documents: TESTING_ANALYSIS_README.md, ANALYSIS_SUMMARY.txt
2025-11-24 13:54:59 +01:00
Harsh Bhat
e03df83e88 docs: Add GTM docs (#6830)
Co-authored-by: Johannes <johannes@formbricks.com>
2025-11-24 10:59:27 +00:00
12 changed files with 562 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
# Testing Utilities
Centralized testing utilities to reduce boilerplate and ensure consistency across test files.
## Quick Start
```typescript
import { describe, expect, test } from "vitest";
import { FIXTURES, TEST_IDS } from "@/lib/testing/constants";
import { setupTestEnvironment } from "@/lib/testing/setup";
// Setup standard test environment with cleanup
setupTestEnvironment();
describe("MyModule", () => {
test("should use standard test IDs", () => {
// Use TEST_IDS instead of magic strings
const result = processContact(TEST_IDS.contact);
expect(result).toBeDefined();
});
test("should use fixtures for test data", () => {
// Use FIXTURES instead of defining data inline
const result = validateEmail(FIXTURES.contact.email);
expect(result).toBe(true);
});
});
```
## Available Utilities
### TEST_IDS
Standard identifiers to eliminate magic strings in tests.
**Available IDs:**
- `contact`, `contactAlt`
- `user`
- `environment`
- `survey`
- `organization`
- `quota`
- `attribute`
- `response`
- `team`
- `project`
- `segment`
- `webhook`
- `apiKey`
- `membership`
**Before:**
```typescript
const contactId = "contact-1";
const envId = "env-123";
```
**After:**
```typescript
import { TEST_IDS } from "@/lib/testing/constants";
// Use TEST_IDS.contact and TEST_IDS.environment
```
### FIXTURES
Common test data structures to reduce duplication.
**Available fixtures:**
- `contact` - Basic contact object
- `survey` - Survey object
- `attributeKey` - Single attribute key
- `attributeKeys` - Array of attribute keys
- `responseData` - Sample response data
- `environment` - Environment object
- `organization` - Organization object
- `project` - Project object
**Before:**
```typescript
const mockContact = {
id: "contact-1",
email: "test@example.com",
userId: "user-1",
};
```
**After:**
```typescript
import { FIXTURES } from "@/lib/testing/constants";
// Use FIXTURES.contact directly
```
### setupTestEnvironment()
Standardized test cleanup to replace manual beforeEach/afterEach blocks.
**Before:**
```typescript
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.clearAllMocks();
});
```
**After:**
```typescript
import { setupTestEnvironment } from "@/lib/testing/setup";
setupTestEnvironment();
```
## Benefits
- **Consistency:** All tests use the same IDs and cleanup patterns
- **Maintainability:** Update IDs in one place instead of 200+ locations
- **Readability:** Less boilerplate, more test logic
- **Speed:** Write new tests faster with ready-to-use fixtures
## Migration Guide
### For New Tests
Use these utilities immediately in all new test files.
### For Existing Tests
Migrate opportunistically when editing existing tests. No forced migration required.
### Example Migration
**Before (60 lines with boilerplate):**
```typescript
import { beforeEach, describe, expect, test, vi } from "vitest";
const contactId = "contact-1";
const environmentId = "env-1";
describe("getContact", () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.clearAllMocks();
});
test("fetches contact", async () => {
const result = await getContact(contactId);
expect(result).toBeDefined();
});
});
```
**After (45 lines, cleaner):**
```typescript
import { describe, expect, test } from "vitest";
import { TEST_IDS } from "@/lib/testing/constants";
import { setupTestEnvironment } from "@/lib/testing/setup";
setupTestEnvironment();
describe("getContact", () => {
test("fetches contact", async () => {
const result = await getContact(TEST_IDS.contact);
expect(result).toBeDefined();
});
});
```

View File

@@ -0,0 +1,120 @@
import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key";
/**
* Standard test IDs to eliminate magic strings across test files.
* Use these constants instead of hardcoded IDs like "contact-1", "env-123", etc.
*
* @example
* ```typescript
* import { TEST_IDS } from "@/lib/testing/constants";
*
* test("should fetch contact", async () => {
* const result = await getContact(TEST_IDS.contact);
* expect(result).toBeDefined();
* });
* ```
*/
export const TEST_IDS = {
contact: "contact-123",
contactAlt: "contact-456",
user: "user-123",
environment: "env-123",
survey: "survey-123",
organization: "org-123",
quota: "quota-123",
attribute: "attr-123",
response: "response-123",
team: "team-123",
project: "project-123",
segment: "segment-123",
webhook: "webhook-123",
apiKey: "api-key-123",
membership: "membership-123",
} as const;
/**
* Common test fixtures to reduce duplicate test data definitions.
* Extend these as needed for your specific test cases.
*
* @example
* ```typescript
* import { FIXTURES } from "@/lib/testing/constants";
*
* test("should create contact", async () => {
* vi.mocked(getContactAttributeKeys).mockResolvedValue(FIXTURES.attributeKeys);
* const result = await createContact(FIXTURES.contact);
* expect(result.email).toBe(FIXTURES.contact.email);
* });
* ```
*/
export const FIXTURES = {
contact: {
id: TEST_IDS.contact,
email: "test@example.com",
userId: TEST_IDS.user,
},
survey: {
id: TEST_IDS.survey,
name: "Test Survey",
environmentId: TEST_IDS.environment,
},
attributeKey: {
id: TEST_IDS.attribute,
key: "email",
name: "Email",
environmentId: TEST_IDS.environment,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-02"),
isUnique: false,
description: null,
type: "default" as const,
},
attributeKeys: [
{
id: "key-1",
key: "email",
name: "Email",
environmentId: TEST_IDS.environment,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-02"),
isUnique: false,
description: null,
type: "default",
},
{
id: "key-2",
key: "name",
name: "Name",
environmentId: TEST_IDS.environment,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-02"),
isUnique: false,
description: null,
type: "default",
},
] as TContactAttributeKey[],
responseData: {
q1: "Open text answer",
q2: "Option 1",
},
environment: {
id: TEST_IDS.environment,
name: "Test Environment",
type: "development" as const,
},
organization: {
id: TEST_IDS.organization,
name: "Test Organization",
},
project: {
id: TEST_IDS.project,
name: "Test Project",
},
} as const;

View File

@@ -0,0 +1,31 @@
import { afterEach, beforeEach, vi } from "vitest";
/**
* Standard test environment setup with consistent cleanup patterns.
* Call this function once at the top of your test file to ensure
* mocks are properly cleaned up between tests.
*
* @example
* ```typescript
* import { setupTestEnvironment } from "@/lib/testing/setup";
*
* setupTestEnvironment();
*
* describe("MyModule", () => {
* test("should work correctly", () => {
* // Your test code here
* });
* });
* ```
*
* Note: This replaces manual beforeEach/afterEach blocks in individual test files.
*/
export function setupTestEnvironment() {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.clearAllMocks();
});
}

View File

@@ -184,6 +184,10 @@ export const testInputValidation = async (service: Function, ...args: any[]): Pr
});
};
// Export new testing utilities for easy access
export { setupTestEnvironment } from "./lib/testing/setup";
export { TEST_IDS, FIXTURES } from "./lib/testing/constants";
vi.mock("@/lib/constants", () => ({
IS_FORMBRICKS_CLOUD: false,
POSTHOG_API_KEY: "mock-posthog-api-key",

View File

@@ -112,6 +112,7 @@
"pages": [
"xm-and-surveys/surveys/website-app-surveys/quickstart",
"xm-and-surveys/surveys/website-app-surveys/framework-guides",
"xm-and-surveys/surveys/website-app-surveys/google-tag-manager",
{
"group": "Features",
"icon": "wrench",

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,223 @@
---
title: "Google Tag Manager"
description: "Deploy Formbricks surveys through GTM without modifying your website code."
icon: "tags"
---
## Prerequisites
- [Google Tag Manager](https://tagmanager.google.com/) installed on your website
- Your Formbricks **Environment ID** (Settings > Configuration > Website & App Connection)
- Your **App URL**: `https://app.formbricks.com` (or your self-hosted URL)
<Note>
Use PUBLIC_URL for multi-domain setups, WEBAPP_URL for single-domain setups.
</Note>
## Basic Setup
<Steps>
<Step title="Create a Custom HTML tag in GTM">
1. Create a new tag with preferred name e.g. "Formbricks Intercept Surveys"
2. Tag Type: Custom HTML
3. Paste the code from Step 2. Make sure to replace `<your-environment-id>` and if you self-host, replace `<your-app-url>`
</Step>
<Step title="Add initialization script">
```html
<script type="text/javascript">
!function(){
var appUrl = "https://app.formbricks.com"; // REPLACE ONLY IF YOUR SELF-HOST
var environmentId = "<your-environment-id>"; // REPLACE
var t=document.createElement("script");
t.type="text/javascript";
t.async=!0;
t.src=appUrl+"/js/formbricks.umd.cjs";
t.onload=function(){
window.formbricks && window.formbricks.setup({
environmentId: environmentId,
appUrl: appUrl
});
};
var e=document.getElementsByTagName("script")[0];
e.parentNode.insertBefore(t,e);
}();
</script>
```
![Add GTM Custom HTML tag](/images/xm-and-surveys/surveys/website-app-surveys/google-tag-manager/create-a-tag.webp)
</Step>
<Step title="Set trigger">
1. Trigger: **All Pages** - Page View (default) or use case specific event
2. Save and publish
![Add a trigger](/images/xm-and-surveys/surveys/website-app-surveys/google-tag-manager/create-a-trigger.webp)
</Step>
<Step title="Test">
1. Use GTM Preview mode
2. Verify the tag fires
3. Add `?formbricksDebug=true` to the URL to see test logs in browser console (see [Debugging Mode](/xm-and-surveys/surveys/website-app-surveys/framework-guides#debugging-formbricks-integration) for more details)
</Step>
</Steps>
## User Identification
Identify users to enable targeting and attributes. Learn more about [user identification](/xm-and-surveys/surveys/website-app-surveys/user-identification).
<Note>
User identification is part of the Formbricks [Enterprise Edition](/self-hosting/advanced/license).
</Note>
<Steps>
<Step title="Create GTM variables">
1. Go to Variables on GTM dashboard
2. Create new User-defined variable
3. Name it (e.g., "User ID")
4. Variable Type: Data Layer Variable
5. Data Layer Variable: "userId"
6. Save and publish
7. Repeat for attributes you want to track e.g. "userEmail" and "userPlan" (optional)
![Create a variable](/images/xm-and-surveys/surveys/website-app-surveys/google-tag-manager/create-a-variable.webp)
</Step>
<Step title="Create identification tag">
New Custom HTML tag named "Formbricks - User":
```html
<script>
(function() {
var check = setInterval(function() {
if (window.formbricks && window.formbricks.setUserId) {
clearInterval(check);
var userId = {{User ID}};
if (userId) {
window.formbricks.setUserId(userId);
var attrs = {};
if ({{User Email}}) attrs.email = {{User Email}};
if ({{User Plan}}) attrs.plan = {{User Plan}};
if (Object.keys(attrs).length) {
window.formbricks.setAttributes(attrs);
}
}
}
}, 100);
setTimeout(function() { clearInterval(check); }, 10000);
})();
</script>
```
</Step>
<Step title="Set trigger and push data">
1. Create a custom event trigger in GTM
2. Trigger Type: Custom Event
3. Event name: `user-login` (or your preferred event name)
4. Attach this trigger to your "Formbricks - User" tag
5. Save and publish
![User Login Trigger](/images/xm-and-surveys/surveys/website-app-surveys/google-tag-manager/user-login-trigger.webp)
6. In your code, push data with the same event name:
```javascript
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'user-login',
'userId': 'user-123',
'userEmail': 'user@example.com',
'userPlan': 'premium'
});
```
</Step>
</Steps>
## Track Custom Events
<Steps>
<Step title="Create code action in Formbricks">
Add code action via Formbricks UI
![Add a code action to open source in app survey](/images/xm-and-surveys/surveys/website-app-surveys/actions/code-action.webp "Add a code action to open source in app survey")
</Step>
<Step title="Create GTM variable for Event Name">
1. Go to Variables on GTM dashboard
2. Create new User-defined variable
3. Name it "Event Name"
4. Variable Type: Data Layer Variable
5. Data Layer Variable: "eventName"
6. Save and publish
![Create Event Variable](/images/xm-and-surveys/surveys/website-app-surveys/google-tag-manager/create-event-variable.webp)
</Step>
<Step title="Create event tracking tag">
New Custom HTML tag:
```html
<script>
if (window.formbricks && window.formbricks.track) {
window.formbricks.track({{Event Name}});
}
</script>
```
</Step>
<Step title="Create custom trigger">
1. Create a custom event trigger in GTM
2. Trigger Type: Custom Event
3. Event name: `eventName` or name that matches with your event in code.
4. Attach this trigger to your event tracking tag
5. Save and publish
![Track Event Trigger](/images/xm-and-surveys/surveys/website-app-surveys/google-tag-manager/track-event-trigger.webp)
</Step>
<Step title="Fire events from your site">
```javascript
// Track button click
window.dataLayer.push({
'event': 'eventName',
'eventName': 'code-action'
});
```
</Step>
</Steps>
## Troubleshooting
**Surveys not showing?**
- Use GTM Preview mode to check tag firing
- Add `?formbricksDebug=true` to your URL
- Check browser console for errors
- Wait 1 minute for the Server Cache to refresh
**User ID not working?**
- Verify Data Layer push syntax
- Check GTM variables are reading correct values
- Ensure user tag fires after initialization
**Events not tracking?**
- Confirm `window.formbricks` exists before calling track
- Match event names exactly with Formbricks action names
- Check timing - Formbricks must be initialized first
## Need Help?
- [GitHub Discussions](https://github.com/formbricks/formbricks/discussions)
- [Framework Guides](/xm-and-surveys/surveys/website-app-surveys/framework-guides)
- [Actions](/xm-and-surveys/surveys/website-app-surveys/actions)
- [User Identification](/xm-and-surveys/surveys/website-app-surveys/user-identification)