finished implementation of Base64BitFlagSets

This commit is contained in:
2026-03-14 00:33:47 +01:00
parent 06e7932ac5
commit 7de7d5e6cb
11 changed files with 419 additions and 91 deletions

View File

@@ -0,0 +1,73 @@
import { Base64BitFlagDefinitionFactory, FlagDefinition } from '~/definitions'
import { Base64BitFlagSet } from '~/flagsets'
import {
applyDeclarations,
FlagWithOrdinal,
ListOfFlagsWithOrdinal,
NamedFlagWithOrdinal,
} from './declarative'
import { FlagSetBuilder } from './generic'
import {
DefineWithOrdinal,
RequireParentsOrDefineWithOrdinal,
WithOrdinal,
WithOrdinalOrCompose,
} from './syntax'
import { InvalidOperationError } from '~/errors'
export class Base64BitFlagSetBuilder
implements
WithOrdinalOrCompose<string, Base64BitFlagSet>,
RequireParentsOrDefineWithOrdinal<string, Base64BitFlagSet>
{
private readonly _underlying: FlagSetBuilder<number>
public constructor() {
this._underlying = new FlagSetBuilder()
}
public define(): WithOrdinal<string, Base64BitFlagSet>
public define(alias: string): WithOrdinalOrCompose<string, Base64BitFlagSet>
public define(alias?: string): WithOrdinalOrCompose<string, Base64BitFlagSet> {
this._underlying.define(alias)
return this
}
public compose(...flags: string[]): DefineWithOrdinal<string, Base64BitFlagSet> {
this._underlying.compose(flags)
return this
}
public withValue(): never {
throw new InvalidOperationError('withValue')
}
public withOrdinal(
ordinal: number,
): RequireParentsOrDefineWithOrdinal<string, Base64BitFlagSet> {
this._underlying.withValue(ordinal)
return this
}
public requires(...flags: string[]): DefineWithOrdinal<string, Base64BitFlagSet> {
this._underlying.requires(flags)
return this
}
public getResult(): Base64BitFlagSet {
const graph = this._underlying.finish()
const factory = new Base64BitFlagDefinitionFactory()
return new Base64BitFlagSet(graph.intoDictionary(factory))
}
}
export function createBase64BitFlagSet(declarations: FlagWithOrdinal[]): Base64BitFlagSet
export function createBase64BitFlagSet<D extends string>(
declarations: Record<D, NamedFlagWithOrdinal>,
): Base64BitFlagSet & Record<D, FlagDefinition<number>>
export function createBase64BitFlagSet(declarations: ListOfFlagsWithOrdinal): Base64BitFlagSet {
const builder = new Base64BitFlagSetBuilder()
applyDeclarations(declarations, builder)
return builder.getResult()
}

View File

@@ -1,15 +1,4 @@
import type { DefineWithOrdinal, DefineWithValue, DefineWithValueOrOrdinal } from './syntax' import type { DefineWithValueOrOrdinal } from './syntax'
import { FlagsGraph, refByAlias } from '~/definitions'
// Generic helper functions
/**
* Copies the record keys into the 'as' property of the values and return an
* array containing those values.
*/
function toDeclarationArray<D>(record: Record<string, D>): (D & { as: string })[] {
return Object.keys(record).map((key) => ({ ...record[key], as: key }))
}
// Declarations for builders that supports only definitions by value // Declarations for builders that supports only definitions by value
@@ -23,17 +12,13 @@ export type ListOfFlagsWithValue<F> = FlagWithValue<F>[] | Record<string, NamedF
// Declarations for builders that supports only definitions by ordinal // Declarations for builders that supports only definitions by ordinal
export type FlagWithOrdinal<F> = export type FlagWithOrdinal =
| { ordinal: number; as?: string; requires?: string[] } | { ordinal: number; as?: string; requires?: string[] }
| { compose: string[]; as: string } | { compose: string[]; as: string }
export type NamedFlagWithOrdinal<F> = export type NamedFlagWithOrdinal = { ordinal: number; requires?: string[] } | { compose: string[] }
| { ordinal: number; requires?: string[] }
| { compose: string[] }
export type ListOfFlagsWithOrdinal<F> = export type ListOfFlagsWithOrdinal = FlagWithOrdinal[] | Record<string, NamedFlagWithOrdinal>
| FlagWithOrdinal<F>[]
| Record<string, NamedFlagWithOrdinal<F>>
// Declarations for builders that supports definitions by value and ordinal // Declarations for builders that supports definitions by value and ordinal
@@ -51,27 +36,43 @@ export type ListOfFlagsWithValueOrOrdinal<F> =
| FlagWithValueOrOrdinal<F>[] | FlagWithValueOrOrdinal<F>[]
| Record<string, NamedFlagWithValueOrOrdinal<F>> | Record<string, NamedFlagWithValueOrOrdinal<F>>
// Helper function
export interface AnyBuilder<F> {
define(alias: string | undefined): unknown
compose(...flags: string[]): unknown
withOrdinal(ordinal: number): unknown
withValue(value: F): unknown
requires(...flags: string[]): unknown
}
export function applyDeclarations<F>( export function applyDeclarations<F>(
declarations: ListOfFlagsWithValueOrOrdinal<F>, declarations: ListOfFlagsWithValueOrOrdinal<F>,
builder: DefineWithValueOrOrdinal<F, unknown, unknown>, builder: AnyBuilder<F>,
) { ) {
const declarationsArray = Array.isArray(declarations) let declarationsArray: FlagWithValueOrOrdinal<F>[]
? declarations if (Array.isArray(declarations)) {
: toDeclarationArray(declarations) declarationsArray = declarations
} else {
declarationsArray = Object.keys(declarations).map((key): FlagWithValueOrOrdinal<F> => {
return { ...declarations[key], as: key }
})
}
for (const declaration of declarationsArray) { for (const declaration of declarationsArray) {
builder.define(declaration.as)
if ('compose' in declaration) { if ('compose' in declaration) {
builder.define(declaration.as).compose(...declaration.compose) builder.compose(...declaration.compose)
} else if ('ordinal' in declaration) {
builder
.define(declaration.as!) // see note above
.withOrdinal(declaration.ordinal)
.requires(...(declaration.requires ?? []))
} else { } else {
builder if ('ordinal' in declaration) {
.define(declaration.as!) // see note above builder.withOrdinal(declaration.ordinal)
.withValue(declaration.value) } else {
.requires(...(declaration.requires ?? [])) builder.withValue(declaration.value)
}
if (declaration.requires !== undefined) {
builder.requires(...declaration.requires)
}
} }
} }
} }

View File

@@ -1,4 +1,5 @@
export { ArrayFlagSetBuilder, createArrayFlagSet } from './array' export { ArrayFlagSetBuilder, createArrayFlagSet } from './array'
export { Base64BitFlagSetBuilder, createBase64BitFlagSet } from './base64'
export { BigBitFlagSetBuilder, createBigBitFlagSet } from './bigint' export { BigBitFlagSetBuilder, createBigBitFlagSet } from './bigint'
export { CollectionFlagSetBuilder, createCollectionFlagSet } from './collection' export { CollectionFlagSetBuilder, createCollectionFlagSet } from './collection'
export { BitFlagSetBuilder, createBitFlagSet } from './number' export { BitFlagSetBuilder, createBitFlagSet } from './number'

View File

@@ -1,4 +1,4 @@
import type { FlagsDictionary, FlagSet } from '~' import type { FlagSet } from '~'
export interface Root<F, S, R = FlagSet<F, S>> { export interface Root<F, S, R = FlagSet<F, S>> {
/** /**
@@ -48,42 +48,42 @@ export interface RequireParentsOrDefineWithValue<F, S, R> extends DefineWithValu
// Syntax for builders that supports only definitions by ordinal // Syntax for builders that supports only definitions by ordinal
export interface DefineWithOrdinal<F, S, R = FlagSet<F, S>> extends Root<F, S, R> { export interface DefineWithOrdinal<S, R = FlagSet<number, S>> extends Root<number, S, R> {
/** /**
* Define an anonymous flag. * Define an anonymous flag.
*/ */
define(): WithOrdinal<F, S, R> define(): WithOrdinal<S, R>
/** /**
* Define a named flag. * Define a named flag.
* @param alias The name of the flag. * @param alias The name of the flag.
*/ */
define(alias: string): WithOrdinalOrCompose<F, S, R> define(alias: string): WithOrdinalOrCompose<S, R>
} }
export interface WithOrdinal<F, S, R> { export interface WithOrdinal<S, R> {
/** /**
* Set the value of this flag. * Set the value of this flag.
* @param ordinal The number of the flag (starting at 1). A unique value * @param ordinal The number of the flag (starting at 1). A unique value
* will be assigned based on this number. * will be assigned based on this number.
*/ */
withOrdinal(ordinal: number): RequireParentsOrDefineWithOrdinal<F, S, R> withOrdinal(ordinal: number): RequireParentsOrDefineWithOrdinal<S, R>
} }
export interface WithOrdinalOrCompose<F, S, R> extends WithOrdinal<F, S, R> { export interface WithOrdinalOrCompose<S, R> extends WithOrdinal<S, R> {
/** /**
* Define this flag as a composed flag. * Define this flag as a composed flag.
* @param flags The name of the flags in the group. * @param flags The name of the flags in the group.
*/ */
compose(...flags: string[]): DefineWithOrdinal<F, S, R> compose(...flags: string[]): DefineWithOrdinal<S, R>
} }
export interface RequireParentsOrDefineWithOrdinal<F, S, R> extends DefineWithOrdinal<F, S, R> { export interface RequireParentsOrDefineWithOrdinal<S, R> extends DefineWithOrdinal<S, R> {
/** /**
* Set the parents of this flag. * Set the parents of this flag.
* @param flags The names of the parent flags. * @param flags The names of the parent flags.
*/ */
requires(...flags: string[]): DefineWithOrdinal<F, S, R> requires(...flags: string[]): DefineWithOrdinal<S, R>
} }
// Syntax for builders that supports definitions by value and ordinal // Syntax for builders that supports definitions by value and ordinal
@@ -124,11 +124,8 @@ export interface WithValueOrOrdinalOrCompose<F, S, R> extends WithValueOrOrdinal
compose(...flags: string[]): DefineWithValueOrOrdinal<F, S, R> compose(...flags: string[]): DefineWithValueOrOrdinal<F, S, R>
} }
export interface RequireParentsOrDefineWithValueOrOrdinal<F, S, R> extends DefineWithValueOrOrdinal< export interface RequireParentsOrDefineWithValueOrOrdinal<F, S, R>
F, extends DefineWithValueOrOrdinal<F, S, R> {
S,
R
> {
/** /**
* Set the parents of this flag. * Set the parents of this flag.
* @param flags The names of the parent flags. * @param flags The names of the parent flags.

View File

@@ -0,0 +1,118 @@
import { decodeB64Byte, encodeB64Byte, normaliseB64String, ZERO_STRING } from '~/base64'
import { FlagDefinition, FlagDefinitionFactory, PartialFlagDefinition } from '.'
interface PrecomputedValues {
base: string
additive: string
subtractive: string
}
function union(first: string, second: string): string {
const [shorter, longer] = first.length < second.length ? [first, second] : [second, first]
let result = ''
for (let i = 0; i < longer.length; i++) {
if (i < shorter.length) {
const value = decodeB64Byte(shorter[i]) | decodeB64Byte(longer[i])
result += encodeB64Byte(value)
} else {
result += longer[i]
}
}
return normaliseB64String(result)
}
export class Base64BitFlagDefinition implements FlagDefinition<string> {
private readonly _baseValue: string
private readonly _additiveValue: string
private readonly _subtractiveValue: string
private readonly _alias: string | undefined
public constructor(precomputedValues: PrecomputedValues, alias: string | undefined) {
this._baseValue = precomputedValues.base
this._additiveValue = precomputedValues.additive
this._subtractiveValue = precomputedValues.subtractive
this._alias = alias
}
public get alias(): string | undefined {
return this._alias
}
public get values(): string {
return this._baseValue
}
public isIn(set: string): boolean {
let result = set.length >= this._additiveValue.length
for (let i = 0; i < this._additiveValue.length && result; i++) {
const value = decodeB64Byte(this._additiveValue[i])
result = (decodeB64Byte(set[i]) & value) === value
}
return result
}
public addTo(set: string): string {
return union(set, this._additiveValue)
}
public removeFrom(set: string): string {
return set
}
}
export class Base64BitFlagDefinitionFactory implements FlagDefinitionFactory<number, string> {
public makeDefinitions(
sortedPartialDefinitions: PartialFlagDefinition<number>[],
results: Map<PartialFlagDefinition<number>, FlagDefinition<string>>,
): void {
const precomputedValues = new Map<PartialFlagDefinition<number>, PrecomputedValues>()
for (let i = 0; i < sortedPartialDefinitions.length; i++) {
const pfd = sortedPartialDefinitions[i]
let stringValue = ZERO_STRING
if (pfd.value !== undefined) {
if (pfd.value < 1) {
throw new RangeError('Indices should be greater than or equal to 1.')
}
const indexFromZero = pfd.value - 1
const leadingBytes = ZERO_STRING.repeat(indexFromZero / 6)
const bigEnd = encodeB64Byte(1 << indexFromZero % 6)
stringValue = leadingBytes + bigEnd
}
const values: PrecomputedValues = {
base: stringValue,
additive: stringValue,
subtractive: stringValue,
}
for (const parentPfd of pfd.parents) {
const parentValues = precomputedValues.get(parentPfd)
if (parentValues !== undefined) {
values.additive = union(values.additive, parentValues.additive)
if (pfd.value === undefined) {
values.base = union(values.base, parentValues.base)
}
}
}
precomputedValues.set(pfd, values)
}
for (let i = sortedPartialDefinitions.length - 1; i >= 0; i--) {
const pfd = sortedPartialDefinitions[i]
const values = precomputedValues.get(pfd)!
for (const childPfd of pfd.children) {
const childValues = precomputedValues.get(childPfd)
if (childValues !== undefined) {
values.subtractive = union(values.subtractive, childValues.subtractive)
}
}
results.set(pfd, new Base64BitFlagDefinition(values, pfd.alias))
}
}
}

View File

@@ -1,4 +1,4 @@
import { FlagDefinition, FlagsDictionary, printFlagValue } from '~/definitions' import { FlagDefinition, FlagsDictionary } from '~/definitions'
import { InternalError } from '~/errors' import { InternalError } from '~/errors'
export interface PartialFlagInit<F> { export interface PartialFlagInit<F> {
@@ -14,7 +14,7 @@ export function refByAlias(refs: string[]): PartialFlagInit<never>[] {
export interface FlagDefinitionFactory<F, S> { export interface FlagDefinitionFactory<F, S> {
makeDefinitions( makeDefinitions(
sortedPartialDefinitions: PartialFlagDefinition<F>[], sortedPartialDefinitions: PartialFlagDefinition<F>[],
results: Map<PartialFlagDefinition<F>, FlagDefinition<F, S>>, results: Map<PartialFlagDefinition<F>, FlagDefinition<S>>,
): void ): void
} }
@@ -130,23 +130,27 @@ export class FlagsGraph<F> {
public intoDictionary<S>(factory: FlagDefinitionFactory<F, S>): FlagsDictionary<F, S> { public intoDictionary<S>(factory: FlagDefinitionFactory<F, S>): FlagsDictionary<F, S> {
const sortedPartialDefinitions = this.sortedDefinitions() const sortedPartialDefinitions = this.sortedDefinitions()
const definitions = new Map<PartialFlagDefinition<F>, FlagDefinition<F, S>>() const definitions = new Map<PartialFlagDefinition<F>, FlagDefinition<S>>()
factory.makeDefinitions(sortedPartialDefinitions, definitions) factory.makeDefinitions(sortedPartialDefinitions, definitions)
const aliasToDefinition = new Map<string, FlagDefinition<F, S>>() const aliasToDefinition = new Map<string, FlagDefinition<S>>()
for (const [alias, pfd] of this._aliasToDefinition.entries()) { for (const [alias, pfd] of this._aliasToDefinition.entries()) {
const definition = definitions.get(pfd) const definition = definitions.get(pfd)
if (definition === undefined) { if (definition === undefined) {
throw new InternalError(`factory didn't provide any definition for ${pfd}`) throw new InternalError(
`factory didn't provide any definition for ${pfd.toString()}`,
)
} }
aliasToDefinition.set(alias, definition) aliasToDefinition.set(alias, definition)
} }
const valueToDefinition = new Map<F, FlagDefinition<F, S>>() const valueToDefinition = new Map<F, FlagDefinition<S>>()
for (const [value, pfd] of this._valueToDefinition.entries()) { for (const [value, pfd] of this._valueToDefinition.entries()) {
const definition = definitions.get(pfd) const definition = definitions.get(pfd)
if (definition === undefined) { if (definition === undefined) {
throw new InternalError(`factory didn't provide any definition for ${pfd}`) throw new InternalError(
`factory didn't provide any definition for ${pfd.toString()}`,
)
} }
valueToDefinition.set(value, definition) valueToDefinition.set(value, definition)
} }
@@ -156,22 +160,22 @@ export class FlagsGraph<F> {
} }
class BiMapFlagsDictionary<F, S> implements FlagsDictionary<F, S> { class BiMapFlagsDictionary<F, S> implements FlagsDictionary<F, S> {
private readonly _aliasToDefinition: Map<string, FlagDefinition<F, S>> private readonly _aliasToDefinition: Map<string, FlagDefinition<S>>
private readonly _valueToDefinition: Map<F, FlagDefinition<F, S>> private readonly _valueToDefinition: Map<F, FlagDefinition<S>>
public constructor( public constructor(
aliasToDefinition: Map<string, FlagDefinition<F, S>>, aliasToDefinition: Map<string, FlagDefinition<S>>,
valueToDefinition: Map<F, FlagDefinition<F, S>>, valueToDefinition: Map<F, FlagDefinition<S>>,
) { ) {
this._aliasToDefinition = aliasToDefinition this._aliasToDefinition = aliasToDefinition
this._valueToDefinition = valueToDefinition this._valueToDefinition = valueToDefinition
} }
public findByAlias(alias: string): FlagDefinition<F, S> | undefined { public findByAlias(alias: string): FlagDefinition<S> | undefined {
return this._aliasToDefinition.get(alias) return this._aliasToDefinition.get(alias)
} }
public findByValue(value: F): FlagDefinition<F, S> | undefined { public findByValue(value: F): FlagDefinition<S> | undefined {
return this._valueToDefinition.get(value) return this._valueToDefinition.get(value)
} }
} }

View File

@@ -1,3 +1,4 @@
export { Base64BitFlagDefinition, Base64BitFlagDefinitionFactory } from './base64'
export { BigBitFlagDefinition, BigBitFlagDefinitionFactory } from './bigint' export { BigBitFlagDefinition, BigBitFlagDefinitionFactory } from './bigint'
export { FlagDefinition, FlagsDictionary, printFlagValue, valueToString } from './dictionary' export { FlagDefinition, FlagsDictionary, printFlagValue, valueToString } from './dictionary'
export { export {

View File

@@ -1,6 +1,18 @@
import { decodeB64Byte, encodeB64Byte, normaliseB64String, ZERO_STRING } from '~/base64'
import { FlagDefinition, FlagsDictionary } from '~/definitions'
import { Base64BitflagIterator, EnumerateFlags, useIterator } from '~/enumeration'
import type { FlagSet } from '.' import type { FlagSet } from '.'
import { decodeB64Byte, encodeB64Byte, normaliseB64String, ZERO_STRING } from '../base64'
import { Base64BitflagIterator, EnumerateFlags, useIterator } from '../enumeration' function toBase64(value: number): string {
if (value < 1) {
throw new RangeError('Indices should be greater than or equal to 1.')
}
const indexFromZero = value - 1
const leadingBytes = ZERO_STRING.repeat(indexFromZero / 6)
const bigEnd = encodeB64Byte(1 << indexFromZero % 6)
return leadingBytes + bigEnd
}
/** /**
* Provides flags that are stored in strings using a little-endian base 64 * Provides flags that are stored in strings using a little-endian base 64
@@ -11,22 +23,31 @@ import { Base64BitflagIterator, EnumerateFlags, useIterator } from '../enumerati
* instead if you need the data to be easily understandable by other systems. * instead if you need the data to be easily understandable by other systems.
*/ */
export class Base64BitFlagSet implements FlagSet<number, string> { export class Base64BitFlagSet implements FlagSet<number, string> {
/*protected wrapValue(value: number): string { private readonly _dictionary: FlagsDictionary<number, string>
if (value < 1) {
throw new RangeError( public constructor(dictionary: FlagsDictionary<number, string>) {
'Indices should be greater than or equal to 1.' this._dictionary = dictionary
) }
}
const indexFromZero = value - 1
const leadingBytes = ZERO_STRING.repeat(indexFromZero / 6)
const bigEnd = encodeByte(1 << indexFromZero % 6)
return leadingBytes + bigEnd
}*/
public none(): string { public none(): string {
return '' return ''
} }
public of(...values: number[]): string {
return normaliseB64String(
values.reduce((set, value) => this.union(set, toBase64(value)), ZERO_STRING),
)
}
public named(...aliases: string[]): string {
return normaliseB64String(
aliases.reduce(
(set, alias) => this.union(set, this.getFlag(alias)?.values ?? ZERO_STRING),
ZERO_STRING,
),
)
}
public union(first: string, second: string): string { public union(first: string, second: string): string {
let result = '' let result = ''
@@ -107,19 +128,41 @@ export class Base64BitFlagSet implements FlagSet<number, string> {
return result return result
} }
public hasAny(flags: string, required: string): boolean {
return this.minimum(this.intersection(flags, required)) !== ''
}
public hasAll(flags: string, required: string): boolean {
return this.isSuperset(flags, this.maximum(required))
}
public enumerate(flags: string): EnumerateFlags<number> { public enumerate(flags: string): EnumerateFlags<number> {
return useIterator(flags, Base64BitflagIterator) return useIterator(flags, Base64BitflagIterator)
} }
maximum(flags: string): string { public maximum(flags: string): string {
throw new Error('not implemented') let result = ZERO_STRING
for (const value of this.enumerate(flags)) {
const definition = this._dictionary.findByValue(value)
if (definition !== undefined) {
result = definition.addTo(result)
}
}
return normaliseB64String(result)
} }
minimum(flags: string): string { public minimum(flags: string): string {
throw new Error('not implemented') let result = ZERO_STRING
for (const value of this.enumerate(flags)) {
const definition = this._dictionary.findByValue(value)
if (definition !== undefined && definition.isIn(flags)) {
result = definition.addTo(result)
}
}
return normaliseB64String(result)
} }
public getFlag(alias: string): FlagDefinition<number, number> | undefined { public getFlag(alias: string): FlagDefinition<string> | undefined {
return this._dictionary.lookUp(alias) return this._dictionary.findByAlias(alias)
} }
} }

View File

@@ -1,5 +1,5 @@
import { EnumerateFlags } from '../enumeration' import { FlagDefinition } from '~/definitions'
import { FlagDefinition } from '../definitions' import { EnumerateFlags } from '~/enumeration'
/** /**
* Represents a group of flags of type `F` and the relationships between * Represents a group of flags of type `F` and the relationships between
@@ -128,11 +128,11 @@ export interface FlagSet<F, S> {
* @returns The corresponding definition, or `undefined` if there is no flag * @returns The corresponding definition, or `undefined` if there is no flag
* with this alias. * with this alias.
*/ */
getFlag(alias: string): FlagDefinition<F, S> | undefined getFlag(alias: string): FlagDefinition<S> | undefined
} }
export { ArrayFlagSet } from './array' export { ArrayFlagSet } from './array'
export { Base64BitFlagSet } from './base64' export { Base64BitFlagSet } from './base64'
export { BigBitFlagSet } from './bigint' export { BigBitFlagSet } from './bigint'
export { CollectionFlagSet } from './collection' export { CollectionFlagSet } from './collection'
export { BitFlagSet, BitFlags } from './number' export { BitFlags, BitFlagSet } from './number'

View File

@@ -3,6 +3,7 @@ export {
createBigBitFlagSet, createBigBitFlagSet,
createBitFlagSet, createBitFlagSet,
createCollectionFlagSet, createCollectionFlagSet,
createBase64BitFlagSet,
} from './builders' } from './builders'
export { FlagDefinition } from './definitions' export { FlagDefinition } from './definitions'
export { InvalidBitFlagValueError } from './errors' export { InvalidBitFlagValueError } from './errors'

View File

@@ -1,15 +1,40 @@
import { Base64BitFlagSet } from '~'
import { describe, expect, test } from 'vitest' import { describe, expect, test } from 'vitest'
import { Base64BitFlagSet, createBase64BitFlagSet } from '~'
describe(Base64BitFlagSet, () => { describe(Base64BitFlagSet, () => {
test('none', () => { test('none', () => {
const flags = new Base64BitFlagSet() const flags = createBase64BitFlagSet([])
expect(flags.none()).toEqual('') expect(flags.none()).toEqual('')
}) })
test('of', () => {
const flags = createBase64BitFlagSet([])
expect(flags.of()).toEqual('')
expect(flags.of(1)).toEqual('B')
expect(flags.of(2, 3)).toEqual('G')
})
test('named', () => {
const flags = createBase64BitFlagSet([
{ ordinal: 1, as: 'A' },
{ ordinal: 2, as: 'B' },
{ ordinal: 3, as: 'C' },
{ ordinal: 4, as: 'D' },
{ compose: ['A', 'B'], as: 'AB' },
{ compose: ['A', 'C'], as: 'AC' },
])
expect(flags.named()).toEqual('')
expect(flags.named('A')).toEqual('B')
expect(flags.named('AB', 'D')).toEqual('L')
expect(flags.named('AB', 'AC', 'B')).toEqual('H')
})
test('union', () => { test('union', () => {
const flags = new Base64BitFlagSet() const flags = createBase64BitFlagSet([])
expect(flags.union('', '')).toEqual('') expect(flags.union('', '')).toEqual('')
expect(flags.union('A', 'A')).toEqual('') expect(flags.union('A', 'A')).toEqual('')
@@ -20,7 +45,7 @@ describe(Base64BitFlagSet, () => {
}) })
test('difference', () => { test('difference', () => {
const flags = new Base64BitFlagSet() const flags = createBase64BitFlagSet([])
expect(flags.difference('', '')).toEqual('') expect(flags.difference('', '')).toEqual('')
expect(flags.difference('A', 'A')).toEqual('') expect(flags.difference('A', 'A')).toEqual('')
@@ -31,7 +56,7 @@ describe(Base64BitFlagSet, () => {
}) })
test('intersection', () => { test('intersection', () => {
const flags = new Base64BitFlagSet() const flags = createBase64BitFlagSet([])
expect(flags.intersection('', '')).toEqual('') expect(flags.intersection('', '')).toEqual('')
expect(flags.intersection('A', 'A')).toEqual('') expect(flags.intersection('A', 'A')).toEqual('')
@@ -43,7 +68,7 @@ describe(Base64BitFlagSet, () => {
}) })
test('isSuperset', () => { test('isSuperset', () => {
const flags = new Base64BitFlagSet() const flags = createBase64BitFlagSet([])
expect(flags.isSuperset('A', 'A')).toBe(true) expect(flags.isSuperset('A', 'A')).toBe(true)
expect(flags.isSuperset('D', 'A')).toBe(true) expect(flags.isSuperset('D', 'A')).toBe(true)
@@ -53,8 +78,40 @@ describe(Base64BitFlagSet, () => {
expect(flags.isSuperset('I', 'E')).toBe(false) expect(flags.isSuperset('I', 'E')).toBe(false)
}) })
test('hasAny', () => {
const flags = createBase64BitFlagSet([
{ ordinal: 1, as: 'A' },
{ ordinal: 2, as: 'B', requires: ['A'] },
{ ordinal: 3, as: 'C', requires: ['A'] },
{ ordinal: 4, as: 'D', requires: ['B', 'C'] },
])
expect(flags.hasAny('P', 'A')).toBe(false)
expect(flags.hasAny('A', 'A')).toBe(false)
expect(flags.hasAny('H', 'B')).toBe(true)
expect(flags.hasAny('B', 'H')).toBe(true)
expect(flags.hasAny('F', 'M')).toBe(false)
expect(flags.hasAny('C', 'C')).toBe(false)
})
test('hasAll', () => {
const flags = createBase64BitFlagSet([
{ ordinal: 1, as: 'A' },
{ ordinal: 2, as: 'B', requires: ['A'] },
{ ordinal: 3, as: 'C', requires: ['A'] },
{ ordinal: 4, as: 'D', requires: ['B', 'C'] },
])
expect(flags.hasAll('P', 'A')).toBe(true)
expect(flags.hasAll('A', 'A')).toBe(true)
expect(flags.hasAll('H', 'C')).toBe(true)
expect(flags.hasAll('G', 'C')).toBe(false)
expect(flags.hasAll('B', 'H')).toBe(false)
expect(flags.hasAll('F', 'M')).toBe(false)
})
test('enumerate', () => { test('enumerate', () => {
const flags = new Base64BitFlagSet() const flags = createBase64BitFlagSet([])
expect([...flags.enumerate('A')]).toEqual([]) expect([...flags.enumerate('A')]).toEqual([])
expect([...flags.enumerate('B')]).toEqual([1]) expect([...flags.enumerate('B')]).toEqual([1])
@@ -64,4 +121,36 @@ describe(Base64BitFlagSet, () => {
expect([...flags.enumerate('kB')]).toEqual([3, 6, 7]) expect([...flags.enumerate('kB')]).toEqual([3, 6, 7])
expect([...flags.enumerate('AAB')]).toEqual([13]) expect([...flags.enumerate('AAB')]).toEqual([13])
}) })
test('maximum', () => {
const flags = createBase64BitFlagSet([
{ ordinal: 1, as: 'A' },
{ ordinal: 2, as: 'B', requires: ['A'] },
{ ordinal: 3, as: 'C', requires: ['A'] },
{ ordinal: 4, as: 'D', requires: ['B', 'C'] },
])
expect(flags.maximum('A')).toEqual('')
expect(flags.maximum('B')).toEqual('B')
expect(flags.maximum('C')).toEqual('D')
expect(flags.maximum('D')).toEqual('D')
expect(flags.maximum('E')).toEqual('F')
expect(flags.maximum('I')).toEqual('P')
})
test('minimum', () => {
const flags = createBase64BitFlagSet([
{ ordinal: 1, as: 'A' },
{ ordinal: 2, as: 'B', requires: ['A'] },
{ ordinal: 3, as: 'C', requires: ['A'] },
{ ordinal: 4, as: 'D', requires: ['B', 'C'] },
])
expect(flags.minimum('A')).toEqual('')
expect(flags.minimum('B')).toEqual('B')
expect(flags.minimum('C')).toEqual('')
expect(flags.minimum('D')).toEqual('D')
expect(flags.minimum('E')).toEqual('')
expect(flags.minimum('N')).toEqual('F')
})
}) })