Skip to content

Commit 8d6a545

Browse files
jaredlockhartclaude
andcommitted
docs: add missing FeatureHolder methods to FML spec
Because * Several FeatureHolder methods in the source are undocumented in the FML spec: recordExperimentExposure, recordMalformedConfiguration, toJSONObject, and the testing helpers * Co-enrollment exposure recording pattern was only in the co-enrollment doc, not the main FML reference This commit * Adds recordExperimentExposure(slug) for co-enrolling features with Swift and Kotlin examples * Adds recordMalformedConfiguration(partId) for reporting invalid experiment configurations as Glean telemetry * Adds toJSONObject() for getting raw JSON representation * Adds testing helpers section: withCachedValue, withInitializer, withSdk, isUnderTest with examples and cross-link to Android integration guide Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 93fdf4c commit 8d6a545

1 file changed

Lines changed: 126 additions & 0 deletions

File tree

docs/technical-reference/fml/fml-spec.mdx

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,132 @@ FxNimbus.features.appMenu.recordExposure()
160160
> The user is exposed to the hamburger menu icon every time the toolbar is shown, but the menu items only when the menu is opened.
161161
> This would suggest that the hamburger menu icon needs to be specified in a different feature, e.g. the `toolbar` feature.
162162
163+
For [co-enrolling features](/technical-reference/fml/coenrolling-features), you must specify which experiment the exposure is for:
164+
165+
<Tabs
166+
defaultValue="swift"
167+
values={[
168+
{ label: "Swift", value: "swift" },
169+
{ label: "Kotlin", value: "kotlin" },
170+
]
171+
}>
172+
<TabItem value="swift">
173+
174+
```swift
175+
FxNimbus.shared.features.messaging.recordExperimentExposure(slug)
176+
```
177+
178+
</TabItem>
179+
<TabItem value="kotlin">
180+
181+
```kotlin
182+
FxNimbus.features.messaging.recordExperimentExposure(slug)
183+
```
184+
185+
</TabItem>
186+
</Tabs>
187+
188+
### Reporting malformed configuration
189+
190+
If your app detects that a feature configuration from an experiment is invalid, you can report it as telemetry. The `partId` parameter identifies which part of the configuration is malformed.
191+
192+
<Tabs
193+
defaultValue="swift"
194+
values={[
195+
{ label: "Swift", value: "swift" },
196+
{ label: "Kotlin", value: "kotlin" },
197+
]
198+
}>
199+
<TabItem value="swift">
200+
201+
```swift
202+
FxNimbus.shared.features.newtab.recordMalformedConfiguration(partId: "invalid-card")
203+
```
204+
205+
</TabItem>
206+
<TabItem value="kotlin">
207+
208+
```kotlin
209+
FxNimbus.features.newtab.recordMalformedConfiguration(partId = "invalid-card")
210+
```
211+
212+
</TabItem>
213+
</Tabs>
214+
215+
### Getting the JSON representation
216+
217+
You can get the raw JSON representation of a feature's configuration with `toJSONObject()`:
218+
219+
<Tabs
220+
defaultValue="swift"
221+
values={[
222+
{ label: "Swift", value: "swift" },
223+
{ label: "Kotlin", value: "kotlin" },
224+
]
225+
}>
226+
<TabItem value="swift">
227+
228+
```swift
229+
let json = FxNimbus.shared.features.newtab.toJSONObject()
230+
```
231+
232+
</TabItem>
233+
<TabItem value="kotlin">
234+
235+
```kotlin
236+
val json = FxNimbus.features.newtab.toJSONObject()
237+
```
238+
239+
</TabItem>
240+
</Tabs>
241+
242+
### Testing with hardcoded values
243+
244+
Each feature holder provides testing methods to inject values directly, without a running Nimbus SDK:
245+
246+
<Tabs
247+
defaultValue="swift"
248+
values={[
249+
{ label: "Swift", value: "swift" },
250+
{ label: "Kotlin", value: "kotlin" },
251+
]
252+
}>
253+
<TabItem value="swift">
254+
255+
```swift
256+
// Inject a hardcoded configuration for testing
257+
FxNimbus.shared.features.newtab.withCachedValue(myTestConfig)
258+
259+
// Check if running under test
260+
if FxNimbus.shared.features.newtab.isUnderTest() {
261+
// skip network calls, etc.
262+
}
263+
```
264+
265+
</TabItem>
266+
<TabItem value="kotlin">
267+
268+
```kotlin
269+
// Inject a hardcoded configuration for testing
270+
FxNimbus.features.newtab.withCachedValue(myTestConfig)
271+
272+
// Check if running under test
273+
if (FxNimbus.features.newtab.isUnderTest()) {
274+
// skip network calls, etc.
275+
}
276+
```
277+
278+
</TabItem>
279+
</Tabs>
280+
281+
Available testing methods:
282+
- **`withCachedValue(value)`** — overwrite the cached configuration with a test value.
283+
- **`withInitializer(create)`** — change how `Variables` are mapped to the feature object (clears cache).
284+
- **`withSdk(getSdk)`** — reset the SDK connection (clears cache).
285+
- **`isUnderTest()`** — returns `true` when the SDK is a `HardcodedNimbusFeatures` instance.
286+
287+
See the [Android integration guide](/platform-guides/android/integration#unit-and-ui-testing-with-hardcodednimbusfeatures) for a complete testing example using `HardcodedNimbusFeatures`.
288+
163289
### Identifier cases
164290

165291
All the examples below use `kebab-case` for identifiers. When these identifiers are used to generate code, they are transformed to the language-specific casing. For example, a feature is specified in the FML as being called `spotlight-search`, but would be referred to in Swift as `spotlightSearch`.

0 commit comments

Comments
 (0)