1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-21 20:04:02 +00:00

unit test achievement manager

This commit is contained in:
✨ Audrey ✨
2025-03-19 11:07:27 -04:00
parent d7909bd87a
commit 723c2f7767
3 changed files with 173 additions and 2 deletions

View File

@@ -0,0 +1,152 @@
import { BehaviorSubject, ReplaySubject, Subject, firstValueFrom } from "rxjs";
import { active } from "./achievement-manager";
import {
ItemCreatedTracker,
TotallyAttachedValidator,
UnboundItemCreatedTracker,
} from "./examples/example-validators";
import { AchievementId, AchievementValidator, MetricId } from "./types";
describe("active", () => {
it("passes through empty achievement sets", async () => {
const metrics$ = new BehaviorSubject<Map<MetricId, number>>(new Map());
const earned$ = new BehaviorSubject<Set<AchievementId>>(new Set());
const validators$ = new Subject<AchievementValidator[]>();
const results$ = new ReplaySubject<AchievementValidator[]>(1);
validators$.pipe(active(metrics$, earned$)).subscribe(results$);
const results = firstValueFrom(results$);
validators$.next([]);
await expect(results).resolves.toEqual([]);
});
it("passes through until-earned validators when earned$ omits the achievement id", async () => {
const metrics$ = new BehaviorSubject<Map<MetricId, number>>(new Map());
const earned$ = new BehaviorSubject<Set<AchievementId>>(new Set());
const validators$ = new Subject<AchievementValidator[]>();
const results$ = new ReplaySubject<AchievementValidator[]>(1);
validators$.pipe(active(metrics$, earned$)).subscribe(results$);
const results = firstValueFrom(results$);
validators$.next([TotallyAttachedValidator]);
await expect(results).resolves.toEqual([TotallyAttachedValidator]);
});
it("filters until-earned validators when earned$ includes the achievement id", async () => {
const metrics$ = new BehaviorSubject<Map<MetricId, number>>(new Map());
const earned$ = new BehaviorSubject<Set<AchievementId>>(
new Set([TotallyAttachedValidator.achievement]),
);
const validators$ = new Subject<AchievementValidator[]>();
const results$ = new ReplaySubject<AchievementValidator[]>(1);
validators$.pipe(active(metrics$, earned$)).subscribe(results$);
const results = firstValueFrom(results$);
validators$.next([TotallyAttachedValidator]);
await expect(results).resolves.toEqual([]);
});
it("passes through threshold validators when metric$ omits its metric and the low threshold isn't defined", async () => {
const metrics$ = new BehaviorSubject<Map<MetricId, number>>(new Map());
const earned$ = new BehaviorSubject<Set<AchievementId>>(
new Set([ItemCreatedTracker.achievement]),
);
const validators$ = new Subject<AchievementValidator[]>();
const results$ = new ReplaySubject<AchievementValidator[]>(1);
validators$.pipe(active(metrics$, earned$)).subscribe(results$);
const results = firstValueFrom(results$);
validators$.next([ItemCreatedTracker]);
await expect(results).resolves.toEqual([ItemCreatedTracker]);
});
it("passes through threshold validators when metric$ includes a metric below the validator's `high` threshold", async () => {
const metrics$ = new BehaviorSubject<Map<MetricId, number>>(
new Map([[ItemCreatedTracker.active.metric, 0]]),
);
const earned$ = new BehaviorSubject<Set<AchievementId>>(
new Set([ItemCreatedTracker.achievement]),
);
const validators$ = new Subject<AchievementValidator[]>();
const results$ = new ReplaySubject<AchievementValidator[]>(1);
validators$.pipe(active(metrics$, earned$)).subscribe(results$);
const results = firstValueFrom(results$);
validators$.next([ItemCreatedTracker]);
await expect(results).resolves.toEqual([ItemCreatedTracker]);
});
it("filters threshold validators when metric$ includes a metric equal to the validator's `high` threshold", async () => {
const metrics$ = new BehaviorSubject<Map<MetricId, number>>(
new Map([[ItemCreatedTracker.active.metric, 1]]),
);
const earned$ = new BehaviorSubject<Set<AchievementId>>(
new Set([ItemCreatedTracker.achievement]),
);
const validators$ = new Subject<AchievementValidator[]>();
const results$ = new ReplaySubject<AchievementValidator[]>(1);
validators$.pipe(active(metrics$, earned$)).subscribe(results$);
const results = firstValueFrom(results$);
validators$.next([ItemCreatedTracker]);
await expect(results).resolves.toEqual([]);
});
it("filters threshold validators when metric$ includes a metric greater than the validator's `high` threshold", async () => {
const metrics$ = new BehaviorSubject<Map<MetricId, number>>(
new Map([[ItemCreatedTracker.active.metric, 2]]),
);
const earned$ = new BehaviorSubject<Set<AchievementId>>(
new Set([ItemCreatedTracker.achievement]),
);
const validators$ = new Subject<AchievementValidator[]>();
const results$ = new ReplaySubject<AchievementValidator[]>(1);
validators$.pipe(active(metrics$, earned$)).subscribe(results$);
const results = firstValueFrom(results$);
validators$.next([ItemCreatedTracker]);
await expect(results).resolves.toEqual([]);
});
it("passes through threshold validators when metric$ includes a metric equal to the validator's `low` threshold", async () => {
const metrics$ = new BehaviorSubject<Map<MetricId, number>>(
new Map([[UnboundItemCreatedTracker.active.metric, 2]]),
);
const earned$ = new BehaviorSubject<Set<AchievementId>>(
new Set([UnboundItemCreatedTracker.achievement]),
);
const validators$ = new Subject<AchievementValidator[]>();
const results$ = new ReplaySubject<AchievementValidator[]>(1);
validators$.pipe(active(metrics$, earned$)).subscribe(results$);
const results = firstValueFrom(results$);
validators$.next([UnboundItemCreatedTracker]);
await expect(results).resolves.toEqual([UnboundItemCreatedTracker]);
});
it("filters threshold validators when metric$ includes a metric below to the validator's `low` threshold", async () => {
const metrics$ = new BehaviorSubject<Map<MetricId, number>>(
new Map([[UnboundItemCreatedTracker.active.metric, 0]]),
);
const earned$ = new BehaviorSubject<Set<AchievementId>>(
new Set([UnboundItemCreatedTracker.achievement]),
);
const validators$ = new Subject<AchievementValidator[]>();
const results$ = new ReplaySubject<AchievementValidator[]>(1);
validators$.pipe(active(metrics$, earned$)).subscribe(results$);
const results = firstValueFrom(results$);
validators$.next([UnboundItemCreatedTracker]);
await expect(results).resolves.toEqual([]);
});
});

View File

@@ -25,7 +25,7 @@ function active(
// monitor disabled if outside of threshold
const progress = (m.active.metric && metrics.get(m.active.metric)) || 0;
if (progress > (m.active.high ?? Number.POSITIVE_INFINITY)) {
if (progress >= (m.active.high ?? Number.POSITIVE_INFINITY)) {
return false;
} else if (progress < (m.active.low ?? 0)) {
return false;

View File

@@ -7,6 +7,7 @@ const CredentialGeneratedProgress = "credential-generated" as MetricId;
const TotallyAttachedAchievement = "totally-attached" as AchievementId;
const ItemCreatedMetric = "item-created-metric" as AchievementId;
const UnboundItemCreatedMetric = "unbound-item-created-metric" as AchievementId;
const ItemCreatedAchievement = "item-created" as AchievementId;
const ThreeItemsCreatedAchievement = "three-vault-items-created" as AchievementId;
const FiveItemsCreatedAchievement = "five-vault-items-created" as AchievementId;
@@ -49,6 +50,22 @@ const ItemCreatedTracker = {
},
} satisfies AchievementValidator;
const UnboundItemCreatedTracker = {
achievement: UnboundItemCreatedMetric,
name: `[TRACKER] ${ItemCreatedProgress}`,
description: `Measures ${ItemCreatedProgress}`,
validator: Type.Threshold,
active: { metric: ItemCreatedProgress, low: 1 },
hidden: true,
trigger(item) {
return item.action === "vault-item-added";
},
measure(item, progress) {
const value = 1 + (progress.get(ItemCreatedProgress) ?? 0);
return [progressEvent(ItemCreatedProgress, value)];
},
} satisfies AchievementValidator;
const ItemCreatedValidator = {
achievement: ItemCreatedAchievement,
name: "What an item!",
@@ -92,7 +109,7 @@ const FiveItemsCreatedValidator = {
name: "fiiivvve GoOoOoOolllllllD RIIIIIINGS!!!!!!",
description: "Add five items to your vault",
validator: Type.Threshold,
active: { metric: ItemCreatedProgress, low: 4, high: 5 },
active: { metric: ItemCreatedProgress, low: 3, high: 5 },
hidden: false,
trigger(item) {
return item.action === "vault-item-added";
@@ -112,6 +129,8 @@ export {
TotallyAttachedValidator,
ItemCreatedMetric,
ItemCreatedTracker,
UnboundItemCreatedMetric,
UnboundItemCreatedTracker,
ItemCreatedProgress,
ItemCreatedAchievement,
ItemCreatedValidator,