Skip to content

Commit

Permalink
feat: make data generic
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsiemens committed Nov 22, 2024
1 parent 8e00699 commit c5c6fc3
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,32 @@ import { UserPreferenceScopeType } from 'src/services/user-preferences/models/de
import { type UserPreferenceDefinition } from 'src/services/user-preferences/models/definitions/user-preference-definition'
import {
makeBuilderFromDefinition,
type TestUserPreference,
TestUserPreferenceDefinitionsFakeFactory,
TestUserPreferenceId,
type TestUserPreferencesFakeBuilder,
TestUserPreferencesFakeFactory,
} from 'src/services/user-preferences/user-preferences-service.spec'

describe('When testing CompositeUserPreferencesService', () => {
let compositeUserPreferencesService: CompositeUserPreferencesService<TestUserPreferenceId>
let userPreferences: UserPreferences<TestUserPreferenceId>
let compositeUserPreferencesService: CompositeUserPreferencesService<TestUserPreferenceId, TestUserPreference>
let userPreferences: UserPreferences<TestUserPreferenceId, TestUserPreference>
let expectedScope: UserPreferenceScope
let definitions: UserPreferenceDefinitions<TestUserPreferenceId>
let definitions: UserPreferenceDefinitions<TestUserPreferenceId, TestUserPreference>

beforeEach(() => {
definitions = TestUserPreferenceDefinitionsFakeFactory() as UserPreferenceDefinitions<TestUserPreferenceId>
definitions = TestUserPreferenceDefinitionsFakeFactory() as UserPreferenceDefinitions<
TestUserPreferenceId,
TestUserPreference
>
const prefsBuilder = makeBuilderFromDefinition(definitions)
userPreferences = TestUserPreferencesFakeFactory([prefsBuilder]) as UserPreferences<TestUserPreferenceId>
userPreferences = TestUserPreferencesFakeFactory([prefsBuilder]) as UserPreferences<
TestUserPreferenceId,
TestUserPreference
>
expectedScope = Object.keys(userPreferences)[0] as UserPreferenceScope

compositeUserPreferencesService = new CompositeUserPreferencesService<TestUserPreferenceId>()
compositeUserPreferencesService = new CompositeUserPreferencesService<TestUserPreferenceId, TestUserPreference>()
})

describe('and getting scoped user preferences', () => {
Expand Down Expand Up @@ -83,16 +90,17 @@ describe('When testing CompositeUserPreferencesService', () => {
{ wantsRandom: true },
{ wantsRandom: true },
]
userPreferences = TestUserPreferencesFakeFactory(
userPreferencesBuilder,
) as UserPreferences<TestUserPreferenceId>
userPreferences = TestUserPreferencesFakeFactory(userPreferencesBuilder) as UserPreferences<
TestUserPreferenceId,
TestUserPreference
>
definitions = TestUserPreferenceDefinitionsFakeFactory([
{
id: TestUserPreferenceId.Default,
isOptedInByDefault: true,
allowedScope,
},
]) as UserPreferenceDefinitions<TestUserPreferenceId>
]) as UserPreferenceDefinitions<TestUserPreferenceId, TestUserPreference>

// act
const actualScopedUserPreferences = compositeUserPreferencesService.getScopedUserPreferences(
Expand Down Expand Up @@ -140,9 +148,10 @@ describe('When testing CompositeUserPreferencesService', () => {
{ wantsRandom: true },
{ wantsRandom: true },
]
userPreferences = TestUserPreferencesFakeFactory(
userPreferencesBuilder,
) as UserPreferences<TestUserPreferenceId>
userPreferences = TestUserPreferencesFakeFactory(userPreferencesBuilder) as UserPreferences<
TestUserPreferenceId,
TestUserPreference
>

// act
const expectedPreferenceValue = !testPreferenceValue
Expand Down Expand Up @@ -182,7 +191,7 @@ describe('When testing CompositeUserPreferencesService', () => {
const expectedData = { test: 'test-data' }
const updatingId = TestUserPreferenceId.Default

userPreferences = {} satisfies UserPreferences<TestUserPreferenceId>
userPreferences = {} satisfies UserPreferences<TestUserPreferenceId, TestUserPreference>

// act
const actualUserPreferences = compositeUserPreferencesService.getUpdatedUserPreferenceStorageObject(
Expand All @@ -203,8 +212,8 @@ describe('When testing CompositeUserPreferencesService', () => {
})
})

function getFirstDefinition(definitions: UserPreferenceDefinitions<TestUserPreferenceId>): {
definition?: UserPreferenceDefinition
function getFirstDefinition(definitions: UserPreferenceDefinitions<TestUserPreferenceId, TestUserPreference>): {
definition?: UserPreferenceDefinition<TestUserPreference>
preferenceId: TestUserPreferenceId
} {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import { UserPreferenceScopeType } from 'src/services/user-preferences/models/de
import cloneDeep from 'lodash/cloneDeep'
import { type UserPreferenceDefinition } from 'src/services/user-preferences/models/definitions/user-preference-definition'

export class CompositeUserPreferencesService<TPreferenceIds extends PropertyKey> {
export class CompositeUserPreferencesService<TPreferenceIds extends PropertyKey, T> {
public getScopedUserPreferences(
storedPreferences: UserPreferences<TPreferenceIds>,
storedPreferences: UserPreferences<TPreferenceIds, T>,
currentScope: UserPreferenceScope,
definitions: UserPreferenceDefinitions<TPreferenceIds>,
): CompositeUserPreferences<TPreferenceIds> {
definitions: UserPreferenceDefinitions<TPreferenceIds, T>,
): CompositeUserPreferences<TPreferenceIds, T> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const entriesByIdAndUserPreference = Object.entries(definitions).map<[TPreferenceIds, UserPreference]>(
const entriesByIdAndUserPreference = Object.entries(definitions).map<[TPreferenceIds, UserPreference<T>]>(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
this.createUserPreferenceEntryFromDefinition.bind(this, storedPreferences, currentScope),
Expand All @@ -30,11 +30,11 @@ export class CompositeUserPreferencesService<TPreferenceIds extends PropertyKey>
public getUpdatedUserPreferenceStorageObject(
preferenceId: TPreferenceIds,
isOptedIn: boolean,
data: any,
data: T,
currentScope: UserPreferenceScope,
currentPreferences: UserPreferences<TPreferenceIds>,
currentPreferences: UserPreferences<TPreferenceIds, T>,
allowedScope: UserPreferenceScopeType,
): UserPreferences<TPreferenceIds> {
): UserPreferences<TPreferenceIds, T> {
const userPreferencesToUpdate = currentPreferences ? cloneDeep(currentPreferences) : {}

const effectiveScope = this.getEffectiveScope(currentScope, allowedScope)
Expand All @@ -53,10 +53,10 @@ export class CompositeUserPreferencesService<TPreferenceIds extends PropertyKey>
}

private createUserPreferenceEntryFromDefinition(
storedPreferences: UserPreferences<TPreferenceIds>,
storedPreferences: UserPreferences<TPreferenceIds, T>,
currentScope: UserPreferenceScope,
[definedUserPreferenceId, definition]: [TPreferenceIds, UserPreferenceDefinition],
): [TPreferenceIds, UserPreference] {
[definedUserPreferenceId, definition]: [TPreferenceIds, UserPreferenceDefinition<T>],
): [TPreferenceIds, UserPreference<T>] {
if (!storedPreferences) {
const userPreferenceDefault = { optedIn: definition.isOptedInByDefault, data: definition.defaultData }
return this.createPreferenceEntry(definedUserPreferenceId, userPreferenceDefault)
Expand Down Expand Up @@ -112,25 +112,25 @@ export class CompositeUserPreferencesService<TPreferenceIds extends PropertyKey>

private createPreferenceEntry(
userPreferenceId: TPreferenceIds,
userPreference: UserPreference,
): [TPreferenceIds, UserPreference] {
userPreference: UserPreference<T>,
): [TPreferenceIds, UserPreference<T>] {
return [userPreferenceId, userPreference]
}

// TODO: Should be replaced with Object.fromEntries when the transpiler is updated
private createCompositePreferencesFromEntries(
entries: Array<[TPreferenceIds, UserPreference]>,
): CompositeUserPreferences<TPreferenceIds> {
entries: Array<[TPreferenceIds, UserPreference<T>]>,
): CompositeUserPreferences<TPreferenceIds, T> {
return entries.reduce(
(
composite: CompositeUserPreferences<TPreferenceIds>,
[userPreferenceId, preference]: [TPreferenceIds, UserPreference],
composite: CompositeUserPreferences<TPreferenceIds, T>,
[userPreferenceId, preference]: [TPreferenceIds, UserPreference<T>],
) => {
composite[userPreferenceId] = preference
return composite
},
// eslint-disable-next-line
{} as CompositeUserPreferences<TPreferenceIds>,
{} as CompositeUserPreferences<TPreferenceIds, T>,
)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type UserPreferenceScopeType } from './user-preference-scope-type'

export interface UserPreferenceDefinition {
export interface UserPreferenceDefinition<T> {
isOptedInByDefault: boolean
allowedScope: UserPreferenceScopeType
defaultData: any
defaultData: T
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type UserPreferenceDefinition } from './user-preference-definition'

export type UserPreferenceDefinitions<UserPreferenceId extends PropertyKey> = {
[Id in UserPreferenceId]?: UserPreferenceDefinition
export type UserPreferenceDefinitions<UserPreferenceId extends PropertyKey, T> = {
[Id in UserPreferenceId]?: UserPreferenceDefinition<T>
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface UserPreference {
export interface UserPreference<T> {
optedIn: boolean
data?: any
data?: T
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type UserPreferenceScope } from './user-preference-scope'
import { type UserPreference } from './user-preference'

export type UserPreferences<UserPreferenceId extends PropertyKey> = {
[K in UserPreferenceScope]?: { [Id in UserPreferenceId]: UserPreference }
export type UserPreferences<UserPreferenceId extends PropertyKey, T> = {
[K in UserPreferenceScope]?: { [Id in UserPreferenceId]: UserPreference<T> }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type UserPreference } from '../storage-models/user-preference'

export type CompositeUserPreferences<UserPreferenceId extends PropertyKey> = {
[Id in UserPreferenceId]: UserPreference
export type CompositeUserPreferences<UserPreferenceId extends PropertyKey, T> = {
[Id in UserPreferenceId]: UserPreference<T>
}
61 changes: 40 additions & 21 deletions src/services/user-preferences/user-preferences-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,26 @@ import { type Sync } from 'factory.ts'
import { faker } from '@faker-js/faker'

describe('When testing the User Preferences Service', () => {
let userPreferencesService: UserPreferencesService<TestUserPreferenceId>
let userPreferencesService: UserPreferencesService<TestUserPreferenceId, TestUserPreference>
const cookieKey = 'mp_u_p'
const lowLevelScope: UserPreferenceScope = '1-1-1'
let userPreferences: UserPreferences<TestUserPreferenceId>
let userPreferences: UserPreferences<TestUserPreferenceId, TestUserPreference>

let definitions: UserPreferenceDefinitions<TestUserPreferenceId>
const compositeUserPreferencesService = new CompositeUserPreferencesService<TestUserPreferenceId>()
let definitions: UserPreferenceDefinitions<TestUserPreferenceId, TestUserPreference>
const compositeUserPreferencesService = new CompositeUserPreferencesService<
TestUserPreferenceId,
TestUserPreference
>()

function setupPreferencesWithScope(
definition: UserPreferenceDefinitions<TestUserPreferenceId>,
definition: UserPreferenceDefinitions<TestUserPreferenceId, TestUserPreference>,
scope: UserPreferenceScope | undefined,
): void {
const scopedPreference = makeBuilderFromDefinition(definition, scope)
userPreferences = TestUserPreferencesFakeFactory([scopedPreference]) as UserPreferences<TestUserPreferenceId>
userPreferences = TestUserPreferencesFakeFactory([scopedPreference]) as UserPreferences<
TestUserPreferenceId,
TestUserPreference
>

Cookies.putObject(cookieKey, userPreferences)
}
Expand All @@ -44,11 +50,11 @@ describe('When testing the User Preferences Service', () => {
definitions = TestUserPreferenceDefinitionsFakeFactory([
{ id: TestUserPreferenceId.Default, allowedScope },
{ id: TestUserPreferenceId.PreferenceOne, allowedScope },
]) as UserPreferenceDefinitions<TestUserPreferenceId>
]) as UserPreferenceDefinitions<TestUserPreferenceId, TestUserPreference>

setupPreferencesWithScope(definitions, lowLevelScope)

userPreferencesService = new UserPreferencesService<TestUserPreferenceId>(
userPreferencesService = new UserPreferencesService<TestUserPreferenceId, TestUserPreference>(
definitions,
compositeUserPreferencesService,
lowLevelScope,
Expand Down Expand Up @@ -76,13 +82,16 @@ describe('When testing the User Preferences Service', () => {

it('it should read preferences when there are no scoped prefs', async () => {
// arrange
definitions = TestUserPreferenceDefinitionsFakeFactory() as UserPreferenceDefinitions<TestUserPreferenceId>
definitions = TestUserPreferenceDefinitionsFakeFactory() as UserPreferenceDefinitions<
TestUserPreferenceId,
TestUserPreference
>

const someScope = '1'
setupPreferencesWithScope(definitions, someScope)

const currentScope = '2'
userPreferencesService = new UserPreferencesService<TestUserPreferenceId>(
userPreferencesService = new UserPreferencesService<TestUserPreferenceId, TestUserPreference>(
definitions,
compositeUserPreferencesService,
currentScope,
Expand All @@ -108,12 +117,15 @@ describe('When testing the User Preferences Service', () => {

it("it should throw when the preference can't be found", async () => {
// arrange
definitions = TestUserPreferenceDefinitionsFakeFactory() as UserPreferenceDefinitions<TestUserPreferenceId>
definitions = TestUserPreferenceDefinitionsFakeFactory() as UserPreferenceDefinitions<
TestUserPreferenceId,
TestUserPreference
>

const someScope = '1'
setupPreferencesWithScope(definitions, someScope)

userPreferencesService = new UserPreferencesService<TestUserPreferenceId>(
userPreferencesService = new UserPreferencesService<TestUserPreferenceId, TestUserPreference>(
definitions,
compositeUserPreferencesService,
someScope,
Expand Down Expand Up @@ -154,11 +166,11 @@ describe('When testing the User Preferences Service', () => {
const testData = { test: 'test-data' }
definitions = TestUserPreferenceDefinitionsFakeFactory([
{ id: userPreferenceId, allowedScope, isOptedInByDefault: testOptedInState, defaultData: testData },
]) as UserPreferenceDefinitions<TestUserPreferenceId>
]) as UserPreferenceDefinitions<TestUserPreferenceId, TestUserPreference>

setupPreferencesWithScope(definitions, expectedScope as UserPreferenceScope)

userPreferencesService = new UserPreferencesService<TestUserPreferenceId>(
userPreferencesService = new UserPreferencesService<TestUserPreferenceId, TestUserPreference>(
definitions,
compositeUserPreferencesService,
lowLevelScope,
Expand Down Expand Up @@ -194,9 +206,9 @@ describe('When testing the User Preferences Service', () => {
const testData = { test: 'test-data' }
definitions = TestUserPreferenceDefinitionsFakeFactory([
{ id: userPreferenceId, allowedScope, isOptedInByDefault: testOptedInState, defaultData: testData },
]) as UserPreferenceDefinitions<TestUserPreferenceId>
]) as UserPreferenceDefinitions<TestUserPreferenceId, TestUserPreference>

userPreferencesService = new UserPreferencesService<TestUserPreferenceId>(
userPreferencesService = new UserPreferencesService<TestUserPreferenceId, TestUserPreference>(
definitions,
compositeUserPreferencesService,
lowLevelScope,
Expand Down Expand Up @@ -229,6 +241,10 @@ export enum TestUserPreferenceId {
PreferenceOne = 'preference-one',
}

export type TestUserPreference = {
test: string
}

export function TestUserPreferenceDefinitionsFakeFactory(
config?: Array<{
id: TestUserPreferenceId
Expand All @@ -237,8 +253,8 @@ export function TestUserPreferenceDefinitionsFakeFactory(
allowedScope?: UserPreferenceScopeType
}>,
): Sync.Builder<
UserPreferenceDefinitions<TestUserPreferenceId>,
keyof UserPreferenceDefinitions<TestUserPreferenceId>
UserPreferenceDefinitions<TestUserPreferenceId, TestUserPreference>,
keyof UserPreferenceDefinitions<TestUserPreferenceId, TestUserPreference>
> {
if (!config) {
config = Object.values(TestUserPreferenceId).map(id => ({ id }))
Expand Down Expand Up @@ -276,7 +292,7 @@ export interface TestUserPreferencesFakeBuilder {
}

export function makeBuilderFromDefinition(
definitions: UserPreferenceDefinitions<TestUserPreferenceId>,
definitions: UserPreferenceDefinitions<TestUserPreferenceId, TestUserPreference>,
scope?: UserPreferenceScope,
): TestUserPreferencesFakeBuilder {
return {
Expand All @@ -292,7 +308,10 @@ export function makeBuilderFromDefinition(

export function TestUserPreferencesFakeFactory(
scopes: TestUserPreferencesFakeBuilder[] = [],
): Sync.Builder<UserPreferences<TestUserPreferenceId>, keyof UserPreferences<TestUserPreferenceId>> {
): Sync.Builder<
UserPreferences<TestUserPreferenceId, TestUserPreference>,
keyof UserPreferences<TestUserPreferenceId, TestUserPreference>
> {
return scopes.reduce(
(scopedPreferences, { wantsRandom = false, scope, userPreferenceIds, optedIns, defaultDatas }) => {
const effectiveScope = scope ?? getRandomScope({ excludeGlobal: true })
Expand Down Expand Up @@ -324,7 +343,7 @@ export function TestUserPreferencesFakeFactory(
return scopedPreferences
},
{},
) as UserPreferences<TestUserPreferenceId>
) as UserPreferences<TestUserPreferenceId, TestUserPreference>
}

function getRandomScope({ maxScope = 3, excludeGlobal = false }): UserPreferenceScope {
Expand Down
Loading

0 comments on commit c5c6fc3

Please sign in to comment.