Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ internal readonly struct EventSpec

internal static readonly EventSpec[] Catalogue =
{
new EventSpec("sign_up", new[] { EventField.Text("method", optional: true) }),
new EventSpec("sign_in", new[] { EventField.Text("method", optional: true) }),
new EventSpec("email_acquired", new[] { EventField.Text("source", optional: true) }),
new EventSpec("wishlist_add", new[] {
EventField.Text("gameId"),
EventField.Text("source", optional: true),
EventField.Text("platform", optional: true),
new EventSpec(SampleAppCustomEvents.SignUp, new[] { EventField.Text(SampleAppCustomEventPropertyKeys.Method, optional: true) }),
new EventSpec(SampleAppCustomEvents.SignIn, new[] { EventField.Text(SampleAppCustomEventPropertyKeys.Method, optional: true) }),
new EventSpec(SampleAppCustomEvents.EmailAcquired, new[] { EventField.Text(SampleAppCustomEventPropertyKeys.Source, optional: true) }),
new EventSpec(SampleAppCustomEvents.WishlistAdd, new[] {
EventField.Text(SampleAppCustomEventPropertyKeys.GameId),
EventField.Text(SampleAppCustomEventPropertyKeys.Source, optional: true),
EventField.Text(SampleAppCustomEventPropertyKeys.Platform, optional: true),
}),
new EventSpec("wishlist_remove", new[] { EventField.Text("gameId") }),
new EventSpec(SampleAppCustomEvents.WishlistRemove, new[] { EventField.Text(SampleAppCustomEventPropertyKeys.GameId) }),
new EventSpec(EventNames.Purchase, new[] {
EventField.Text(EventPropertyKeys.Currency),
EventField.Number(EventPropertyKeys.Value),
Expand Down Expand Up @@ -91,16 +91,16 @@ internal readonly struct EventSpec
EventField.Text(EventPropertyKeys.ItemId, optional: true),
}),
new EventSpec(EventNames.MilestoneReached, new[] { EventField.Text(EventPropertyKeys.Name) }),
new EventSpec("game_page_viewed", new[] {
EventField.Text("gameId"),
EventField.Text("gameName", optional: true),
EventField.Text("slug", optional: true),
new EventSpec(SampleAppCustomEvents.GamePageViewed, new[] {
EventField.Text(SampleAppCustomEventPropertyKeys.GameId),
EventField.Text(SampleAppCustomEventPropertyKeys.GameName, optional: true),
EventField.Text(SampleAppCustomEventPropertyKeys.Slug, optional: true),
}),
new EventSpec("link_clicked", new[] {
EventField.Text("url"),
EventField.Text("label", optional: true),
EventField.Text("source", optional: true),
EventField.Text("gameId", optional: true),
new EventSpec(SampleAppCustomEvents.LinkClicked, new[] {
EventField.Text(SampleAppCustomEventPropertyKeys.Url),
EventField.Text(SampleAppCustomEventPropertyKeys.Label, optional: true),
EventField.Text(SampleAppCustomEventPropertyKeys.Source, optional: true),
EventField.Text(SampleAppCustomEventPropertyKeys.GameId, optional: true),
}),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ private void OnPage() => RunAndLog(SampleAppUi.LogLabels.Page, () =>
{
GuardConsentForTrack();
var screen = SceneManager.GetActiveScene().name;
var props = new Dictionary<string, object> { ["path"] = screen };
ImmutableAudience.Track("screen_viewed", props);
var props = new Dictionary<string, object> { [SampleAppCustomEventPropertyKeys.Path] = screen };
ImmutableAudience.Track(SampleAppCustomEvents.ScreenViewed, props);
return Json.Serialize(props, 2);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Immutable.Audience.Samples.SampleApp
{
// Sample-only event names that demonstrate the custom Track pattern.
internal static class SampleAppCustomEvents
{
internal const string SignUp = "sign_up";
internal const string SignIn = "sign_in";
internal const string EmailAcquired = "email_acquired";
internal const string WishlistAdd = "wishlist_add";
internal const string WishlistRemove = "wishlist_remove";
internal const string GamePageViewed = "game_page_viewed";
internal const string LinkClicked = "link_clicked";
internal const string ScreenViewed = "screen_viewed";
}

// Property keys for the sample-app demo events.
internal static class SampleAppCustomEventPropertyKeys
{
internal const string Method = "method";
internal const string Source = "source";
internal const string GameId = "gameId";
internal const string Platform = "platform";
internal const string GameName = "gameName";
internal const string Slug = "slug";
internal const string Url = "url";
internal const string Label = "label";
internal const string Path = "path";
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ public IEnumerator TypedEvent_Resource_FlushReportsOk()
{
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(EventNames.Resource), root =>
{
root.Q<TextField>(SampleAppUi.TypedEventField(EventNames.Resource, "currency")).value = "GOLD";
root.Q<TextField>(SampleAppUi.TypedEventField(EventNames.Resource, "amount")).value = "100";
root.Q<TextField>(SampleAppUi.TypedEventField(EventNames.Resource, EventPropertyKeys.Currency)).value = "GOLD";
root.Q<TextField>(SampleAppUi.TypedEventField(EventNames.Resource, EventPropertyKeys.Amount)).value = "100";
});
}

Expand All @@ -162,8 +162,8 @@ public IEnumerator TypedEvent_Purchase_FlushReportsOk()
{
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(EventNames.Purchase), root =>
{
root.Q<TextField>(SampleAppUi.TypedEventField(EventNames.Purchase, "currency")).value = "USD";
root.Q<TextField>(SampleAppUi.TypedEventField(EventNames.Purchase, "value")).value = "9.99";
root.Q<TextField>(SampleAppUi.TypedEventField(EventNames.Purchase, EventPropertyKeys.Currency)).value = "USD";
root.Q<TextField>(SampleAppUi.TypedEventField(EventNames.Purchase, EventPropertyKeys.Value)).value = "9.99";
});
}

Expand All @@ -172,7 +172,7 @@ public IEnumerator TypedEvent_MilestoneReached_FlushReportsOk()
{
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(EventNames.MilestoneReached), root =>
{
root.Q<TextField>(SampleAppUi.TypedEventField(EventNames.MilestoneReached, "name")).value = "il2cpp_smoke";
root.Q<TextField>(SampleAppUi.TypedEventField(EventNames.MilestoneReached, EventPropertyKeys.Name)).value = "il2cpp_smoke";
});
}

Expand Down Expand Up @@ -403,54 +403,54 @@ public IEnumerator IdentifyTraits_AfterIdentify_FlushReportsOk()
[UnityTest]
public IEnumerator TypedEvent_SignUp_FlushReportsOk()
{
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("sign_up"));
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.SignUp));
}

[UnityTest]
public IEnumerator TypedEvent_SignIn_FlushReportsOk()
{
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("sign_in"));
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.SignIn));
}

[UnityTest]
public IEnumerator TypedEvent_EmailAcquired_FlushReportsOk()
{
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("email_acquired"));
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.EmailAcquired));
}

[UnityTest]
public IEnumerator TypedEvent_WishlistAdd_FlushReportsOk()
{
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("wishlist_add"), root =>
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.WishlistAdd), root =>
{
root.Q<TextField>(SampleAppUi.TypedEventField("wishlist_add", "gameId")).value = "il2cpp_game_1";
root.Q<TextField>(SampleAppUi.TypedEventField(SampleAppCustomEvents.WishlistAdd, SampleAppCustomEventPropertyKeys.GameId)).value = "il2cpp_game_1";
});
}

[UnityTest]
public IEnumerator TypedEvent_WishlistRemove_FlushReportsOk()
{
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("wishlist_remove"), root =>
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.WishlistRemove), root =>
{
root.Q<TextField>(SampleAppUi.TypedEventField("wishlist_remove", "gameId")).value = "il2cpp_game_1";
root.Q<TextField>(SampleAppUi.TypedEventField(SampleAppCustomEvents.WishlistRemove, SampleAppCustomEventPropertyKeys.GameId)).value = "il2cpp_game_1";
});
}

[UnityTest]
public IEnumerator TypedEvent_GamePageViewed_FlushReportsOk()
{
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("game_page_viewed"), root =>
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.GamePageViewed), root =>
{
root.Q<TextField>(SampleAppUi.TypedEventField("game_page_viewed", "gameId")).value = "il2cpp_game_1";
root.Q<TextField>(SampleAppUi.TypedEventField(SampleAppCustomEvents.GamePageViewed, SampleAppCustomEventPropertyKeys.GameId)).value = "il2cpp_game_1";
});
}

[UnityTest]
public IEnumerator TypedEvent_LinkClicked_FlushReportsOk()
{
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent("link_clicked"), root =>
yield return DriveTypedEventAndFlush(SampleAppUi.Buttons.TypedEvent(SampleAppCustomEvents.LinkClicked), root =>
{
root.Q<TextField>(SampleAppUi.TypedEventField("link_clicked", "url")).value = "https://example.com/il2cpp";
root.Q<TextField>(SampleAppUi.TypedEventField(SampleAppCustomEvents.LinkClicked, SampleAppCustomEventPropertyKeys.Url)).value = "https://example.com/il2cpp";
});
}

Expand Down
1 change: 1 addition & 0 deletions src/Packages/Audience/Runtime/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Immutable.Audience.Runtime.Tests")]
[assembly: InternalsVisibleTo("Immutable.Audience.Editor.Tests")]
[assembly: InternalsVisibleTo("Immutable.Audience.Unity")]

// First-party SampleApp reaches Json.Serialize and
Expand Down
4 changes: 4 additions & 0 deletions src/Packages/Audience/Runtime/Unity/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;

// Editor-only test fixture for DeviceCollector reads internals of this assembly.
[assembly: InternalsVisibleTo("Immutable.Audience.Editor.Tests")]
11 changes: 11 additions & 0 deletions src/Packages/Audience/Runtime/Unity/AssemblyInfo.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

113 changes: 113 additions & 0 deletions src/Packages/Audience/Tests/Editor/DeviceCollectorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#nullable enable

using System.Collections.Generic;
using NUnit.Framework;
using Immutable.Audience.Unity;

namespace Immutable.Audience.Tests.Editor
{
// Editor-only (DeviceCollector needs a Unity domain; skipped by the headless dotnet build).
// Pins emitted payload keys against GameLaunchPropertyKeys and ContextKeys.
[TestFixture]
internal class DeviceCollectorTests
{
[Test]
public void CollectGameLaunchProperties_EmitsTheExpectedKeySet()
{
var props = DeviceCollector.CollectGameLaunchProperties();

// Always-present keys. ScreenDpi is conditional (0 on some Linux WMs).
CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.Platform);
CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.Version);
CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.BuildGuid);
CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.UnityVersion);
CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.OsFamily);
CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.DeviceModel);
CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.Gpu);
CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.GpuVendor);
CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.Cpu);
CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.CpuCores);
CollectionAssert.Contains(props.Keys, GameLaunchPropertyKeys.RamMb);
}

[Test]
public void CollectGameLaunchProperties_EmitsNoUnknownKeys()
{
// Confirms the payload only carries known GameLaunchPropertyKeys entries.
var allowed = new HashSet<string>
{
GameLaunchPropertyKeys.Platform,
GameLaunchPropertyKeys.Version,
GameLaunchPropertyKeys.BuildGuid,
GameLaunchPropertyKeys.UnityVersion,
GameLaunchPropertyKeys.OsFamily,
GameLaunchPropertyKeys.DeviceModel,
GameLaunchPropertyKeys.Gpu,
GameLaunchPropertyKeys.GpuVendor,
GameLaunchPropertyKeys.Cpu,
GameLaunchPropertyKeys.CpuCores,
GameLaunchPropertyKeys.RamMb,
GameLaunchPropertyKeys.ScreenDpi,
};

var props = DeviceCollector.CollectGameLaunchProperties();
foreach (var key in props.Keys)
Assert.IsTrue(allowed.Contains(key),
$"DeviceCollector.CollectGameLaunchProperties emitted unknown key '{key}' "
+ "with no matching GameLaunchPropertyKeys constant");
}

[Test]
public void CollectGameLaunchProperties_TruncatesStringValuesToMaxFieldLength()
{
// Every string value respects MaxFieldLength; an untruncated .ToString() would fail here.
var props = DeviceCollector.CollectGameLaunchProperties();
foreach (var kv in props)
{
if (kv.Value is string s)
Assert.LessOrEqual(s.Length, Constants.MaxFieldLength,
$"GameLaunchPropertyKeys.{kv.Key} value exceeds Constants.MaxFieldLength");
}
}

[Test]
public void CollectContext_EmitsTheExpectedKeySet()
{
var ctx = DeviceCollector.CollectContext();

// UserAgent is unconditional. Timezone / Locale / Screen are
// best-effort and may be absent under unusual hosts.
CollectionAssert.Contains(ctx.Keys, ContextKeys.UserAgent);
}

[Test]
public void CollectContext_EmitsNoUnknownKeys()
{
var allowed = new HashSet<string>
{
ContextKeys.UserAgent,
ContextKeys.Timezone,
ContextKeys.Locale,
ContextKeys.Screen,
};

var ctx = DeviceCollector.CollectContext();
foreach (var key in ctx.Keys)
Assert.IsTrue(allowed.Contains(key),
$"DeviceCollector.CollectContext emitted unknown key '{key}' "
+ "with no matching ContextKeys constant");
}

[Test]
public void CollectContext_TruncatesStringValuesToMaxFieldLength()
{
var ctx = DeviceCollector.CollectContext();
foreach (var kv in ctx)
{
if (kv.Value is string s)
Assert.LessOrEqual(s.Length, Constants.MaxFieldLength,
$"ContextKeys.{kv.Key} value exceeds Constants.MaxFieldLength");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "Immutable.Audience.Editor.Tests",
"rootNamespace": "Immutable.Audience.Tests.Editor",
"references": [
"Immutable.Audience.Runtime",
"Immutable.Audience.Unity",
"UnityEditor.TestRunner",
"UnityEngine.TestRunner"
],
"includePlatforms": ["Editor"],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": ["nunit.framework.dll"],
"autoReferenced": false,
"defineConstraints": ["UNITY_INCLUDE_TESTS"],
"versionDefines": [],
"noEngineReferences": false
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions src/Packages/Audience/Tests/Runtime/ConstantsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ public void LibraryVersion_MatchesPackageJson()
"Constants.LibraryVersion must match package.json version");
}

[Test]
public void LibraryName_MatchesPackageJson()
{
// Same idea as LibraryVersion: fails the build if
// Constants.LibraryName drifts from package.json "name".
var packageJson = ReadPackageJson();
var parsed = JsonReader.DeserializeObject(packageJson);

Assert.IsTrue(parsed.TryGetValue("name", out var nameObj),
"package.json is missing a \"name\" field");
Assert.AreEqual(Constants.LibraryName, nameObj,
"Constants.LibraryName must match package.json name");
}

private static string ReadPackageJson()
{
// Walk up from the test binary location looking for the Audience
Expand Down
Loading
Loading